您当前的位置: 首页 >  udp

LinuxC语言のUDP简易聊天室 sokcet

发布时间:2021-11-01 17:19:00 ,浏览量:8

设计思路

考虑到只是一个简易版本的UDP聊天服务,所以很多不完善的地方

服务器

服务器我是开了一个父子进程,分别负责的

  • 接受客户端的消息&&发送某一个客户端的信息
  • 服务器的命令终端(只不过没有实现,只写了广播功能)

然后我封装了一个数据结构,每当客户端发一个包过来,这个包由三部分组成

  • 消息类型(login、cheat、end)
  • 发送者的姓名
  • 发送者的消息构成

然后我们分三种情况来解析这三种消息就行,具体实现就是一个简单的链表结构,详情可以看代码。

CODE
/*
 *
 * 缺点:
 * 1.以奇怪的方式下线的用户不能清理
 * 
 */ #include  #include  #include  #include  #include  #include  #include  #include  #define false 0 #define true 1 #define N 1024 struct MSG{//消息封装 char type; char name[20]; char text[N]; }; struct Node {//用户节点信息 struct sockaddr_in addr; struct Node * next; }; struct Node * init() {//用户节点初始化 struct Node * p = (struct Node *)malloc(sizeof(struct Node)); memset(p,0,sizeof(struct Node)); p->next = NULL; return p; } const char *ip = "127.0.0.1"; const int port = 8000; struct Node *head; void login(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//登陆消息 struct Node *p = head; strcpy(msg.text,msg.name); msg.text[strlen(msg.text) - 1] = '\0'; strcat(msg.text," 已经上线欢迎来聊^_^~"); //puts(msg.text); int cnt = 0; while(p->next) {//发给已经上线的用户 if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr)) < 0){ perror("online error"); } p = p->next; cnt++; } struct Node *k = (struct Node *)malloc(sizeof(struct Node)); k->addr = clientaddr; k->next = NULL; p->next = k; printf("当前总用户数:%d \n上线用户的port: %u \n",cnt,ntohl(clientaddr.sin_port)); } void chat(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//聊天消息处理函数 struct Node *p = head; char str[N]= {0}; //puts(msg.name); //puts(msg.text); sprintf(str,"%s : %s",msg.name,msg.text); strcpy(msg.text,str); puts(msg.text);//输出到服务器终端 while(p->next) { if (memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) != 0) { if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *) &p->next->addr, sizeof(p->next->addr)) < 0) { perror("chat sendto error"); } } p = p->next; } } void quit(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//退出函数 struct Node *p = head; struct Node *q = NULL; sprintf(msg.text,"%s用户已下线",msg.name); puts(msg.text); while(p->next) {//因为头号节点是服务器 if(memcmp(&clientaddr,&p->next->addr,sizeof(clientaddr)) == 0) { q = p->next; p->next = q->next; free(q); q=NULL;//free后将指针赋为NULL } else { sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr)); } p = p->next; } } int socketfd; void my_end(int sign_no) {//这个是注册一个ctr+c退出时候的一个信号处理 close(socketfd); exit(1); } int main() { signal(SIGINT,my_end); head = init(); struct sockaddr_in serveraddr,clientaddr; bzero(&serveraddr,sizeof(serveraddr)); bzero(&clientaddr,sizeof(clientaddr)); struct MSG msg; socklen_t addrlen = sizeof(struct sockaddr); char buf[N] = ""; if((socketfd = socket(AF_INET, SOCK_DGRAM, 0))< 0) { perror("socket error"); } printf("socketfd = %d\n",socketfd); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(port); printf("serveraddr.sin_port = %d\n", htonl(port)); if(bind(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) { // bind绑定 perror("bind error"); } printf("serveraddr.sin_port = %u\n", serveraddr.sin_port); printf("Accepting connections ...\n"); pid_t pid = fork(); if(pid < 0) { perror("fork error"); } if(pid == 0) {//服务器的命令终端(只不过没有实现,只写了广播功能) memset(&msg,0,sizeof(msg)); strncpy(msg.name,"server",6); msg.type = 'C'; while(1) { fgets(buf,N,stdin); strcpy(msg.texxt,buf); msg.text[strlen(msg.text)-1] = '\0'; puts(msg.text); sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr*)&serveraddr,addrlen); } } else {//接受客户端的消息&&发送某一个客户端的信息 while(1) { // puts("father"); if(recvfrom(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&clientaddr,&addrlen) <= 0){ perror("father recvfrom error"); } if(msg.type == 'L') {//login puts("YES"); login(socketfd,msg,clientaddr); } else if(msg.type == 'C') {//chat chat(socketfd,msg,clientaddr); } else if(msg.type == 'Q') {//quit quit(socketfd,msg,clientaddr); } } } close(socketfd);//正常退出的close处理 return 0; } 
客户端

客户端这边的话就稍微简单一点了,因为只有接受和发送这两个事件

  • 发送事件

    • 登陆发送一次
    • 退出发送一次
    • 聊天一直发送
  • 接收事件

    其实我们只用接受服务器给我们发送的消息即可

总体逻辑非常的简单,详情请看代码

CODE
// // Created by Mangata on 2021/10/27. // #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #define N 1024 struct MSG{ char type; char name[20]; char text[N]; }msg; int port = 8000; char *ip = "127.0.0.1"; //int socketfd; //struct sockaddr_in serveraddr; /*void my_fun(int signal_) {
    msg.type = 'Q';
    if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
        perror("Q - sendto error");
         exit(1);
     }
     printf("You Quit !");
     kill(getppid(),SIGKILL);
     exit(1);
}
*/ int main() { struct MSG msg; int socketfd; struct sockaddr_in serveraddr; bzero(&serveraddr,sizeof(serveraddr)); socklen_t addrlen; //signal(SIGINT, my_fun); if((socketfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket failed"); printf("socket failed"); exit(1); } serveraddr.sin_family = AF_INET; //    serveraddr.sin_addr.s_addr = inet_addr(ip); serveraddr.sin_port = htons(port); inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr); printf("输入用户名:"); fgets(msg.name,sizeof(msg.name),stdin); msg.name[strlen(msg.name)-1]='\0'; msg.text[0]='\0'; msg.type= 'L'; printf("name = %s\t type = %c\n",msg.name,msg.type); if(sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {//发送登陆信号 perror("fail to sendto"); printf("fail to sendto"); exit(1); } pid_t pid=fork(); if(pid == 0) {//子进程输入数据 while(1) { fgets(msg.text,N,stdin); msg.text[strlen(msg.text)-1]='\0'; printf("send msg = %s\n",msg.text); if(!strncmp(msg.text,"quit",4)) { msg.type = 'Q'; if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) { perror("Q - sendto error"); exit(1); } printf("You Quit !"); kill(getppid(),SIGKILL); exit(1); } else { msg.type = 'C'; if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0) { printf("C - sendto error"); exit(1); } } } } else { while(1) { recvfrom(socketfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, &addrlen); puts(msg.text); } } close(socketfd); return 0; } 
关于BUG

由于写的时间比较短,有一些发现的bug,但是还不清楚原因

  • 当所有的用户退出后,服务器会段错误然后宕机
  • 用户推出后会有一个用户收不到退出消息(这个原因是删除退出用户的链表的问题,只不过不想修了)
  • 服务器断开的时候,客户端不会有反馈,emmm这个其实只是我没写,逻辑还是蛮简单的,可以给用户发送一个Q类型的消息,然后让用户也自动退出即可 其他倒是没有什么东西了,服务器和用户端的端口是设死了的,所以想要更改的直接改源码就行
效果图 服务器端

在这里插入图片描述

客户端口

在这里插入图片描述 在这里插入图片描述

over

关注
打赏
1688896170
查看更多评论

暂无认证

  • 8浏览

    0关注

    105695博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0537s