【网络编程】 基于UDP的网络聊天室

server/2024/10/16 4:29:40/

前言

        将前面的数据结构,多线程,网络的内容加在一起的一个项目,比较综合,在代码部分采用了分文件编译并且写了比较详细的注释(个人觉得)。

ps:希望对大家有用

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

项目原理分析图

服务端

chatser.h

#ifndef CHATSER_H
#define CHATSER_H
#include<myhead.h>
//从客户端接收消息结构体
typedef struct msgTyp
{char type;//消息类型char userName[20];//用户名char msgText[1024];//消息数据
}msgTyp,*msgTypPtr;//用户信息
typedef struct users
{char userName[20];//用户名struct sockaddr_in cin;//客户端的信息
}Users,*UsersPtr;
//链表的结构体
typedef struct Node
{union {int len;//链表长度UsersPtr data;//用户信息};struct Node *next;//指针域
}Node, *NodePtr;
//创建传递给线程函数的结构体
typedef struct argTyp
{NodePtr L;//链表struct sockaddr_in cin;//客户端的信息struct sockaddr_in sin;//服务器的信息int sfd;//套接字msgTyp msg;//客户端接收消息结构体
}argTyp,*argTypPtr;//创建链表
NodePtr carete_link();
//创建节点
NodePtr create_node(char *username,struct sockaddr_in cin);
//服务器发送消息
void *send_msg(void *arg);
//服务器接收并转发消息
void *recv_msg(void *arg);
//删除节点
void delete_node(NodePtr L, char *username);#endif

chatser.c

#include"chatser.h"
//创建链表
NodePtr carete_link()
{//创建头节点NodePtr L = (NodePtr)malloc(sizeof(Node));if (NULL == L){printf("创建头节点失败\n");return NULL;}//初始化头节点L->len = 0;L->next = NULL;return L;
}
//创建节点
NodePtr create_node(char *username,struct sockaddr_in cin)
{//创建节点NodePtr p =(NodePtr)malloc(sizeof(Node));if (NULL == p){printf("创建节点失败\n");return NULL;}//初始化节点信息p->data = (UsersPtr)malloc(sizeof(Users));//给data用户信息分配空间strcpy(p->data->userName,username);//将用户名复制到data的usernamep->data->cin = cin;//将客户端信息复制到data的cinp->next = NULL;return p;
}
//服务器发送消息
void *send_msg(void *arg)//传入线程函数的结构体
{//获取结构体argTypPtr argTyp = (argTypPtr)arg;//结构体NodePtr L = argTyp->L;//链表struct sockaddr_in sin = argTyp->sin;//服务器信息int sfd = argTyp->sfd;//socketwhile (1){//从终端输入消息char buf[1024] = "";//未处理消息char wbuf[1024] = "";//处理后的消息fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = 0;//处理消息snprintf(wbuf,sizeof(wbuf),"**system**:%s",buf);//遍历链表发送消息NodePtr p = L->next;while(p!=NULL){if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0){printf("发送消息失败\n");return NULL;}p = p->next;}printf("**system** [%s:%d]:chat成功\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); }pthread_exit(NULL);//退出线程       return NULL;
}
//服务器接收并转发消息
void *recv_msg(void *arg)//传入线程函数的结构体
{//获取结构体argTypPtr argTyp = (argTypPtr)arg;NodePtr L = argTyp->L;//链表struct sockaddr_in cin = argTyp->cin;//客户端信息int sfd = argTyp->sfd;//socketchar username[20] = "";//用户名char buf[1024] = "";//未处理的消息char wbuf[1024] = "";//处理后的消息//获取用户名strcpy(username,argTyp->msg.userName);  //获取是什么类型的消息if(argTyp->msg.type == 'L')//登录   {//判断是否有该用户NodePtr q = L->next;// while(q!=NULL)// {//     if(strcmp(q->data->userName,username) == 0)//     {//         printf("该用户已存在\n");//         break;//         return NULL;//     }// }//创建节点NodePtr p = create_node(username,cin);//将节点添加到链表(我采用头插)p->next = L->next;L->next = p;L->len++;//链表长度加1//处理消息snprintf(wbuf,sizeof(wbuf),"**%s已登录**",username);//遍历链表发送消息p = L->next;while(p!=NULL){if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0){printf("发送消息失败\n");return NULL;}p = p->next;}printf("%s [%s:%d]:登录成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));}else if(argTyp->msg.type == 'C')//聊天{//处理消息snprintf(wbuf,sizeof(wbuf),"%s:%s",username,argTyp->msg.msgText);//遍历链表转发消息NodePtr p = L->next;while(p!=NULL){if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0){printf("发送消息失败\n");return NULL;}p = p->next;}printf("%s [%s:%d]:chat成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));}else if(argTyp->msg.type == 'Q')//退出{//删除节点delete_node(L,username);//处理消息snprintf(wbuf,sizeof(wbuf),"**%s已退出**",username);//循环遍历链表发送消息NodePtr p = L->next;while(p!=NULL){if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0){printf("发送消息失败\n");return NULL;}p = p->next;}  printf("%s [%s:%d]:退出成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));L->len--;//链表长度减1}pthread_exit(NULL);//退出线程   return NULL;
}
//删除节点
void delete_node(NodePtr L, char *username)
{//删除节点
void delete_node(NodePtr L, char *username)
{//找到要删除节点的前驱节点NodePtr p = L;while (p->next != NULL && strcmp(p->next->data->userName, username) != 0){p = p->next;}//删除节点if (p->next != NULL){NodePtr temp = p->next; // 保存要删除的节点p->next = p->next->next; // 删除节点free(temp->data); // 释放节点数据free(temp); // 释放节点}
}
}

sermain.c

#include"chatser.h"
int main(int argc, const char *argv[])
{//创建链表NodePtr L = carete_link();//创建管道int sfd = socket(AF_INET,SOCK_DGRAM,0);if (sfd == -1){perror("socket");return 1;}if(argc != 3){printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号return 1;}//填充服务端地址结构体struct sockaddr_in sin;sin.sin_family = AF_INET;	sin.sin_port = htons(atoi(argv[2]));sin.sin_addr.s_addr = inet_addr(argv[1]);//绑定if (bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1){perror("bind error");return 1;}//获取客户端地址结构体struct sockaddr_in cin;socklen_t len = sizeof(cin);//线程实现收发数据pthread_t pid1,pid2;//填充结构体信息struct argTyp arg = {L,cin,sin,sfd};//创建发送线程if(pthread_create(&pid1,NULL,send_msg,&arg)==-1){perror("pthread_create error");return 1;}//将线程设置为分离状态pthread_detach(pid1);while(1){//接收客户端发送的数据if(recvfrom(sfd,&arg.msg,sizeof(arg.msg),0,(struct sockaddr*)&cin,&len)==-1){perror("recvfrom error");return 1;}//填充结构体struct argTyp arg1 = {L,cin,sin,sfd};arg1.msg = arg.msg;//创建接收线程if(pthread_create(&pid2,NULL,recv_msg,&arg1)==-1){perror("pthread_create error");return 1;}//将线程设置为分离状态//pthread_detach(pid1);pthread_detach(pid2);}close(sfd);return 0;
}

客户端

chatcli.h

#ifndef CHATCLI_H
#define CHATCLI_H
#include<myhead.h>
//创建发送消息结构体
typedef struct msgTyp
{char type;//消息类型char userName[20];//用户名char msgText[1024];//消息数据
}msgTyp,*msgTypPtr;//创建线程函数传输的结构体
typedef struct argTyp
{struct sockaddr_in sin;//服务器的信息int cfd;//套接字msgTyp msg;//客户端接收消息结构体
}argTyp,*argTypPtr;//客户端发送消息
void *send_msg(void *arg);
//客户端接收并转发消息
void *recv_msg(void *arg);// 将全局变量改为外部声明
extern int flag; // 标志位判断用户是否创建
extern char userName[20]; // 存放姓名
extern int num; // 记录用户是否退出#endif

chatcli.c

#include"chatcli.h"
// 在这里定义全局变量
int flag = 0;
char userName[20] = "";
int num = 1;//客户端发送消息
void *send_msg(void *arg)
{//printf("?\n");argTypPtr argTyp = (argTypPtr)arg;//获取传来的结构体struct sockaddr_in sin = argTyp->sin;int cfd = argTyp->cfd;char msgText[1024]="";//存放消息内容char buf[1024]="";//用来中转的while (num == 1){fgets(buf,sizeof(buf),stdin);//获取终端传来的数据buf[strlen(buf)-1]=0;//表示该用户未注册if(flag ==0){//printf("%d\n",flag);argTyp->msg.type = 'L';//发送的消息类型为登录strcpy(userName, buf);//将用户名记录下来//printf("%d\n",flag);strcpy(argTyp->msg.userName, buf);//给用户名赋值//printf("%s\n",argTyp->msg.userName);strcpy(argTyp->msg.msgText, msgText);//给消息数据赋值//printf("%d\n",flag);if (sendto(cfd, &argTyp->msg, sizeof(argTyp->msg), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1){perror("sendto error");return NULL;}flag++;//表示用户已经注册//printf("%d\n",flag);}else if (flag > 0){if(strcmp(buf,"quit")==0){argTyp->msg.type = 'Q';//发送的消息类型为登出strcpy(argTyp->msg.userName,userName);//给用户名赋值strcpy(argTyp->msg.msgText,msgText);//给消息数据赋值if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("sendto error");return NULL;}num = 0;//表示退出break;}argTyp->msg.type = 'C';//发送的消息类型为聊天strcpy(argTyp->msg.userName,userName);//给用户名赋值strcpy(argTyp->msg.msgText,buf);//给消息数据赋值if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("sendto error");return NULL;}}}pthread_exit(NULL);
}
//客户端接收消息
void *recv_msg(void *arg)
{argTypPtr argTyp = (argTypPtr)arg;//接收传来的结构体int cfd = argTyp->cfd;//获取套接字char buf[1024]="";//存放消息内容	while (num == 1){bzero(buf,sizeof(buf));//清空容器if(recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL)==-1){perror("recvfrom error");return NULL;}printf("%s\n",buf);}pthread_exit(NULL);
}

climain.c

#include"chatcli.h"
// 声明 num 为外部变量
extern int num;
int main(int argc, const char *argv[])
{//创建管道int cfd = socket(AF_INET,SOCK_DGRAM,0);if (cfd == -1){perror("socket");return 1;}//判断输入的参数是否正确if(argc != 3){printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号return 1;}//填充服务端地址结构体struct sockaddr_in sin;sin.sin_family = AF_INET;	sin.sin_port = htons(atoi(argv[2]));sin.sin_addr.s_addr = inet_addr(argv[1]);//创建线程pthread_t pid1,pid2;//结构体赋值struct argTyp arg = {sin,cfd};//创建发送线程if(pthread_create(&pid1,NULL,send_msg,&arg) != 0){printf("创建线程失败\n");return -1;}//创建接收线程if(pthread_create(&pid2,NULL,recv_msg,&arg) != 0){printf("创建线程失败\n");return -1;}//设置线程分离pthread_detach(pid1);pthread_detach(pid2);while (num);	close(cfd);return 0;
}

 项目效果图


http://www.ppmy.cn/server/102453.html

相关文章

机器学习/自主系统与亚当·斯密

人工智能中的机器学习和自主系统是当前科技领域的热门话题&#xff0c;它们与亚当斯密的经济学理论之间可能存在一些潜在的联系和启示。亚当斯密的经济学理论主要关注市场经济的运行和资源分配。他的核心观点是&#xff0c;通过市场机制的作用&#xff0c;个体追求自身利益的行…

python绘制蕨菜叶分形

一花一叶一世界,一草一木一浮生. 使用了四个不同的线性变换&#xff0c;根据概率选择其中一个变换并更新 x 和 y 坐标。然后将生成的绿色点绘制出来&#xff0c;形成一片蕨菜叶。 import numpy as np import matplotlib.pyplot as pltdef fern_fractal(num_points):# 初始化坐…

初始化列表 / 隐式转换 / 静态

目录 初始化列表隐式转换单参数的隐式类型转换多参数的隐式类型转换explicit关键字 static 初始化列表 大部分时候成员变量在对象实例化的时候调用构造函数就整体定义了&#xff0c;注意此时只有定义&#xff0c;不算初始化。而定义后的值的值是在构造函数里面给的。我们知道构…

王立铭脑科学50讲——05篇,脑到底是有什么组成?

王立铭脑科学50讲——05篇&#xff0c;脑到底是有什么组成&#xff1f; 我对课程感兴趣的点&#xff1a; 1、神经元学说 &#xff08;1&#xff09;神经细胞的信号传递有明确的方向&#xff0c;从树突到细胞体&#xff0c;再从细胞体到轴突 &#xff08;2&#xff09;细胞的…

初识Linux · 基本指令(1)

目录 前言&#xff1a; 基本指令 1.1 pwd 1.2 ls 1.3 mkdir cd clear 1.4 touch 1.5 ls部分补充 1.6 whoami 1.7 有关目录以及路径 前言&#xff1a; 今天是Linux系列的第一章节&#xff0c;对于Linux的主线学习大概会更新两个半月左右&#xff0c;中间穿插着算法…

Java垃圾收集器工作原理

在Java编程中&#xff0c;对象的内存分配主要发生在堆&#xff08;Heap&#xff09;上。堆是Java虚拟机&#xff08;JVM&#xff09;中的一块运行时数据区&#xff0c;用于存放由new关键字创建的对象和数组。与栈&#xff08;Stack&#xff09;内存分配相比&#xff0c;堆内存分…

Python知识点:如何使用OpenCV与Raspberry Pi进行摄像头应用

要在Raspberry Pi上使用OpenCV进行摄像头应用&#xff0c;你可以按照以下步骤进行操作&#xff1a; 安装OpenCV和相关依赖&#xff1a;首先&#xff0c;确保你的Raspberry Pi系统是最新的&#xff0c;然后安装OpenCV库。你可以通过apt-get或pip来安装OpenCV。例如&#xff0c;使…

使用docker compose一键部署redis服务

使用docker compose一键部署redis服务 1、创建安装目录 mkdir /data/redis/ -p && cd /data/redis2、创建docker-compose.yml文件 version: 3 services:redis:image: registry.cn-hangzhou.aliyuncs.com/xiaopangpang/redis:7.0.5container_name: redisrestart: al…