linux之linux并发编程(3)

I/O复用服务器
I/O 复用技术是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。它也可用于并发服务器的设计,常用函数 select() 或 epoll() 来实现。详情,请看《select、poll、epoll的区别使用》。
socket(...); // 创建套接字
bind(...);   // 绑定
listen(...); // 监听
while(1)
{
    if(select(...) > 0) // 检测监听套接字是否可读
    {
        if(FD_ISSET(...)>0) // 套接字可读,证明有新客户端连接服务器  
        {
            accpet(...);// 取出已经完成的连接
            process(...);// 处理请求,反馈结果
        }
    }
    close(...); // 关闭连接套接字:accept()返回的套接字
}

参考代码:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <errno.h>
  5 #include <string.h>
  6 #include <sys/socket.h>
  7 #include <sys/types.h>
  8 #include <netinet/in.h>
  9 #include <arpa/inet.h>
 10 #include <sys/select.h>
 11 #define SERV_PORT 8080
 12 #define LIST 20                //服务器最大接受连接
 13 #define MAX_FD 10              //FD_SET支持描述符数量
 14 int main(int argc, char *argv[])
 15 {
 16     int sockfd;
 17     int err;
 18     int i;
 19     int connfd;
 20     int fd_all[MAX_FD]; //保存所有描述符,用于select调用后,判断哪个可读
 21     
 22     //下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值
 23     fd_set fd_read;    //FD_SET数据备份
 24     fd_set fd_select;  //用于select
 25     struct timeval timeout;         //超时时间备份
 26     struct timeval timeout_select;  //用于select
 27     
 28     struct sockaddr_in serv_addr;   //服务器地址
 29     struct sockaddr_in cli_addr;    //客户端地址
 30     socklen_t serv_len;
 31     socklen_t cli_len;
 32     
 33     //超时时间设置
 34     timeout.tv_sec = 10;
 35     timeout.tv_usec = 0;
 36     
 37     //创建TCP套接字
 38     sockfd = socket(AF_INET, SOCK_STREAM, 0);
 39     if(sockfd < 0)
 40     {
 41         perror("fail to socket");
 42         exit(1);
 43     }
 44     
 45     // 配置本地地址
 46     memset(&serv_addr, 0, sizeof(serv_addr));
 47     serv_addr.sin_family = AF_INET;            // ipv4
 48     serv_addr.sin_port = htons(SERV_PORT);    // 端口, 8080
 49     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
 50     serv_len = sizeof(serv_addr);
 51     
 52     // 绑定
 53     err = bind(sockfd, (struct sockaddr *)&serv_addr, serv_len);
 54     if(err < 0)
 55     {
 56         perror("fail to bind");
 57         exit(1);
 58     }
 59     // 监听
 60     err = listen(sockfd, LIST);
 61     if(err < 0)
 62     {
 63         perror("fail to listen");
 64         exit(1);
 65     }
 66     
 67     //初始化fd_all数组
 68     memset(&fd_all, -1, sizeof(fd_all));
 69     fd_all[0] = sockfd;   //第一个为监听套接字
 70     
 71     FD_ZERO(&fd_read);    // 清空
 72     FD_SET(sockfd, &fd_read);  //将监听套接字加入fd_read
 73     int maxfd;
 74     maxfd = fd_all[0];  //监听的最大套接字
 75     
 76     while(1){
 77     
 78         // 每次都需要重新赋值,fd_select,timeout_select每次都会变
 79         fd_select = fd_read;
 80         timeout_select = timeout;
 81         
 82         // 检测监听套接字是否可读,没有可读,此函数会阻塞
 83         // 只要有客户连接,或断开连接,select()都会往下执行
 84         err = select(maxfd+1, &fd_select, NULL, NULL, NULL);
 85         //err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select);
 86         if(err < 0)
 87         {
 88                 perror("fail to select");
 89                 exit(1);
 90         }
 91         if(err == 0){
 92             printf("timeout\n");
 93         }
 94         
 95         // 检测监听套接字是否可读
 96         if( FD_ISSET(sockfd, &fd_select) ){//可读,证明有新客户端连接服务器
 97             
 98             cli_len = sizeof(cli_addr);
 99             
100             // 取出已经完成的连接
101             connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
102             if(connfd < 0)
103             {
104                 perror("fail to accept");
105                 exit(1);
106             }
107             
108             // 打印客户端的 ip 和端口
109             char cli_ip[INET_ADDRSTRLEN] = {0};
110             inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
111             printf("----------------------------------------------\n");
112             printf("client ip=%s,port=%d\n", cli_ip,ntohs(cli_addr.sin_port));
113             
114             // 将新连接套接字加入 fd_all 及 fd_read
115             for(i=0; i < MAX_FD; i++){
116                 if(fd_all[i] != -1){
117                     continue;
118                 }else{
119                     fd_all[i] = connfd;
120                     printf("client fd_all[%d] join\n", i);
121                     break;
122                 }
123             }
124             
125             FD_SET(connfd, &fd_read);
126             
127             if(maxfd < connfd)
128             {
129                 maxfd = connfd;  //更新maxfd
130             }
131         
132         }
133         
134         //从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd)
135         for(i=1; i < maxfd; i++){
136             if(FD_ISSET(fd_all[i], &fd_select)){
137                 printf("fd_all[%d] is ok\n", i);
138                 
139                 char buf[1024]={0};  //读写缓冲区
140                 int num = read(fd_all[i], buf, 1024);
141                 if(num > 0){
142                     //收到 客户端数据并打印
143                     printf("receive buf from client fd_all[%d] is: %s\n", i, buf);
144                     
145                     //回复客户端
146                     num = write(fd_all[i], buf, num);
147                     if(num < 0){
148                         perror("fail to write ");
149                         exit(1);
150                     }else{
151                         //printf("send reply\n");
152                     }
153                     
154                     
155                 }else if(0 == num){ // 客户端断开时
156                     
157                     //客户端退出,关闭套接字,并从监听集合清除
158                     printf("client:fd_all[%d] exit\n", i);
159                     FD_CLR(fd_all[i], &fd_read);
160                     close(fd_all[i]);
161                     fd_all[i] = -1;
162                     
163                     continue;
164                 }
165                 
166             }else {
167                 //printf("no data\n");                  
168             }
169         }
170     
171     }
172     
173     return 0;
174 }

 

posted @ 2020-03-27 17:49  Sunny_Boy_H  阅读(224)  评论(0编辑  收藏  举报