引入复用技术,可以减少进程数,无论连接多少客户端,提供服务的进程只有一个。
select函数可以实现IO复用,它可以将多个文件描述符集中到一起统一监视:
是否存在套接字接收数据?无需阻塞传输数据的套接字有哪些?哪些套接字发生了异常?
下面是具体用例:
//复用:时分复用;频分复用 //使用select函数完成IO复用 //1.设置文件描述符;指定监视范围;设置超时 //2.调用select函数 //3.查看调用结果 #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 30 int main(void) { fd_set reads, temps; int result, str_len; char buf[BUF_SIZE]; struct timeval timeout; FD_ZERO(&reads); FD_SET(0, &reads); //0表示标准输入描述符 while(1) { //调用select函数后,除了发生变化的描述符对应位以外剩下所有位将 //初始化为0,因此应记住初始值reads。 temps=reads; //timeout设置应在循环里,因为调用select后timeout会被替换为超时前剩余时间 timeout.tv_sec=5; timeout.tv_usec=0; //调用select函数,监控范围是1,timeout是5s result=select(1, &temps, 0, 0, &timeout); if(result==-1) { puts("select error"); break; } else if(result==0) { puts("time out"); } else { //验证第0位是否为1,即是否有标准输入 if(FD_ISSET(0, &temps)) { str_len=read(0, buf, BUF_SIZE); buf[str_len]=0; printf("message from console: %s \n", buf); } } } return 0; }
实现IO复用的服务器端:
//使用select函数实现IO复用回声服务器 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 30 void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; int str_len; char buf[BUF_SIZE]; fd_set reads, cpy_reads; struct timeval timeout; int fd_max, fd_num; if(argc!=2) { exit(1); } //TCP socket serv_sock=socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; //IPV4协议族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //主机字节序(host)转换成网络字节序(net)(大端序) serv_addr.sin_port = htons(atoi(argv[1])); //端口号 if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind error"); if(listen(serv_sock, 5) == -1) error_handling("listen error"); FD_ZERO(&reads); FD_SET(serv_sock, &reads); fd_max=serv_sock; while(1) { cpy_reads=reads; timeout.tv_sec=5; timeout.tv_usec=5000; if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1) break; if(fd_num==0) continue; int i; for(i=0;i<fd_max+1;i++) { if(FD_ISSET(i, &cpy_reads)) { if(i==serv_sock) //发生连接请求 { clnt_addr_size=sizeof(clnt_addr); clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //将新的客户端套接字加入监控 FD_SET(clnt_sock, &reads); if(fd_max<clnt_sock) fd_max=clnt_sock; printf("connected client: %d \n", clnt_sock); } else //读取消息 { str_len=read(i, buf, BUF_SIZE); if(str_len==0) //如果接收到EOF结束符要关闭套接字 { FD_CLR(i, &reads); //将监控位置0 close(i); printf("close client: %d \n", i); } else { write(i, buf, str_len); } } } } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }