定义:I/O 复用使得程序能同时监听多个文件描述符,这对于提高程序的性能至关重要。
网络程序在下列情况下需要使用 I/O 复用技术:
- ◼ TCP 服务器同时要处理监听套接字和连接套接字。
- ◼ 服务器要同时处理 TCP 请求和 UDP 请求。
- ◼ 程序要同时处理多个套接字。
- ◼ 客户端程序要同时处理用户输入和网络连接。
- ◼ 服务器要同时监听多个端口。
- 需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当 多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一 个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以 配合使用多线程或多进程等编程方法。
I/O复用方法的功能:检查若干个描述符有没有关系的事件产生。
(1)在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

①nfds是指定被监听的文件描述符的总数目;
fd_set 是结构指针类型:
②readfds指向可读事件对应的文件描述符集合;
③writefds是指向可写事件对应的文件描述符集合;
④exceptfds是指向异常事件对应的文件描述符集合。
fd_set结构体定义:
⑤timeout是用来设置select函数的超时时间。它是一个timeval结构类型的指针,采用指针参数是因为内核将修改它,用来告诉应用程序select等待了多久。
(3)sclect成功时返回就绪(可读、可写和异常)文件描述符的总数。
- 如果在超时时间内没有任何文件描述符就绪,select将返回0。select失败时返回-1并设置errno。
- 如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为 EINTR。
- ①socket内核接收缓存区中的字节数大于或等于其低水位标记SO RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
- ②socket通信的对方关闭连接。此时对该socket的读操作将返回0。监听socket上有新的连接请求。
- ③socket 上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
- ①socket内核发迭缓存区中的可用字节数大于或等于其低水位标记so_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
- ②socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE 信号。
- ③socket使用非阻塞connect连接成功或者失败(超时)-之后。
- ④socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
- 注意:select能处理的异常情况只有一种:socket上接收到带外数据。
//select用法
#include
#include
#include
#include
#include
#include#define STDIN 0int main()
{int fd=STDIN;//键盘对应的描述符fd_set fdset;//收集描述符while(1){FD_ZERO(&fdset);//清空集合FD_SET(fd,&fdset);//添加描述符(fd)到集合struct timeval tv={5,0};int n=select(fd+1,&fdset,NULL,NULL,&tv);if(n==-1){printf("select err\n");//退出}else if(n==0){printf("time out\n");//超时}else{//检测描述符上是否有数据,如果为真,就是有数据if(FD_ISSET(fd,&fdset))//按位与,同1为1{char buff[128]={0};read(fd,buff,127);printf("buff=%s\n",buff);}} }
}
//tcp服务器端
#include
#include
#include
#include
#include
#include
#include
#include
#include#define MAXFD 10void fds_init(int fds[])
{for(int i=0;imaxfd)//找到数组中描述符的最大值{maxfd=fds[i];}}struct timeval tv={5,0};int n=select(maxfd+1,&fdset,NULL,NULL,&tv);//阻塞五秒钟if(n==-1){printf("select err\n");}else if(n==0){printf("time out\n");}else{for(int i=0;i
//客户端cli
#include
#include
#include
#include
#include
#include
#includeint main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){printf("socket err\n");exit(0);}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);//1024以内是专用;我们用4096以上,root saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//三次握手开始if(res==-1){printf("connect failed\n");exit(0);}//从键盘获取数据while(1){printf("input: \n");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);//清空int n=recv(sockfd,buff,127,0);if(n<=0)//判断服务器是否关闭{break;}printf("buff=%s\n",buff);}close(sockfd);}
如有错误,敬请指正。
您的收藏与点赞都是对我最大的鼓励和支持!