• udp群聊


    1. server端维护一个链表,用于存放客户端的联系方式。结构如下:

    typedef struct sockaddr_in SA ;
    
    typedef struct client_tag
    {
        SA ct_addr;
        struct client_tag* ct_next;
    }CNODE, *pcNODE;
    2. 服务器创建一个socket端口,用于接收客户端发送的消息。消息类别分为:通知上线,通知下线,以及聊天信息。因为消息类别不同,我们使用结构体将客户端发送的消息进行如下封装:
    #define TYPE_ON   1
    #define TYPE_OFF  2
    #define TYPE_CHAT 3
    
    #define SIZE 1024
    
    typedef struct msg_tag
    {
        int  msg_type;
        int  msg_len;  /* 实际消息长度 */
        char msg_buf[SIZE];
    }MSG, *pMSG;

    注意,服务器所创建的socket端口需要绑定自己的联系方式,以便其他客户端可以发消息(sendto函数)给服务器。

    3. 服务器使用select轮询函数监听自己的socket端口。当返回值为0(轮询时间内没有客户端发消息)或者-1(收到信号,出错)时,继续轮询;当返回值为1时,说明有客户端发送消息。我们可以从recvfrom函数的传出参数中获取客户端的联系方式,此时根据收到的MSG类型,进行处理。如果MSG类型为上线,则将该客户端的联系方式加入链表;如果MSG类型为下线,则将其从链表中删除;如果MSG类型为聊天信息,则服务器将其转发给所有客户端。

    server端

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #define TYPE_ON   1
    #define TYPE_OFF  2
    #define TYPE_CHAT 3
    #define SIZE 1024
    typedef struct sockaddr_in SA ;
    typedef struct msg_tag
    {
        int msg_type ;
        int msg_len;
        char msg_buf[SIZE] ;
    }MSG, *pMSG;
    typedef struct client_tag
    {
        SA ct_addr ;
        struct client_tag* ct_next ;
    }CNODE, *pCNODE;
    
    void msg_broadcast(int sockfd, char* msg, pCNODE phead)
    {
        int n ;
        while(phead)
        {
            n = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&phead -> ct_addr, sizeof(SA) );
    
            printf("%d: %s : %d 
    ", n, inet_ntoa(phead -> ct_addr.sin_addr), ntohs(phead -> ct_addr.sin_port));
            phead = phead -> ct_next ;
        }
    }
    
    void list_insert(pCNODE * phead, pCNODE p)
    {
        p ->ct_next = *phead ;
        *phead = p ;
    }
    
    void list_delete(pCNODE* phead, SA* p)
    {
        pCNODE pCur, pPre ;
        pPre = NULL ;
        pCur = *phead ;
        while(pCur)
        {
            if(pCur -> ct_addr.sin_port == p ->sin_port && pCur ->ct_addr.sin_addr.s_addr ==p ->sin_addr.s_addr )
            {
                break ;
            }else 
            {
                pPre = pCur ;
                pCur = pCur -> ct_next ;
            }
        }
        if(pPre == NULL)
        {
            *phead = pCur -> ct_next ;
            free(pCur);
            pCur = NULL ;
        }else 
        {
            pPre -> ct_next = pCur -> ct_next ;
            free(pCur);
            pCur = NULL ;
        }
    }
    
    
    int main(int argc, char* argv[])// EXE CONF
    {
        if(argc != 2)
        {
            printf("USAGE: EXE CONF ! 
    ");
            exit(1);
        }
        pCNODE my_list = NULL ;
        /* 创建服务器socket端口 */    
        int fd_server ;
        if((fd_server = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
        {
            perror("socket");
            exit(1);
        }
        /* 从配置文件中读取服务器联系方式,以便绑定到socket端口 */
        FILE* fp_conf ;    
        fp_conf = fopen(argv[1], "r");
        if(fp_conf == NULL)
        {
            perror("fopen");
            exit(1);
        }
        char server_ip[32]="";
        int server_port ;
        fscanf(fp_conf,"%s%d",server_ip, &server_port);
        fclose(fp_conf);
        /* 绑定服务器socket端口的联系方式 */
        SA server_addr ;
        memset(&server_addr, 0, sizeof(SA));
        server_addr.sin_family = AF_INET ;
        server_addr.sin_port = htons(server_port);
        server_addr.sin_addr.s_addr = inet_addr(server_ip);
        if(-1 ==bind(fd_server, (struct sockaddr*)&server_addr, sizeof(SA)))
        {
            perror("bind");
            close(fd_server);
            exit(1);
        }
        /* 设置select参数:监听集合以及轮询时间 */
        fd_set readset, readyset ;
        FD_ZERO(&readset);
        FD_ZERO(&readyset);
        FD_SET(fd_server, &readset);
        struct timeval tm ;
        
        /* 进入轮询 */
        int select_ret ;
        while(1)
        {
            readyset = readset ;
            tm.tv_sec = 0 ;
            tm.tv_usec = 1000 ;
            select_ret = select(fd_server + 1, &readyset, NULL, NULL, &tm);
            if(select_ret == 0)
            {
                continue ;
            }else if(select_ret == -1)
            {
                continue ;
            }else if(select_ret == 1)
            {
                pCNODE pNew = (pCNODE)calloc(1, sizeof(CNODE));
                int len = sizeof(SA);
                char info[1024];
                MSG my_msg ;
                memset(&my_msg, 0, sizeof(MSG));
                recvfrom(fd_server,&my_msg,sizeof(my_msg), 0, (struct sockaddr*)&(pNew ->ct_addr), &len);
                    if(my_msg.msg_type == TYPE_ON)//on
                    {
                        list_insert(&my_list, pNew );
                        printf("%s:%d on! 
    ",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port));
                    
                    }else if(my_msg.msg_type == TYPE_OFF)// off
                    {
                        list_delete(&my_list, &(pNew -> ct_addr) );
                        printf("%s:%d off! 
    ",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port));
                        //kris add: 当客户端通知下线时,发送一条空消息给客户端,这样可以使对方孙子的recvfrom返回值为0
                        //从而可以退出循环,退出进程
                        sendto(fd_server,"",0,0,(struct sockaddr*)&(pNew->ct_addr),sizeof(SA));
                    }else //send
                    {
                        printf("chat msg ! 
    ");
                        memset(info, 0, 1024);
                        sprintf(info,"	from %s:%5d:
    %s
    ",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port),my_msg.msg_buf);
                        puts(info);
                        msg_broadcast(fd_server, info, my_list);    
                    }
            }
        }
    
        return 0 ;
    }

    client端
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    typedef struct sockaddr* pSA ;
    typedef struct sockaddr_in SA ;
    #define TYPE_ON 1
    #define TYPE_OFF 2
    #define TYPE_CHAT 3
    #define SIZE 1024
    
    /* 将消息封装成结构体 */
    typedef struct msg_tag
    {
        int msg_type ;
        int msg_len;
        char msg_buf[SIZE] ;
    }MSG, *pMSG;
    
    int main(int argc, char* argv[])
    {
        if(argc != 2)
        {
            printf("USAGE: EXE CONF ! 
    ");
            exit(1);
        }
        /* 从配置文件中读取服务器的联系方式:IP及端口号 */
        FILE* fp_conf ;    
        char server_ip[32]="";
        int server_port ;
        fp_conf = fopen(argv[1], "r");
        if(fp_conf == NULL)
        {
            perror("fopen");
            exit(1);
        }
        fscanf(fp_conf,"%s%d",server_ip, &server_port);
        fclose(fp_conf);
    
        /* 创建客户端socket */
        int fd_client ;
        if((fd_client = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
        {
            perror("socket");
            exit(1);
        }
        /* 存入服务器联系方式 */
        SA server_addr ;
        memset(&server_addr, 0, sizeof(SA));
        server_addr.sin_family = AF_INET ;
        server_addr.sin_port = htons(server_port);
        server_addr.sin_addr.s_addr = inet_addr(server_ip);
        /* 通知服务器上线 */
        //MSG my_msg = {TYPE_ON,2,"on"} ;
        MSG my_msg = {TYPE_ON,0,""} ;
        sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));    
        /* 孙子进程用于接收服务器转发的消息,并显示在屏幕上 */
        /* 当儿子进程fork出孙子后,立马会退出,从而被父进程(主程序)wait掉。
         * 从而孙子成为孤儿进程,当其退出时资源会被init所回收。
         * 实际上fork出孙子有两点原因,如下:
         * 一是不愿意父进程wait子进程,因为wait是阻塞函数。
         * 二是如果不wait,在子进程先退的情况下父进程不能回收其资源,从而先退的子进程会成为僵尸进程。
         * 现在让儿子fork出孙子后立马滚蛋,这样孙子就直接变成孤儿进程了,由init收养,并在退出时由init回收资源。
         * 注意,父进程先滚蛋其实是无碍的,大不了init做儿子(孤儿进程)的爹,并由init来回收资源。但通常父进程会模拟服务器,不会退。*/
        if(fork() == 0)
        {
            if(fork() == 0)
            {
                
                char msg_buf[1024];
                // recvfrom是阻塞函数,孙子进程退出程序后,会被init回收。此处 
                while(memset(msg_buf, 0, 1024), recvfrom(fd_client, msg_buf, 1024, 0, NULL, NULL) > 0)
                {
                    write(1,msg_buf, strlen(msg_buf));
    
                }
                printf("child exit ! 
    ");
                close(fd_client);
                exit(0);
                /*  用于说明爷爷退了之后,孙子不会被一起带走。
                    sleep(5);
                    while(1)
                    {
                        printf("hahahahaha
    ");
                    }
                */
            }
            close(fd_client);
            exit(0);
        }
        wait(NULL);
        /* 从键盘输入消息,发送给服务器,按ctrl+D退出循环 */
        while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, SIZE, stdin) != NULL)
        {
            my_msg.msg_type = TYPE_CHAT ;
            my_msg.msg_len = strlen(my_msg.msg_buf);
            sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));    
        }
        /* 向服务器发送离线消息 */
        my_msg.msg_type = TYPE_OFF ;
        my_msg.msg_len = 0 ;
        sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));    
    
        close(fd_client);
    
        return 0 ;
        /*注意:只要主进程滚蛋了,马上会显示出shell界面。但是此时,fork出来的进程可能并没有结束。
         *fork出来的进程先滚蛋,是不会显示shell界面的。只有主进程滚蛋,才会显示出shell界面。     */
    }
  • 相关阅读:
    jQuery源码笔记——四
    jQuery源码笔记——三
    jQuery源码笔记——二
    深度理解作用域链和闭包
    事务的传播机制
    Jvm的运行时数据区
    SpringBoot 工程结构
    MyBatis的<if>标签判空
    Redis学习
    MyBatis调用Oracle的存储过程
  • 原文地址:https://www.cnblogs.com/hxjbc/p/3963984.html
Copyright © 2020-2023  润新知