欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > 【网络编程】 基于UDP的网络聊天室

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

2024/10/24 21:36:21 来源:https://blog.csdn.net/m0_62859255/article/details/141272316  浏览:    关键词:【网络编程】 基于UDP的网络聊天室

前言

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

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;
}

 项目效果图

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com