zoukankan      html  css  js  c++  java
  • linux网络编程之socket编程(十四)

    经过令国鸡冻的APEC会之后,北京的冬天终于不冷了,有暖气的日子就是倍儿爽呀~~洗完热水澡,舒服的躺在床上欢乐地敲打着键盘,是件多么幸福的事呀,好了,抒发情感后,正题继续。

    上节中已经初步学习了UDP的编程,这次主要是进一步加深对UDP的认识,用它来实现一个简易的聊天室程序,下面首先来看一下该程序的总的逻辑架构图:

    下面来将其进行分解:

    以上就是聊天程序所涉及的一些消息交互的过程,在正式开始代码前,先来看一下该程序的最后效果,对其有一个更加直观的感觉:

    接下来再来登录一个用户,这时还是登录aa,用有什么效果呢?

    下面给用户发送消息:

    接下来客户端退出:

    以上就是聊天程序的效果,下面则正式进入代码的阶段,重在分析其流程:

    先看一下代码结构:

    其中在上面看到了很多状态消息,都利用宏定义在pub.h头文件中:

    接下来定义的一些消息结构,也定义在头文件中:

    【说明】:关于这里用到的c++知识可以完全理解既可,稍有一点上层编程语言的都很容易理解,例如java,实际上我也还没学过c++的内容,不过将来会扎实地学习它的,这里只是为了实验需要,重在实验的理解。

    下面贴出头文件的具体代码:

    pub.h:

    #ifndef _PUB_H_
    #define _PUB_H_
    
    #include <list>
    #include <algorithm>
    using namespace std;
    
    // C2S
    #define C2S_LOGIN            0x01
    #define C2S_LOGOUT            0x02
    #define C2S_ONLINE_USER        0x03
    
    #define MSG_LEN                512
    
    
    // S2C
    #define S2C_LOGIN_OK        0x01
    #define S2C_ALREADY_LOGINED    0x02
    #define S2C_SOMEONE_LOGIN    0x03
    #define S2C_SOMEONE_LOGOUT    0x04
    #define S2C_ONLINE_USER        0x05
    
    // C2C
    #define C2C_CHAT            0x06
    
    typedef struct message
    {
        int cmd;
        char body[MSG_LEN];
    } MESSAGE;
    
    typedef struct user_info
    {
        char username[16];
        unsigned int ip;
        unsigned short port;
    } USER_INFO;
    
    typedef struct chat_msg
    {
        char username[16];
        char msg[100];
    }CHAT_MSG;
    
    typedef list<USER_INFO> USER_LIST;
    
    #endif /* _PUB_H_ */

    接着开始分析下服务端的代码:

    其main函数代码就不做过多解释了,上节UDP编程中已经详细提到过,下面先贴出来:

    下面来具体分析该函数:

    对应于逻辑图:

    下面各个消息处理进行一一分解:

    登录do_login:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
    }

    接下来判断用户是否已经登录过:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
    
        }
    }

    如果是没有登录过,那就是登录成功了,接下来会进行一系列处理,由于便于理解流程,所以下面说明时会对照着客户端的代码:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);//将新的用户插入到集合中
    
            // 登录成功应答
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_LOGIN_OK);
            sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
    
        }
    }

    这时看一下客户端的代码,登录成功应答时客户端是怎么处理的:

    void chat_cli(int sock)
    {
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen;
    
        MESSAGE msg;
        while (1)
        {
            //输入用户名
            memset(username,0,sizeof(username));
            printf("please inpt your name:");
            fflush(stdout);
            scanf("%s", username);
    
            
            //准备向服务端发送登录请求
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_LOGIN);
            strcpy(msg.body, username);
    
            //发送登录请求给服务端
            sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
            memset(&msg, 0, sizeof(msg));
            //接收服务端的消息,其中就是登录请求的应答信息
            recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
            int cmd = ntohl(msg.cmd);
            if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
                printf("user %s already logined server, please use another username
    ", username);
            else if (cmd == S2C_LOGIN_OK)
            {//证明用户已经成功登录了
                printf("user %s has logined server
    ", username);
                break;
            }
    
        }
    }

    接着服务端向客户端发送在线人数及列表:

    chatsrv.cpp:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);//将新的用户插入到集合中
    
            // 登录成功应答
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_LOGIN_OK);
            sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            int count = htonl((int)client_list.size());
            // 发送在线人数
            sendto(sock, &count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            printf("sending user list information to: %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            // 发送在线列表
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                sendto(sock, &*it/* *it表示USER_INFO */, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
            }
    
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
        }
    }

    客户端收到在线列表的处理代码:

    chatcli.cpp:

    void chat_cli(int sock)
    {
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen;
    
        MESSAGE msg;
        while (1)
        {
            //输入用户名
            memset(username,0,sizeof(username));
            printf("please inpt your name:");
            fflush(stdout);
            scanf("%s", username);
    
            
            //准备向服务端发送登录请求
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_LOGIN);
            strcpy(msg.body, username);
    
            //发送登录请求给服务端
            sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
            memset(&msg, 0, sizeof(msg));
            //接收服务端的消息,其中就是登录请求的应答信息
            recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
            int cmd = ntohl(msg.cmd);
            if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
                printf("user %s already logined server, please use another username
    ", username);
            else if (cmd == S2C_LOGIN_OK)
            {//证明用户已经成功登录了
                printf("user %s has logined server
    ", username);
                break;
            }
    
        }
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    
        int n = ntohl(count);
        printf("has %d users logined server
    ", n);
    
    
        for (int i=0; i<n; i++)
        {
            USER_INFO user;
            recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);//每接收到一个用户,则插入到聊天成员列表中
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%d %s <-> %s:%d
    ", i, user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    
    }

    下面则向其它用户通知有新用户登录:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);//将新的用户插入到集合中
    
            // 登录成功应答
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_LOGIN_OK);
            sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            int count = htonl((int)client_list.size());
            // 发送在线人数
            sendto(sock, &count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            printf("sending user list information to: %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            // 发送在线列表
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                sendto(sock, &*it, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
            }
    
            // 向其他用户通知有新用户登录
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                if (strcmp(it->username,msg.body) == 0)
                    continue;
    
                struct sockaddr_in peeraddr;
                memset(&peeraddr, 0, sizeof(peeraddr));
                peeraddr.sin_family = AF_INET;
                peeraddr.sin_port = it->port;
                peeraddr.sin_addr.s_addr = it->ip;
    
                msg.cmd = htonl(S2C_SOMEONE_LOGIN);
                memcpy(msg.body, &user, sizeof(user));
    
                if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)
                    ERR_EXIT("sendto");
            }
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
        }
    }

    如果发现该用户已经登录了,则给出已登录的提示:

    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);//将新的用户插入到集合中
    
            // 登录成功应答
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_LOGIN_OK);
            sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            int count = htonl((int)client_list.size());
            // 发送在线人数
            sendto(sock, &count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            printf("sending user list information to: %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            // 发送在线列表
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                sendto(sock, &*it, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
            }
    
            // 向其他用户通知有新用户登录
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                if (strcmp(it->username,msg.body) == 0)
                    continue;
    
                struct sockaddr_in peeraddr;
                memset(&peeraddr, 0, sizeof(peeraddr));
                peeraddr.sin_family = AF_INET;
                peeraddr.sin_port = it->port;
                peeraddr.sin_addr.s_addr = it->ip;
    
                msg.cmd = htonl(S2C_SOMEONE_LOGIN);
                memcpy(msg.body, &user, sizeof(user));
    
                if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)
                    ERR_EXIT("sendto");
    
            }
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
    
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_ALREADY_LOGINED);
            sendto(sock, &reply_msg, sizeof(reply_msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
        }
    }

    接下来,回到客户端这边来,当登录成功之后,会列出该客户端能用到的命令:

    chatcli.cpp:

    void chat_cli(int sock)
    {
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen;
    
        MESSAGE msg;
        while (1)
        {
            //输入用户名
            memset(username,0,sizeof(username));
            printf("please inpt your name:");
            fflush(stdout);
            scanf("%s", username);
    
            
            //准备向服务端发送登录请求
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_LOGIN);
            strcpy(msg.body, username);
    
            //发送登录请求给服务端
            sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
            memset(&msg, 0, sizeof(msg));
            //接收服务端的消息,其中就是登录请求的应答信息
            recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
            int cmd = ntohl(msg.cmd);
            if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
                printf("user %s already logined server, please use another username
    ", username);
            else if (cmd == S2C_LOGIN_OK)
            {//证明用户已经成功登录了
                printf("user %s has logined server
    ", username);
                break;
            }
    
        }
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    
        int n = ntohl(count);
        printf("has %d users logined server
    ", n);
    
    
        for (int i=0; i<n; i++)
        {
            USER_INFO user;
            recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%d %s <-> %s:%d
    ", i, user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    
    
        printf("
    Commands are:
    ");
        printf("send username msg
    ");
        printf("list
    ");
        printf("exit
    ");
        printf("
    ");
    
    }

    接下来用I/O复用模型select函数,来并发处理I/O套接字,因为既有可能产生键盘套接字,也有sock,所以需要用I/O复用模型,如下:

    chatcli.cpp:

    void chat_cli(int sock)
    {
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen;
    
        MESSAGE msg;
        while (1)
        {
            //输入用户名
            memset(username,0,sizeof(username));
            printf("please inpt your name:");
            fflush(stdout);
            scanf("%s", username);
    
            
            //准备向服务端发送登录请求
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_LOGIN);
            strcpy(msg.body, username);
    
            //发送登录请求给服务端
            sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
            memset(&msg, 0, sizeof(msg));
            //接收服务端的消息,其中就是登录请求的应答信息
            recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
            int cmd = ntohl(msg.cmd);
            if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
                printf("user %s already logined server, please use another username
    ", username);
            else if (cmd == S2C_LOGIN_OK)
            {//证明用户已经成功登录了
                printf("user %s has logined server
    ", username);
                break;
            }
    
        }
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    
        int n = ntohl(count);
        printf("has %d users logined server
    ", n);
    
    
        for (int i=0; i<n; i++)
        {
            USER_INFO user;
            recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%d %s <-> %s:%d
    ", i, user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    
    
        printf("
    Commands are:
    ");
        printf("send username msg
    ");
        printf("list
    ");
        printf("exit
    ");
        printf("
    ");
    
        fd_set rset;
        FD_ZERO(&rset);
        int nready;
        while (1)
        {
            FD_SET(STDIN_FILENO, &rset);//将标准输入加入到集合中
            FD_SET(sock, &rset);//将sock套接字加入集合中
            nready = select(sock+1, &rset, NULL, NULL, NULL);
            if (nready == -1)
                    ERR_EXIT("select");
    
            if (nready == 0)
                    continue;
    
            if (FD_ISSET(sock, &rset))
            {
                peerlen = sizeof(peeraddr);
                memset(&msg,0,sizeof(msg));
                recvfrom(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, &peerlen);
                int cmd = ntohl(msg.cmd);
                //将服务端发过来的消息进行分发
                switch (cmd)
                {
                case S2C_SOMEONE_LOGIN:
                    do_someone_login(msg);
                    break;
                case S2C_SOMEONE_LOGOUT:
                    do_someone_logout(msg);
                    break;
                case S2C_ONLINE_USER:
                    do_getlist(sock);
                    break;
                case C2C_CHAT:
                    do_chat(msg);
                    break;
            default:
                break;
    
                }
            }
            if (FD_ISSET(STDIN_FILENO, &rset))
            {//标准输入产生了事件
                char cmdline[100] = {0};
                if (fgets(cmdline, sizeof(cmdline), stdin) == NULL)
                    break;
    
                if (cmdline[0] == '
    ')
                    continue;
                cmdline[strlen(cmdline) - 1] = '';
                //对用户敲的命令进行解析处理
                parse_cmd(cmdline, sock, &servaddr);
            }
        }
    }

    下面来看一下parse_cmd函数的实现:

     在看具体代码前,先看一下用户输入命令的几种情况:

    下面具体来看一下该命令解析函数的实现:

    首先从输入的字符中查找空格,并替换成'',如下:

    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');//检查空格
        if (p != NULL)
            *p = '';//将控格替换成
    
        strcpy(cmd, cmdline);
    }

    然后下面对其输入的命令进行判断:

    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');
        if (p != NULL)
            *p = '';
    
        strcpy(cmd, cmdline);
        
        if (strcmp(cmd, "exit") == 0)
        {//退出
            
    
        }
        else if (strcmp(cmd, "send") == 0)
        {//向用户发送消息
            
        }
        else if (strcmp(cmd, "list") == 0)
        {//列出在线用户列表
            
        }
        else
        {//说明输入命令有误,给出正确命令提示
            printf("bad command
    ");
            printf("
    Commands are:
    ");
            printf("send username msg
    ");
            printf("list
    ");
            printf("exit
    ");
            printf("
    ");
        }
    }

    当用户敲入了"exit"命令时,会执行下面这段逻辑:

    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');
        if (p != NULL)
            *p = '';
    
        strcpy(cmd, cmdline);
        
        if (strcmp(cmd, "exit") == 0)
        {//退出
            MESSAGE msg;
            memset(&msg,0,sizeof(msg));
            msg.cmd = htonl(C2S_LOGOUT);//向服务器发送C2S_LOGOUT消息
            strcpy(msg.body, username);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
    
            printf("user %s has logout server
    ", username);
            exit(EXIT_SUCCESS);
    
        }
        else if (strcmp(cmd, "send") == 0)
        {//向用户发送消息
            
        }
        else if (strcmp(cmd, "list") == 0)
        {//列出在线用户列表
            
        }
        else
        {//说明输入命令有误,给出正确命令提示
            printf("bad command
    ");
            printf("
    Commands are:
    ");
            printf("send username msg
    ");
            printf("list
    ");
            printf("exit
    ");
            printf("
    ");
        }
    }

    当用户向其它用户发送聊天信息的话:

    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');
        if (p != NULL)
            *p = '';
    
        strcpy(cmd, cmdline);
        
        if (strcmp(cmd, "exit") == 0)
        {//退出
            MESSAGE msg;
            memset(&msg,0,sizeof(msg));
            msg.cmd = htonl(C2S_LOGOUT);
            strcpy(msg.body, username);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
    
            printf("user %s has logout server
    ", username);
            exit(EXIT_SUCCESS);
    
        }
        else if (strcmp(cmd, "send") == 0)
        {//向用户发送消息
            char peername[16]={0};//要发送的用户名
            char msg[MSG_LEN]={0};//要发送的消息
    
            //下面则开始解析命令
            /* send  user  msg  */
            /*       p     p2   */
            while (*p++ == ' ') ;
            char *p2;
            p2 = strchr(p, ' ');
            if (p2 == NULL)
            {
                printf("bad command
    ");
                printf("
    Commands are:
    ");
                printf("send username msg
    ");
                printf("list
    ");
                printf("exit
    ");
                printf("
    ");
                return;
            }
            *p2 = '';
            strcpy(peername, p);
    
            while (*p2++ == ' ') ;
            strcpy(msg, p2);
            //然后将消息发送给对方,这里封装了一个方法
            sendmsgto(sock, peername, msg);
        }
        else if (strcmp(cmd, "list") == 0)
        {//列出在线用户列表
        }
        else
        {//说明输入命令有误,给出正确命令提示
            printf("bad command
    ");
            printf("
    Commands are:
    ");
            printf("send username msg
    ");
            printf("list
    ");
            printf("exit
    ");
            printf("
    ");
        }
    }

    下面来看一下sendmsgto方法的具体实现:

    bool sendmsgto(int sock, char* name, char* msg)
    {
        if (strcmp(name, username) == 0)
        {//如果向当前用户发送消息,给出错误提示
            printf("can't send message to self
    ");
            return false;
        }
    
        return true;
    }
    bool sendmsgto(int sock, char* name, char* msg)
    {
        if (strcmp(name, username) == 0)
        {//如果向当前用户发送消息,给出错误提示
            printf("can't send message to self
    ");
            return false;
        }
    
        //下面开始遍历要发送的用户是否已经登录
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,name) == 0)
                break;
        }
    
        if (it == client_list.end())
        {//说明要发送的用户还没有登录,给出错误提示
            printf("user %s has not logined server
    ", name);
            return false;
        }
        return true;
    }
    bool sendmsgto(int sock, char* name, char* msg)
    {
        if (strcmp(name, username) == 0)
        {//如果向当前用户发送消息,给出错误提示
            printf("can't send message to self
    ");
            return false;
        }
    
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,name) == 0)
                break;
        }
    
        if (it == client_list.end())
        {
            printf("user %s has not logined server
    ", name);
            return false;
        }
    
        //流程走到这,证明要发送的用户是已经成功登录过的,所以接下来是组拼消息
        MESSAGE m;
        memset(&m,0,sizeof(m));
        m.cmd = htonl(C2C_CHAT);//向服务器发送C2C_CHAT命令
    
        CHAT_MSG cm;
        strcpy(cm.username, username);
        strcpy(cm.msg, msg);
    
        memcpy(m.body, &cm, sizeof(cm));
        //strcpy(m.body,msg);
    
        struct sockaddr_in    peeraddr;
        memset(&peeraddr,0,sizeof(peeraddr));
        peeraddr.sin_family      = AF_INET;
        peeraddr.sin_addr.s_addr = it->ip;
        peeraddr.sin_port        = it->port;
    
        in_addr tmp;
        tmp.s_addr = it->ip;
    
        printf("sending message [%s] to user [%s] <-> %s:%d
    ",  msg, name, inet_ntoa(tmp), ntohs(it->port));
    
        //发送消息
        sendto(sock, (const char*)&m, sizeof(m), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr));
        return true;
    }

    如果是输入的在线用户列表的命令,则会走如下逻辑:

    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');
        if (p != NULL)
            *p = '';
    
        strcpy(cmd, cmdline);
        
        if (strcmp(cmd, "exit") == 0)
        {//退出
            MESSAGE msg;
            memset(&msg,0,sizeof(msg));
            msg.cmd = htonl(C2S_LOGOUT);
            strcpy(msg.body, username);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
    
            printf("user %s has logout server
    ", username);
            exit(EXIT_SUCCESS);
    
        }
        else if (strcmp(cmd, "send") == 0)
        {//向用户发送消息
            char peername[16]={0};
            char msg[MSG_LEN]={0};
    
            /* send  user  msg  */
            /*       p     p2   */
            while (*p++ == ' ') ;
            char *p2;
            p2 = strchr(p, ' ');
            if (p2 == NULL)
            {
                printf("bad command
    ");
                printf("
    Commands are:
    ");
                printf("send username msg
    ");
                printf("list
    ");
                printf("exit
    ");
                printf("
    ");
                return;
            }
            *p2 = '';
            strcpy(peername, p);
    
            while (*p2++ == ' ') ;
            strcpy(msg, p2);
            sendmsgto(sock, peername, msg);
        }
        else if (strcmp(cmd, "list") == 0)
        {//列出在线用户列表
            MESSAGE msg;
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_ONLINE_USER);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
        }
        else
        {//说明输入命令有误,给出正确命令提示
            printf("bad command
    ");
            printf("
    Commands are:
    ");
            printf("send username msg
    ");
            printf("list
    ");
            printf("exit
    ");
            printf("
    ");
        }
    }

    至此parse_cmd函数的实现就分析到这,客户端还有可能会收到服务端发来的网络的消息,所以下面来看一下这些消息的分发:

    下面来具体分析一下实现:

    当有用户登录了,do_someone_login函数实现如下:

    当有用户登出了,do_someone_logout函数实现如下:

    当用户要获取当前在线用户列表时,do_getlist函数实现如下:

    void do_getlist(int sock)
    {
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);//首先得到用户列表的总个数
        printf("has %d users logined server
    ", ntohl(count));
        client_list.clear();//将当前的在线列表清空
    
        int n = ntohl(count);
        for (int i=0; i<n; i++)//然后再一个个接收用户,插入到在线列表集合中
        {
            USER_INFO user;
            recvfrom(sock,&user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%s <-> %s:%d
    ", user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    }

    当要发送消息时,do_chat函数消息实现如下:

    void do_chat(const MESSAGE& msg)
    {
        CHAT_MSG *cm = (CHAT_MSG*)msg.body;
        printf("recv a msg [%s] from [%s]
    ", cm->msg, cm->username);
        //recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    }

    好了,关于聊天程序的大体流程已经分析完毕,具体的一些细节可以细看代码,下面将完整代码贴出来:

    chatsrv.cpp:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <sys/wait.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #include "pub.h"
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    // 聊天室成员列表
    USER_LIST client_list;
    
    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr);
    void do_logout(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr);
    void do_sendlist(int sock, struct sockaddr_in *cliaddr);
    
    void chat_srv(int sock)
    {
        struct sockaddr_in cliaddr;
        socklen_t clilen;
        int n;
        MESSAGE msg;
        while (1)
        {
            memset(&msg, 0, sizeof(msg));
            clilen = sizeof(cliaddr);
            n = recvfrom(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&cliaddr, &clilen);
            if (n < 0)
            {
                if (errno == EINTR)
                    continue;
                ERR_EXIT("recvfrom");
            }
    
            int cmd = ntohl(msg.cmd);
            switch (cmd)
            {
            case C2S_LOGIN:
                do_login(msg, sock, &cliaddr);
                break;
            case C2S_LOGOUT:
                do_logout(msg, sock, &cliaddr);
                break;
            case C2S_ONLINE_USER:
                do_sendlist(sock, &cliaddr);
                break;
            default:
                break;
            }
        }
    }
    
    void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        //从客户端信息中来初使化user结构体
        USER_INFO user;
        strcpy(user.username, msg.body);
        user.ip = cliaddr->sin_addr.s_addr;
        user.port = cliaddr->sin_port;
        
        /* 查找用户 */
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
            {
                break;
            }
        }
    
        if (it == client_list.end())    /* 没找到用户 */
        {
            printf("has a user login : %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            client_list.push_back(user);//将新的用户插入到集合中
    
            // 登录成功应答
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_LOGIN_OK);
            sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            int count = htonl((int)client_list.size());
            // 发送在线人数
            sendto(sock, &count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
            printf("sending user list information to: %s <-> %s:%d
    ", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
            // 发送在线列表
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                sendto(sock, &*it, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
            }
    
            // 向其他用户通知有新用户登录
            for (it=client_list.begin(); it != client_list.end(); ++it)
            {
                if (strcmp(it->username,msg.body) == 0)
                    continue;
    
                struct sockaddr_in peeraddr;
                memset(&peeraddr, 0, sizeof(peeraddr));
                peeraddr.sin_family = AF_INET;
                peeraddr.sin_port = it->port;
                peeraddr.sin_addr.s_addr = it->ip;
    
                msg.cmd = htonl(S2C_SOMEONE_LOGIN);
                memcpy(msg.body, &user, sizeof(user));
    
                if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)
                    ERR_EXIT("sendto");
    
            }
        }
        else    /* 找到用户 */
        {
            printf("user %s has already logined
    ", msg.body);
    
            MESSAGE reply_msg;
            memset(&reply_msg, 0, sizeof(reply_msg));
            reply_msg.cmd = htonl(S2C_ALREADY_LOGINED);
            sendto(sock, &reply_msg, sizeof(reply_msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
        }
    }
    
    void do_logout(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
    {
        printf("has a user logout : %s <-> %s:%d
    ",  msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
    
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
                break;
        }
    
        if (it != client_list.end())
            client_list.erase(it);
    
        // 向其他用户通知有用户登出
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
                continue;
    
            struct sockaddr_in peeraddr;
            memset(&peeraddr, 0, sizeof(peeraddr));
            peeraddr.sin_family = AF_INET;
            peeraddr.sin_port = it->port;
            peeraddr.sin_addr.s_addr = it->ip;
    
            msg.cmd = htonl(S2C_SOMEONE_LOGOUT);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)
                ERR_EXIT("sendto");
    
        }
    }
    
    void do_sendlist(int sock, struct sockaddr_in *cliaddr)
    {
        MESSAGE msg;
        msg.cmd = htonl(S2C_ONLINE_USER);
        sendto(sock, (const char*)&msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
    
        int count = htonl((int)client_list.size());
        /* 发送在线用户数 */
        sendto(sock, (const char*)&count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
        /* 发送在线用户列表 */
        for (USER_LIST::iterator it=client_list.begin(); it != client_list.end(); ++it)
        {
            sendto(sock, &*it, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
        }
    }
    
    int main(void)
    {
        int sock;
        if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
            ERR_EXIT("socket");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
            ERR_EXIT("bind");
    
        chat_srv(sock);
        return 0;
    }

    chatcli.cpp:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #include "pub.h"
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    // 当前用户名
    char username[16];
    
    // 聊天室成员列表
    USER_LIST client_list;
    
    
    void do_someone_login(MESSAGE& msg);
    void do_someone_logout(MESSAGE& msg);
    void do_getlist();
    void do_chat();
    
    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr);
    bool sendmsgto(int sock, char* username, char* msg);
    
    void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
    {
        char cmd[10]={0};
        char *p;
        p = strchr(cmdline, ' ');
        if (p != NULL)
            *p = '';
    
        strcpy(cmd, cmdline);
        
        if (strcmp(cmd, "exit") == 0)
        {//退出
            MESSAGE msg;
            memset(&msg,0,sizeof(msg));
            msg.cmd = htonl(C2S_LOGOUT);
            strcpy(msg.body, username);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
    
            printf("user %s has logout server
    ", username);
            exit(EXIT_SUCCESS);
    
        }
        else if (strcmp(cmd, "send") == 0)
        {//向用户发送消息
            char peername[16]={0};
            char msg[MSG_LEN]={0};
    
            /* send  user  msg  */
            /*       p     p2   */
            while (*p++ == ' ') ;
            char *p2;
            p2 = strchr(p, ' ');
            if (p2 == NULL)
            {
                printf("bad command
    ");
                printf("
    Commands are:
    ");
                printf("send username msg
    ");
                printf("list
    ");
                printf("exit
    ");
                printf("
    ");
                return;
            }
            *p2 = '';
            strcpy(peername, p);
    
            while (*p2++ == ' ') ;
            strcpy(msg, p2);
            sendmsgto(sock, peername, msg);
        }
        else if (strcmp(cmd, "list") == 0)
        {//列出在线用户列表
            MESSAGE msg;
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_ONLINE_USER);
    
            if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
                ERR_EXIT("sendto");
        }
        else
        {//说明输入命令有误,给出正确命令提示
            printf("bad command
    ");
            printf("
    Commands are:
    ");
            printf("send username msg
    ");
            printf("list
    ");
            printf("exit
    ");
            printf("
    ");
        }
    }
    
    bool sendmsgto(int sock, char* name, char* msg)
    {
        if (strcmp(name, username) == 0)
        {
            printf("can't send message to self
    ");
            return false;
        }
    
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,name) == 0)
                break;
        }
    
        if (it == client_list.end())
        {
            printf("user %s has not logined server
    ", name);
            return false;
        }
    
        MESSAGE m;
        memset(&m,0,sizeof(m));
        m.cmd = htonl(C2C_CHAT);
    
        CHAT_MSG cm;
        strcpy(cm.username, username);
        strcpy(cm.msg, msg);
    
        memcpy(m.body, &cm, sizeof(cm));
        //strcpy(m.body,msg);
    
        struct sockaddr_in    peeraddr;
        memset(&peeraddr,0,sizeof(peeraddr));
        peeraddr.sin_family      = AF_INET;
        peeraddr.sin_addr.s_addr = it->ip;
        peeraddr.sin_port        = it->port;
    
        in_addr tmp;
        tmp.s_addr = it->ip;
    
        printf("sending message [%s] to user [%s] <-> %s:%d
    ",  msg, name, inet_ntoa(tmp), ntohs(it->port));
    
        sendto(sock, (const char*)&m, sizeof(m), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr));
        return true;
    }
    
    void do_getlist(int sock)
    {
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);//首先得到用户列表的总个数
        printf("has %d users logined server
    ", ntohl(count));
        client_list.clear();//将当前的在线列表清空
    
        int n = ntohl(count);
        for (int i=0; i<n; i++)//然后再一个个接收用户,插入到在线列表集合中
        {
            USER_INFO user;
            recvfrom(sock,&user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%s <-> %s:%d
    ", user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    }
    
    void do_someone_login(MESSAGE& msg)
    {
        USER_INFO *user = (USER_INFO*)msg.body;
        in_addr tmp;
        tmp.s_addr = user->ip;
        printf("%s <-> %s:%d has logined server
    ", user->username, inet_ntoa(tmp), ntohs(user->port));
        client_list.push_back(*user);
    }
    
    void do_someone_logout(MESSAGE& msg)
    {
        USER_LIST::iterator it;
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            if (strcmp(it->username,msg.body) == 0)
                break;
        }
    
        if (it != client_list.end())
            client_list.erase(it);
    
        printf("user %s has logout server
    ", msg.body);
    }
    
    void do_chat(const MESSAGE& msg)
    {
        CHAT_MSG *cm = (CHAT_MSG*)msg.body;
        printf("recv a msg [%s] from [%s]
    ", cm->msg, cm->username);
        //recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    }
    
    void chat_cli(int sock)
    {
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen;
    
        MESSAGE msg;
        while (1)
        {
            //输入用户名
            memset(username,0,sizeof(username));
            printf("please inpt your name:");
            fflush(stdout);
            scanf("%s", username);
    
            
            //准备向服务端发送登录请求
            memset(&msg, 0, sizeof(msg));
            msg.cmd = htonl(C2S_LOGIN);
            strcpy(msg.body, username);
    
            //发送登录请求给服务端
            sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
            memset(&msg, 0, sizeof(msg));
            //接收服务端的消息,其中就是登录请求的应答信息
            recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
            int cmd = ntohl(msg.cmd);
            if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
                printf("user %s already logined server, please use another username
    ", username);
            else if (cmd == S2C_LOGIN_OK)
            {//证明用户已经成功登录了
                printf("user %s has logined server
    ", username);
                break;
            }
    
        }
        int count;
        recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
    
        int n = ntohl(count);
        printf("has %d users logined server
    ", n);
    
    
        for (int i=0; i<n; i++)
        {
            USER_INFO user;
            recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
            client_list.push_back(user);
            in_addr tmp;
            tmp.s_addr = user.ip;
    
            printf("%d %s <-> %s:%d
    ", i, user.username, inet_ntoa(tmp), ntohs(user.port));
        }
    
    
        printf("
    Commands are:
    ");
        printf("send username msg
    ");
        printf("list
    ");
        printf("exit
    ");
        printf("
    ");
    
        fd_set rset;
        FD_ZERO(&rset);
        int nready;
        while (1)
        {
            FD_SET(STDIN_FILENO, &rset);//将标准输入加入到集合中
            FD_SET(sock, &rset);//将sock套接字加入集合中
            nready = select(sock+1, &rset, NULL, NULL, NULL);
            if (nready == -1)
                    ERR_EXIT("select");
    
            if (nready == 0)
                    continue;
    
            if (FD_ISSET(sock, &rset))
            {
                peerlen = sizeof(peeraddr);
                memset(&msg,0,sizeof(msg));
                recvfrom(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, &peerlen);
                int cmd = ntohl(msg.cmd);
                //将服务端发过来的消息进行分发
                switch (cmd)
                {
                case S2C_SOMEONE_LOGIN:
                    do_someone_login(msg);
                    break;
                case S2C_SOMEONE_LOGOUT:
                    do_someone_logout(msg);
                    break;
                case S2C_ONLINE_USER:
                    do_getlist(sock);
                    break;
                case C2C_CHAT:
                    do_chat(msg);
                    break;
            default:
                break;
    
                }
            }
            if (FD_ISSET(STDIN_FILENO, &rset))
            {//标准输入产生了事件
                char cmdline[100] = {0};
                if (fgets(cmdline, sizeof(cmdline), stdin) == NULL)
                    break;
    
                if (cmdline[0] == '
    ')
                    continue;
                cmdline[strlen(cmdline) - 1] = '';
                //对用户敲的命令进行解析处理
                parse_cmd(cmdline, sock, &servaddr);
            }
        }
    }
    
    
    int main(void)
    {
        int sock;
        if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
            ERR_EXIT("socket");
    
        chat_cli(sock);
    
        return 0;
    }

    其中用到的头文件在文章开头已经有了,这次的学习到这,主要是重在体会代码的流程。

  • 相关阅读:
    Eclipse快捷键大全
    Quartz任务调度快速入门
    Spring整合logback日志
    Java实现二维码的生成与解析
    跨域问题及解决方案
    SpringBoot项目直接在linux下运行
    SpringBoot拦截器中使用RedisTemplate
    Codeforces Round #345 (Div. 1) C. Table Compression dp+并查集
    HDU 4489 The King’s Ups and Downs dp
    HDU 4747 Mex 递推/线段树
  • 原文地址:https://www.cnblogs.com/webor2006/p/4104383.html
Copyright © 2011-2022 走看看