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服务器中具有监听套接字与已连接套接字两个概念,在服务器端实现中主要过程如下:
- 初始化rset,转2;
- 添加监听套接字到rset中,转3;
- select阻塞等待是否存在描述符就绪,转4;
- 当客户端连接后,rset中设置的监听套接字就绪,添加已连接套接字到rset,转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 阅读(252) 评论(0) 编辑 收藏 举报