io多路复用-select()

参照《Unix网络编程》相关章节内容,实现了一个简单的单线程IO多路复用服务器与客户端。

普通迭代服务器,由于执行recvfrom则会发生阻塞,直到客户端发送数据并正确接收后才能够返回,一个服务器进程只能服务于一个客户端,解决这种问题可采用多线程方式(参见虚拟机隐藏进程检测工具实现)和IO多路复用select和poll,select()机制的优势在于可以同时等待多个描述符就绪。

与IO复用密切相关的另一种IO模型是在多线程中使用阻塞式IO。

 

简要描述select机制:

fd_set rset

void FD_ZERO(fd_set *fdset)

void FD_SET(int fd, fd_set *fdset)

void FD_CLR(int fd, fd_set *fdset)

void FD_ISSET(int fd, fd_set *fdset)

其中rset类型为fd_set,可理解为一个位图,用于标识哪些描述符正在被监听。FD_ZERO用于初始化rset,FD_SET用于设置新的用于被监听的描述符,FD_CLR用于清空rset,FD_ISSET用户判断具体哪个描述符就绪。

 

由于tcp服务器中具有监听套接字与已连接套接字两个概念,在服务器端实现中主要过程如下:

  1. 初始化rset,转2;
  2. 添加监听套接字到rset中,转3;
  3. select阻塞等待是否存在描述符就绪,转4;
  4. 当客户端连接后,rset中设置的监听套接字就绪,添加已连接套接字到rset,转5;
  5. 判断哪个描述符就绪(一般为新的已连接套接字),进行套接字读写操作,转1;

在《Unix网络编程》中,上述步骤5结束后直接转2,但在实际测试与资料查阅中,由于内核会修改描述符内容,使得需要重新初始化rset才能有效。

代码测试:

服务端:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/select.h>
  4 #include <unistd.h>
  5 #include <sys/socket.h>
  6 #include <netinet/in.h>
  7 #include <linux/netlink.h>
  8 #include <string.h>
  9 #include <arpa/inet.h>
 10 
 11 #define IP_ADDR 127.0.0.1
 12 #define IP_PORT 8081
 13 
 14 #define LISTEN_NUM 10
 15 
 16 #define MAXLINE 1024
 17 
 18 int main(void)
 19 {
 20     struct sockaddr_in clitaddr, servaddr;
 22     unsigned int clilen;
 23     int listenfd, connfd, sockfd, ret;
 24 
 25     int maxindex, i, n;
 26 
 27     int maxfd;
 28     fd_set allset;
 29 
 30     int client[FD_SETSIZE];
 31     int nready;
 32 
 33     char buf[MAXLINE];
 34 
 35     int reuse = 1;
 36     
 37     listenfd = socket(AF_INET,SOCK_STREAM,0);
 38 
 39     if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
 40         return -1;
 41     }
 42 
 43     bzero(&servaddr,sizeof(servaddr));
 44     servaddr.sin_family = AF_INET;
 45     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 46     servaddr.sin_port = htons(IP_PORT);
 47 
 48     ret = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
 49     if(ret == -1) {
 50         printf("bind socket error\n");
 51         exit(0);
 52     }
 53 
 54     ret = listen(listenfd,LISTEN_NUM);
 55     if(ret == -1) {
 56         printf("listen socket error\n");
 57         exit(0);
 58     }
 59 
 60     maxfd = listenfd;
 61     maxindex = -1;
 62     for(i = 0; i < FD_SETSIZE; i++) {
 63         client[i] = -1;
 64     }
 65     //FD_ZERO(&allset);
 66     //FD_SET(listenfd, &allset);
 67 
 68     for( ; ; ) {
 69         //reset = allset;
 70         FD_ZERO(&allset);
 71         FD_SET(listenfd, &allset);
 72         for (i = 0; i <= maxindex; i++) {
 73             FD_SET(client[i], &allset);
 74         }
 75 
 76         nready = select(maxfd + 1, &allset, NULL, NULL, NULL);
 77 
 78         if(FD_ISSET(listenfd, &allset)) {
 79             clilen = sizeof(clitaddr);
 80             connfd = accept(listenfd, (struct sockaddr*)&clitaddr, &clilen);
 81             //connfd = accept(listenfd, (struct sockaddr*)&clitaddr, sizeof(clitaddr));
 82 
 83             //printf("clitaddr ip : %d,port : %d\n", inet_ntoa(clitaddr.sin_addr), clitaddr.sin_port);
 84             fprintf(stdout, "accept clitaddr %s:%d\n", inet_ntoa(clitaddr.sin_addr), clitaddr.sin_port);
 85             for(i = 0; i < FD_SETSIZE; i++) {
 86                 if(client[i] < 0){
 87                     client[i] = connfd;
 88                     break;
 89                 }
 90             }
 91 
 92             if(i == FD_SETSIZE) {
 93                 printf("too many clients\n");
 94                 exit(0);
 95             }
 96 
 97             FD_SET(connfd, &allset);
 98             if(connfd > maxfd) {
 99                 maxfd = connfd;
100             }
101             if(i > maxindex) {
102                 maxindex = i;
103             }
104             if(--nready <= 0)
105                 continue;
106         }
107 
108         for(i = 0; i <= maxindex; i++) {
109             if((sockfd = client[i]) < 0)
110                 continue;
111             if(FD_ISSET(sockfd, &allset)) {
112                 if((n = recv(sockfd, buf, MAXLINE, 0)) == 0) {
113                     close(sockfd);
114                     FD_CLR(sockfd, &allset);
115                     client[i] = -1;
116                 }
117                 else {
118                     printf("server recv buf is %s\n", buf);
119                     send(sockfd, buf, strlen(buf), 0);
120                 }
121                 if(--nready <= 0)
122                     break;
123             }
124         }
125     }
126 
127     return 0;
128 }

客户端:

 1 #include <netinet/in.h>
 2 #include <sys/socket.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <sys/select.h>
 7 #include <time.h>
 8 #include <unistd.h>
 9 #include <sys/types.h>
10 #include <errno.h>
11 #include <math.h>
12 
13 #define SERVER_ADDR "127.0.0.1"
14 #define SERVER_PORT 8081
15 
16 #define MAXLINE 1024
17 
18 void str_cli(FILE *fp, int sockfd)
19 {
20     int maxfdp, stdineof;
21     fd_set rset;
22     char buf[MAXLINE];
23     int n;
24 
25     stdineof = 0;
26     FD_ZERO(&rset);
27 
28     for( ; ; ) {
29         if(stdineof == 0)
30             FD_SET(fileno(fp), &rset);
31         FD_SET(sockfd, &rset);
32         maxfdp = (fileno(fp) > sockfd) ? (fileno(fp) + 1) : (sockfd + 1);
33         //maxfdp = max(fileno(fp), sockfd) + 1;
34         select(maxfdp, &rset, NULL, NULL, NULL);
35 
36         if(FD_ISSET(sockfd, &rset)) {
38             if ((n = recv(sockfd, buf, MAXLINE, 0)) == 0) {
39                 if (stdineof = 1) {
40                     return ;
41                 }
42                 else {
43                     printf("server prematurely\n");
44                     exit(0);
45                 }
46             }
47             printf("client:sockfd ready\n");
48             //write(fileno(stdout), buf, n);
49             write(fileno(stdout), buf, strlen(buf) + 1);
50         }
51 
52         if (FD_ISSET(fileno(fp), &rset)) {
53             if ((n = read(fileno(fp), buf, MAXLINE)) == 0) {
54                 stdineof = 1;
55                 shutdown(sockfd, SHUT_WR);
56                 FD_CLR(fileno(fp), &rset);
57                 continue;
58             }
59             //buf[n] = '\0';
60             printf("client buf is %s",buf);
61             send(sockfd, buf, strlen(buf), 0);
62         }
63     }
64     return ;
65 }
66 
67 int main(void)
68 {
69     int sockfd, ret;
70     struct sockaddr_in servaddr;
71 
72     sockfd = socket(AF_INET, SOCK_STREAM, 0);
73     if(sockfd < 0) {
74         printf("create socket error\n");
75         exit(0);
76     }
77 
78     bzero(&servaddr, sizeof(servaddr));
79     servaddr.sin_family = AF_INET;
80     servaddr.sin_port = htons(SERVER_PORT);
81     servaddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
82 
83     ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
84     if(ret < 0) {
85         printf("connect error\n");
86         exit(0);
87     }
88 
89     str_cli(stdin, sockfd);
90     exit(0);
91 }

执行结果:

服务器启动后处于监听状态,可以启动多个客户端连入服务器请求并响应。实验结果略。

posted on 2017-10-24 15:52  chenjx_ucs  阅读(245)  评论(0编辑  收藏  举报

导航