I/O 复用使得程序能同时监听多个文件描述符,这对于提高程序的性能至关重要。通常,
网络程序在下列情况下需要使用 I/O 复用技术:
1.TCP服务器同时要处理监听套接字和连接套接字2.服务器同时要处理TCP请求和UDP请求。
3.程序同时要处理多个套接字。
4.客服端程序要同时处理用户输入和网络连接。
5.服务器要同时监听多个端口。
需要指出的是, I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当
多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一
个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以
配合使用多线程或多进程等编程方法。1select
1.1select的接口介绍
select系统调用的用途是:再一段指定时间内,监听用户感兴趣的文件描述符的可读、可写和异常事件。
select系统调用的原型如下:
# include <sys/select.h> int select (int maxfd,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,struct timeval*timeout); /* select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时间内没有任何文件描述符就绪,select将返回0.select失败返回-1,如果是在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。 maxfd 参数指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所 有文件描述符中的最大值+1 readfds、 writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件 描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件 描述符。 select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪 通过下列宏可以访问 fd_set 结构中的位: FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位 FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd,fd添加到集合(置一) FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置 timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指 针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。 timeval 结构的定义如下 struct timeval { long tv_sec;//秒数 long tv_usec://微秒数 }; 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递 NULL,则 select 将一直阻塞,直到某个文件描述符就绪 */
1.2select的示例代码
使用select实现的TCP服务器如下:
初始化服务器端的sockfd套接字
int initsocket() { int sockfd=socket(AF_INET,SOCK_STREAM,0); if (sockfd==-1) return -1; struct sockaddr_in saddr; memset(&saddr,0,sizeof (saddr)); saddr.sin_family=AF_INET; saddr.sin_port=htons(6000); saddr.sin_addr.s_addr=inet_addr("127.0.0.1"); int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof (saddr)); if (res==-1) return -1; res=listen(sockfd,5); if (res==-1) return -1; return sockfd; }
初始化记录服务器套接字的数组
void initFds(int fds[],int n) { int i=0; for (;i<n;i++) { fds[i]=-1; } }
将套接字描述符添加到数组中
void AddFdToFds(int fds[],int fd,int n) { int i=0; for (;i<n;i++) { if (fds[i]==-1) { fds[i]=fd; break; } } }
删除数组中的套接字描述符
void DelFromFds(int fds[],int fd,int n) { for (int i=0;i<n;i++) { if (fds[i]==fd) { fds[i]=-1; break; } } }
将数组中的套接字描述符设置到fd_set变量中,并返回当前最大文件描述符
int SetFsToFdset(fd_set*fdset,int fds[],int n) { FD_ZERO(fdest);//fdest置零 int i=0; int maxfd=fds[0];//最大文件描述符的值 for (;i<n;i++) { if (fds[i]!=-1) { FD_SET(fds[i],fdest);//将fdest的位fds[i]的值置为1 if (fds[i]>maxfd) { maxfd=fds[i]; } } } return maxfd; }
服务器和客户端的连接
void GetClientLink(int sockfd,int fds[],int n) { struct sockaddr_in caddr; int len=sizeof (caddr); memset(&caddr,0,sizeof(caddr)); int c=accept(sockfd,(struct sockaddr*)&caddr,&len); if (c<0) { return; } AddFdToFds(fds,c,n); }
处理客户端数据
void DealClientData(int fds[],int n,int clifd) { char data[128]; int num=recv(clifd,data,127,0); if (num<=0) { DelFdFromFds(fds,clifd,n); close(clifd); } else { printf ("%d:%s\n",clifd,data); send(clifd,"ok",2,0); } }
处理select返回的就绪事件
void DealReadyEvent(int fds[],int n,fd_set*fdset,int sockfd) { int i=0; for (;i<n;i++) { if (fds[i]!=-1&&FD_ISSET(fds[i],fdest)) { if(fds[i]==sockfd) { GetClientLink(sockfd,fds,n); } else { DealClientLink(fds,n,fds[i]); } } } }
主函数
int main() { int sockfd=initsocket(); assert(sockfd!=-1); fd_set readfds; int fds[1024]; initFds(fds,1024); AddFdToFds(fds,sockfd,1024); while(1) { int maxfd=SetFdToFdset(&readfds,fds,MAX_FD); struct timeval timeout; timeout.tv_sec=2;//秒数 timeout.tv_usec=0;//微秒数 int n=select(maxfd+1,&readfds,NULL,NULL,&timeout); if (n<0) { printf ("select error\n"); break; } else if (n==0) { printf ("timeout\n"); continue; } DealReadyEvent(fds, 1024, &readfds, sockfd); } exit(0); }
TCP 的客户端代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <assert.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int initsocket() { int sockfd=socket(AF_INET,SOCK_STREAM,0); if (sockfd==-1) return -1; struct sockaddr_in saddr; memset(&saddr,0,sizeof (saddr)); saddr.sin_family=AF_INET; saddr.sin_port=htons(6000); saddr.sin_addr.s_addr=inet_addr("127.0.0.1"); int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof (saddr)); if (res==-1) return -1; res=listen(sockfd,5); if (res==-1) return -1; return sockfd; } int main () { int sockfd=initsocket(); assert(sockfd!=-1); while(1) { printf ("please input:"); char buff[128]={0}; fgets(buff,127,stdin); if (strncmp(buff,"bye",3)) { break; } int n=send(sockfd,buff,strlen(buff)-1,0); if (n<=0) { printf ("send error\n"); break; } memset(buff,0,128); n=recv(sockfd,buff,127,0); if(n<=0) { printf ("recv error\n"); break; } printf ("%s\n",buff); } close(sockfd); exit(0); }