• 嵌入式养成计划-31-网络编程----TCP的并发服务器模型------IO模型--IO多路复用


    六十七、 TCP的并发服务器模型

    67.1 循环服务器模型

    • 一次只能处理一个客户端,当上一个客户端退出后,才能处理下一个客户端
    • 缺点:无法同时处理多个客户端

    代码模型

    sfd = socket();
    bind();
    listen();
    while(1){
        newfd = accept();
        while(1){
            recv();
            send();    
        }
        close(newfd);
    }
    close(sfd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    67.2 并发服务器模型

    • 目的:可以同时处理多个客户端的请求。
    • 实现:创建多进程或者创建多线程实现
      • 父进程 / 主线程 只负责连接(accept)
      • 子进程 / 分支线程只负责与客户端交互(recv / send);

    67.2.1 多进程并发服务器

    67.2.1.1 代码模型

    void handler(int sig){
        while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    
    signal(17, handler);
    sfd = socket();
    bind();
    listen();
    while(1){
        newfd = accept();
        cpid = fork();
        if(0 == cpid){
            close(sfd);
            while(1){
                recv();
                send();        
            }
            close(newfd);
            exit(0);         //退出子进程    
        }
        close(newfd);
    }
    close(sfd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    67.2.1.2 代码示例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)
    
    #define PORT 8888               //端口号的网络字节序,1024~49151
    #define IP "192.168.125.55"     //本机IP,ifconfig
    
    
    int deal_cli_msg(int newfd, struct sockaddr_in cin);
    
    void handler(int sig)
    {
        //循环回收僵尸进程
        //有子进程,没有僵尸进程  == 0
        //没有子进程,也没有僵尸进程 ==-1、
        while(waitpid(-1, NULL, WNOHANG) > 0); 
        return ;
    }
    
    int main(int argc, const char *argv[])
    {
        //捕获17号 SIGCHLD信号
        if(signal(SIGCHLD, handler) == SIG_ERR)
        {   
            ERR_MSG("signal");
            return -1; 
        }   
    
        //创建流式套接字
        int sfd = socket(AF_INET, SOCK_STREAM, 0); 
        if(sfd < 0)
        {   
            ERR_MSG("socket");
            return -1; 
        }   
        printf("socket create success  sfd=%d\n", sfd);
    
        //允许端口快速复用
        int reuse = 1;
        if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
        {   
            ERR_MSG("setsockopt");
            return -1; 
        }   
        printf("允许端口快速复用成功\n");
    
    
        //填充地址信息结构体给bind函数绑定使用;
        //真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;          //必须填AF_INET;
        sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151
        sin.sin_addr.s_addr = inet_addr(IP);    //本机IP的网络字节序,ifconfig
    
        //绑定服务器的地址信息---》必须绑定
        if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
        {   
            ERR_MSG("bind");
            return -1; 
        }   
        printf("bind success\n");
    
        //将套接字转换成被动监听状态
        if(listen(sfd, 128) < 0)
        {   
            ERR_MSG("listen");                                                                                    
            return -1; 
        }   
        printf("listen success\n");
    
    
        struct sockaddr_in cin;     //存储客户端的地址信息
        socklen_t addrlen = sizeof(cin);
        int newfd = -1; 
        pid_t cpid = -1; 
    
        while(1)
        {   
            //父进程只负责连接
            newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
            if(newfd < 0)
            {
                ERR_MSG("accept");
                return -1; 
            }
            printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
    
            //能运行到当前位置,则代表有客户端连接成功,
            //此时需要创建一个子进程,专门用于与客户端交互
            cpid = fork();
            if(0 == cpid)
            {
                close(sfd);     //子进程只负责交互,sfd没有用
    
                deal_cli_msg(newfd, cin);
    
                close(newfd);
                exit(0);        //退出子进程,子进程只负责交互,不允许回到accept函数。
            }
            else if(cpid < 0)
            {
                ERR_MSG("fork");;
                return -1; 
            }
    
            close(newfd);       //在父进程中newfd没有用
        }   
    
    
        //关闭套接字
        close(sfd);
        return 0;
    }
    
    
    //子进程负责与客户端交互的函数
    int deal_cli_msg(int newfd, struct sockaddr_in cin)
    {
        char buf[128] = ""; 
        ssize_t res = 0;
        while(1)
        {   
            bzero(buf, sizeof(buf));
            //接收数据
            res = recv(newfd, buf, sizeof(buf), 0); 
            if(res < 0)
            {
                ERR_MSG("recv");
                return -1; 
            }
            else if(0 == res)
            {
                printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \
                        inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
                break;
            }
    
            printf("[%s:%d] newfd=%d : %s __%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);
    
            if(strcmp(buf, "quit") == 0)
                break;
    
    
            //发送数据
            strcat(buf, "*_*");     //数据可以选择冲终端获取
            if(send(newfd, buf, sizeof(buf), 0) < 0)
            {
                ERR_MSG("send");
                return -1; 
            }
            printf("send success\n");
    
        }   
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    67.2.2 多进程并发服务器

    67.2.2.1 代码模型

    sfd = socket();
    bind();
    listen();
    while(1){
        newfd = accept();
        pthread_create( , , deal_cli_msg, );
        pthread_detach(tid);
    }
    close(sfd);
    
    void* deal_cli_msg(void* arg){
        while(1){
            recv();
            send();    
        }
        close(newfd);
        pthread_exit();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    67.2.2.1 代码示例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
                                                                                             
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)
    
    #define PORT 8888               //端口号的网络字节序,1024~49151
    #define IP "192.168.125.55"     //本机IP,ifconfig
    
    //传递给线程执行体的数据封装成结构体
    struct Climsg
    {
        int newfd;
        struct sockaddr_in cin;
    };
    
    void* deal_cli_msg(void* arg) ;     //void* arg = &info
    
    int main(int argc, const char *argv[])
    {
        //创建流式套接字
        int sfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sfd < 0)
        {
            ERR_MSG("socket");
            return -1;
        }
        printf("socket create success  sfd=%d\n", sfd);
    
        //允许端口快速复用
        int reuse = 1;
        if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
        {
            ERR_MSG("setsockopt");
            return -1;
        }
        printf("允许端口快速复用成功\n");
    
    
        //填充地址信息结构体给bind函数绑定使用;
        //真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;          //必须填AF_INET;
        sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151
        sin.sin_addr.s_addr = inet_addr(IP);    //本机IP的网络字节序,ifconfig
    
        //绑定服务器的地址信息---》必须绑定
        if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
        {
            ERR_MSG("bind");
            return -1;
        }
        printf("bind success\n");
    
        //将套接字转换成被动监听状态
        if(listen(sfd, 128) < 0)
        {
            ERR_MSG("listen");
            return -1;
        }
        printf("listen success\n");
    
    
        struct sockaddr_in cin;     //存储客户端的地址信息
        socklen_t addrlen = sizeof(cin);
        int newfd = -1;
        pthread_t tid;      //存储线程tid号
        struct Climsg info;
        while(1)
        {
            //代码先阻塞在accept函数,再断开连接关闭的文件描述符时
            //accept函数每次在阻塞的时候,会先预选一个没有被占用的文件描述符
            //当解除阻塞的时候,若预选的文件描述符没有被占用,则直接返回预选的文件描述符
            //若预选的文件描述符被占用,则会重新遍历一个没有被使用的文件名描述符返回
    
            //主线程只负责连接(accept)
            newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
            if(newfd < 0)
            {
                ERR_MSG("accept");
                return -1;
            }
            printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
    
            info.newfd = newfd;
            info.cin = cin;
    
            //能运行到当前位置,则代表有客户端连接成功,
            //此时需要创建一个分支线程,专门用于处理客户端的交互
            if(pthread_create(&tid, NULL, deal_cli_msg, (void*)&info) != 0)
            {
                fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
                return -1;
            }
    
            pthread_detach(tid);    //分离线程
        }
    
    
        //关闭套接字
        close(sfd);
        return 0;
    }
    
    
    //线程执行体 ---> 分支线程只负责交互
    void* deal_cli_msg(void* arg)       //void* arg = &info
    {
        //newfd和cin必须另存,每个客户端都有自己独立的通信文件描述符和地址信息。
        //如果使用全局变量,或者指针方式间接访问,会导致所有线程共用一份newfd和cin,
        //那么newfd和cin会被覆盖
        int newfd = ((struct Climsg*)arg)->newfd;
        struct sockaddr_in cin = ((struct Climsg*)arg)->cin;
    
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            //接收数据
            res = recv(newfd, buf, sizeof(buf), 0);
            if(res < 0)
            {
                ERR_MSG("recv");
                break;
            }
            else if(0 == res)
            {
                printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \
                        inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
                break;
            }
    
            printf("[%s:%d] newfd=%d : %s __%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);
    
            if(strcmp(buf, "quit") == 0)
                break;
    	
            //发送数据
            strcat(buf, "*_*");     //数据可以选择冲终端获取
            if(send(newfd, buf, sizeof(buf), 0) < 0)
            {
                ERR_MSG("send");
                break;
            }
            printf("send success\n");
    
        }
        close(newfd);
        pthread_exit(NULL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    六十八、IO模型

    68.1 阻塞IO

    1. 最常用,最简单,效率最低的。
    2. 创建套接字文件描述符后,默认处于阻塞IO模式;
    3. read, write, recv, send, recvfrom ,sendto,accept

    68.2 非阻塞IO

    1. .防止进程阻塞在IO函数上,当一个程序使用了非阻塞IO模式的套接字,那么它需要使用一个循环来不停的判断该文件描述符是否有数据可读,称之为polling;
    2. 应用程序不停的polling内核监测IO事件是否产生,cpu消耗率高;
    3. IO中导致函数阻塞的原因是因为文件描述符有阻塞属性。
    read阻塞:
    	文件描述符有阻塞属性:0号文件描述符有阻塞属性 + 读属性---0号文件描述符加上非阻塞属性
    
    • 1
    • 2
    • 修改 IO 为非阻塞方式
    1. 先获取0号文件描述符原有属性
    2. 在原有属性的基础上将阻塞  设置为  非阻塞
    3. 将修改后的属性重新设置回0号文件描述符中
    
    • 1
    • 2
    • 3

    fcntl函数

    功能:
    	获取/设置文件描述属性;
    原型:
           #include 
           #include 
    
           int fcntl(int fd, int cmd, ... /* arg */ );
    参数:
        int fd:指定要设置或者获取属性的文件描述符
        int cmd:
            F_GETFL (void):获取属性,第三个参数不用填,获取到的属性在返回值返回;
            F_SETFL (int):设置属性,第三个参数是int类型;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    68.3 信号驱动IO

    1. 异步通信方式;
    2. 信号驱动IO是指预先告诉内核,使得某个文件描述符发生IO事件的时候,内核会通知相关进程
      SIGIO;
    3. 对于TCP而言,信号驱动IO对TCP没有用。因为信号产生过于频繁,而且不能区分是哪个文件描述符发生的。

    68.4 IO多路复用(重点!!!)

    1. 进程中如果同时需要处理多路输入输出流,
    2. 在无法用多进程多线程,可以选择用IO多路复用;
    3. 由于不需要创建新的进程和线程,减少系统的资源开销,减少上下文切换的次数。
      1. 上下文:运行一个进程所需要的所有资源
      2. 上下文切换:从A进程切换到B进程,A进程的资源要完全替换成B进程的,是一个耗时操作。
    4. 增加并发量的时候可以使用IO多路复用
    5. 允许同时对多个IO进行操作,内核一旦发现进程执行一个或多个IO事件,会通知该进程。
      在这里插入图片描述

    68.4.1 select

    68.4.1.1 select函数

    功能:
    	阻塞函数,让内核监测集合中是否有文件描述符准备就绪,若准备就绪则解除阻塞;
    	当函数解除阻塞后,集合中会只剩下产生事件的文件描述符;例如:
    	0号准备就绪,则集合中只剩下0号
    	sfd准备就绪,则集合中只能下sfd;
    	0和sfd均准备就绪,则0和sfd均存在
         
    	若不将数据从触发事件的文件描述符对应的空间中取出,此时该文件描述符一直处于就绪状态。
    原型:
           #include 
    
           #include 
           #include 
           #include 
    
           int select(int nfds, fd_set *readfds, fd_set *writefds,
                      fd_set *exceptfds, struct timeval *timeout);
    参数:
        int nfds:需要填充三个集合中最大的文件描述符编号+1;
        fd_set *readfds, fd_set *writefds,
    	fd_set *exceptfds:读集合,写,其他集合,
    			若集合不使用,填NULL; 一般只用读集合;
        struct timeval *timeout:设置超时时间;   
    			1. 若不想设置超时时间,填NULL,则当前函数会一直阻塞,直到集合中有文件描述符准备就绪。
    			2. 设置超时时间; 若时间到后依然没有事件产生,则该函数解除阻塞,且返回失败情况。
    				struct timeval {
    				    long    tv_sec;         /* seconds */
    				    long    tv_usec;        /* microseconds */
    				};
    返回值:
        >0, 成功返回成功触发事件的文件描述符个数;
        =0, 超时了;
        失败,返回-1,更新errno;
      
    操作集合的函数   
           void FD_CLR(int fd, fd_set *set);    //将fd从集合中删除
           int  FD_ISSET(int fd, fd_set *set);  //判断fd是否在集合中
           void FD_SET(int fd, fd_set *set);    //将fd添加到结合中
           void FD_ZERO(fd_set *set);           //清空集合
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    68.4.1.2 select的TCP模型

    sfd = socket();
    bind();
    listen();
    while(1){
        tempfds = readfds;
        select(maxfd+1, &tempfds, NULL, NULL, NULL);
        for(int i=0; i<=maxfd; i++){
            if(FD_ISSET(i, &tempfds) == 0) continue;
            if(0 == i){
                fgets();        
            }
            else if(sfd == i){
                newfd = accept()
                FD_SET(newfd, &readfds);
                maxfd = maxfd>newfd?maxfd:newfd;        
            }    
            else{
                    res = recv(i, );
                    if(0 == res){
                        close(i);    
                        FD_CLR(i, &readfds);
                        while(!FD_ISSET(maxfd, &readfds) && maxfd-->=0);            
                    }
                    send();                 
            }
        }
    }
    close(sfd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    68.4.1.3 select的TCP服务器代码

    68.4.1.4 select的TCP客户端代码

    这俩的代码都在这个链接里面,再放进来就太长了,写文档都在卡

    IO多路复用实现TCP客户端与TCP并发服务器

    68.4.2 poll

    68.4.2.1 poll函数

    功能:
    	阻塞函数,阻塞等待集合中有文件描述符准备就绪,若准备就绪,则立即解除阻塞。
    原型:
           #include 
    
           int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    参数:
        struct pollfd *fds:指定要监测的集合
    		struct pollfd {
    		    int   fd;         /* file descriptor */     指定要监测的文件描述符
    		    short events;     /* requested events */    指定要监测的事件
    		    short revents;    /* returned events */     实际产生的事件
    		};
            事件:
                POLLIN     这里有数据可读
                POLLOUT    可写
                POLLERR    错误事件,只有在revents中有效
                
        nfds_t nfds:指定要监测的文件描述符的个数;
        int timeout:超时时间,以ms为单位
                    >0, 设置超时时间,以ms为单位;
                    =0, 不阻塞,即使没有文件描述符准备就绪,该函数不阻塞;
                    <0, 不设置超时时间,一直阻塞,直到当集合中有文件描述符准备就绪,该函数解除阻塞;
    返回值:
        >0, 实际产生事件的文件描述符个数;
        =0, 超时了
        =-1,更新errno;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    68.4.2.2 poll的TCP客户端

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define ERR_MSG(msg) do{\
     fprintf(stderr, "__%d__ ", __LINE__);\
     perror(msg);\
    }while(0)
    
    #define SER_PORT 8888           //服务器绑定的端口号
    #define SER_IP "192.168.125.55"     //服务器绑定的IP                                                  
    
    int main(int argc, const char *argv[])
    {
     //创建流式套接字
     int cfd = socket(AF_INET, SOCK_STREAM, 0);
     if(cfd < 0)
     {
         ERR_MSG("socket");
         return -1;
     }
     printf("socket create success  cfd=%d\n", cfd);
    
     //允许端口快速复用
     int reuse = 1;
     if(setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
     {
         ERR_MSG("setsockopt");
         return -1;
     }
     printf("允许端口快速复用成功\n");
    
    
     //绑定客户端的地址信息---》非必须绑定
     //客户端若不绑定,则操作系统会自动给客户端绑定本机IP及随机端口
    
    
     //填充服务器的地址信息结构体给connect函数使用;
     //真实的地址信息结构体根据地址族指定,AF_INET:man 7 ip
     struct sockaddr_in sin;
     sin.sin_family      = AF_INET;              //必须填AF_INET;
     sin.sin_port        = htons(SER_PORT);      //服务器绑定的端口号
     sin.sin_addr.s_addr = inet_addr(SER_IP);    //服务器绑定的IP
    
    
     //连接服务器,想要连接哪个服务器就需要填充哪个服务器绑定的地址信息
     if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
     {
         ERR_MSG("connect");
         return -1;
     }
     printf("connect server success\n");
    
     //创建集合
     struct pollfd fds[2] = {0};
     //将需要的文件描述符添加到集合中
     fds[0].fd       = 0;        //指定要监测0号文件描述符
     fds[0].events   = POLLIN;   //指定要监测读事件
    
     fds[1].fd       = cfd;      //指定要监测cfd
     fds[1].events   = POLLIN;
    
     int p_res = 0;
     char buf[128] = "";
     ssize_t res = 0;
     while(1)
     {
         p_res = poll(fds, sizeof(fds)/sizeof(struct pollfd), -1);
         if(p_res < 0)
         {
             ERR_MSG("poll");
             return -1;
         }
         else if(0 == p_res)
         {
             printf("time out");
             break;
         }
         //能运行到当前位置,则代表集合中有文件描述符准备就绪
         //即revents成员中有数据了,
         //判断revents中是否有POLLIN事件
         if(fds[0].revents & POLLIN)
         {
             printf("触发键盘输入事件\n");
             bzero(buf, sizeof(buf));
             fgets(buf, sizeof(buf), stdin);
             buf[strlen(buf)-1] = '\0';
    
             //发送数据
             if(send(cfd, buf, sizeof(buf), 0) < 0)
                 //if(write(cfd, buf, sizeof(buf)) < 0)
             {
                 ERR_MSG("send");
                 return -1;
             }
             printf("send success\n");
    
         }
    
         if(fds[1].revents & POLLIN)
         {
             bzero(buf, sizeof(buf));
             //接收数据
             res = recv(cfd, buf, sizeof(buf), 0);
             //res = read(cfd, buf, sizeof(buf));
             if(res < 0)
             {
                 ERR_MSG("recv");
                 return -1;
             }
             else if(0 == res)
             {
                 printf("[%s:%d] cfd=%d 服务器下线__%d__\n", \
                         SER_IP, SER_PORT, cfd, __LINE__);
                 break;
             }
             printf("[%s:%d] cfd=%d :%s __%d__\n", \
                     SER_IP, SER_PORT, cfd, buf, __LINE__);
    
         }
     }
     //关闭套接字
     close(cfd);
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    68.4.2.3 epoll(到驱动部分再讲解)(是重点)

  • 相关阅读:
    IDEA中DEBUG技巧
    55、MINE: Towards Continuous Depth MPI with NeRF for Novel View Synthesis
    服务器安装Ubuntu20及系统扩容
    【附源码】计算机毕业设计JAVA抑抑心理交流平台
    景联文科技:关于语音标注,你知道多少?
    Linux 系统 Vi和Vim编辑器—笔记7
    echarts,y轴边上数据过长超出屏幕
    Python使用MySQL,无记录则插入,有记录则更新 - ON DUPLICATE KEY UPDATE
    uniapp小程序点击按钮直接退出小程序效果demo(整理)
    JavaSE之多线程高级(死锁、线程等待和唤醒)
  • 原文地址:https://blog.csdn.net/qq_52625576/article/details/133653243