网络IO 多路IO复用
select
poll
epoll
为什么要用多路io复用呢?
如果不用多路io复用,可以开辟多个线程,或者一个线程循环的去读取每个io的数据,这样的开销是非常的大的,而且会大大的影响系统的性能,因此引入多路io复用
select 用于多路io复用,具体是如何实现的?
内部使用fd_set的方式来进行实现,因为做select 只需要标志自己内部的fd 可用还是不可用,则相当于是1个bit位, 0/1的取值。
因此使用fd_set 位图的方式来进行对select的实现
select的代码实现流程
1、创建sockfd
socket(AF_INET,SOCK_STREAM,0)
2、初始化addr 属性 struct sockaddr_in (sa_handler ,sa_port,sa_addr.s_addr)
3、bind sockfd
4、listen 设置最大连接数
5、定义select 的集合,fd_set,定义检测集合 rfds,具体使用的集合rsets
6、传入select 参数中,select 的参数,5个,maxfd+1,可读集合,可写集合,异常集合,时间 -- 返回值为 select监听到的连接数 nready
7、判断 sockfd 是否在rset集合里面,如果在,则定义客户端的数据结构, 和定义客户端数据结构的长度,需要传入到accept中
8、开始iaccept,返回clientfd, 把 clientfd 放入 rfds中, 且更新maxfd的大小
9、如果--nready ==0 ,则继续select 阻塞监听,若--nready 大于零, 则是 客户端和服务器进行数据通信
10、数据通信的时候服务器 使用 recv/read 读取数据,放入到buff中,
如果函数返回值是 <0 ,则需要判断,错误号若为 EAGAIN || EWOULDBLOCK -- 被信号打断了,未读取到数据,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd
如果函数返回值是 == 0,说明客户端断开连接了,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd
如果函数返回值是 > 0,说明正常读取到数据,开始对数据进行处理
最后 关闭sockfd 套接字 close(sockfd)
编码
写一个简单的demo,来感受一下select的使用
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <pthread.h> #include <errno.h> #include <fcntl.h> #include <sys/epoll.h> #include <sys/poll.h> #include <unistd.h> #define BUFFER_LENGTH 1024 int main(int argc,char * argv[]) { //端口从命令行输入 如 : ./server 8888 if(argc < 2) { perror("please input port nunber"); return -1; } //建立tcp套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0) { perror("socket"); return -1; } //初始化属性 struct sockaddr_in seraddr; memset(&seraddr,0,sizeof(seraddr)); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(atoi(argv[1])); seraddr.sin_addr.s_addr = INADDR_ANY; //bind int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in)); if(ret < 0){ perror("bind"); return -1; } //设置最大连接数 ret = listen(sockfd,10); if(ret < 0){ perror("listen"); return -1; } //select fd_set rset,rfds; FD_ZERO(&rfds); FD_SET(sockfd,&rfds); int maxfd = sockfd; //开始select监听 while(1){ rset = rfds; int nready = select(maxfd+1,&rset,NULL,NULL,NULL); if (nready < 0) { printf("select error : %d\n", errno); continue; } //开始accept if(FD_ISSET(sockfd,&rset)){ struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_len = sizeof(client_addr); int clientfd = accept(sockfd,(struct sockaddr*)&client_addr,&client_len); if(clientfd <= 0){ printf("accept error\n"); continue; } //打印消息 char str[BUFFER_LENGTH] = {0}; printf("recvived from %s at port %d, sockfd:%d, clientfd:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port), sockfd, clientfd); if (maxfd == FD_SETSIZE) { printf("clientfd --- out range\n"); break; } FD_SET(clientfd,&rfds); if (clientfd > maxfd) maxfd = clientfd; printf("\nsockfd:%d, maxfd:%d, clientfd:%d\n\n", sockfd, maxfd, clientfd); printf("nready == %d\n",nready); if (--nready == 0) continue; //说明 当前请求的都是连接,并没有发数据 } //客户端开始和服务器进行数据的传输 for(int i = sockfd+1;i<=maxfd;i++){ if(FD_ISSET(i,&rset)){ char buffer[BUFFER_LENGTH] = {0}; int ret = recv(i, buffer, BUFFER_LENGTH, 0); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("read all data"); } FD_CLR(i, &rfds); close(i); } else if (ret == 0) { printf(" disconnect %d\n", i); FD_CLR(i, &rfds); close(i); break; } else { printf("Recv: %s, %d Bytes\n", buffer, ret); //将客户端发过的数据,发回去 send(i, buffer, BUFFER_LENGTH, 0); } //减去一个客户端的fd if (--nready == 0) break; } } } close(sockfd); return 0; }
验证和调试