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 "); 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("---------------------------------------------- "); 112 printf("client ip=%s,port=%d ", 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 ", 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 ", 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 ", 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 "); 152 } 153 154 155 }else if(0 == num){ // 客户端断开时 156 157 //客户端退出,关闭套接字,并从监听集合清除 158 printf("client:fd_all[%d] exit ", 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 "); 168 } 169 } 170 171 } 172 173 return 0; 174 }