一个简单的服务器端程序:
#include "unp.h"
int main(int argc,char**argv)
{
int listenfd,connfd;
pid_t childpid;
socklet_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=hton1(INADDR_ANY);
servaddr.sin_port=htons(serv_port);
bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
listen(listenfd,LISTENQ);
for(;;)
{
clilen=sizeof(cliaddr);
connfd=accept(listenfd,(SA*)&cliaddr,&clilen);
if((childpid==fork())==0))
{
close(listenfd);//子进程关闭监听接口
while(n=read(sockfd,buf,maxline)>0)
writen(sockfd,buf,n);
exit(0);}
close(connfd);//关闭连接
}
}
对于上面的这个简单的服务器程序,如果客户阻塞于read调用期间,服务器进程被杀死了。服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然客户阻塞在从标准输入读入字节流,它看不到这个EOF,知道可以从套接口读为止。这样的进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个IO条件就绪,内核就通知进程读数据,这就是I/O复用,是由select和poll这两个函数支持的。
I/O复用模型,主要用在以下场合:
1、当客户处理多个描述字的时候,必须使用I/O复用
2、一个客户同时处理多个描述字是有可能得,不过比较少见。
3、一个TCP既要处理监听套接口,又要处理已连接的套接口
4、一个服务器既要处理TCP又要处理UDP
5、一个服务器要处理多个进程和协议
在介绍I/O模型之前,首先介绍UNIX下的5种I/O模型
1、阻塞I/O模型:上面的那个服务器简单的例子就是阻塞I/O模型,如果接收不到数据,则进程阻塞在读数据上,直到有数据到达,进程继续执行。
2、非阻塞I/O模型:当应用进程从服务器端读取数据时,若内核无数据准备好,并不是阻塞进程,而是则返回一个错误,过一段时间后,进程再次以轮询的方式读取数据,重复上面步骤,直到数据准备好,返回给用户进程成功读取的信息。这么做使进程从阻塞IO中脱离出来,但是不挺的轮询耗费大量的cpu时间,不过这种模型偶尔也会遇到,通常是在只专门提供某种功能的系统中才有。
3、I/O复用模型,有了I/O复用模型,我们就可以调用色了select和poll函数,阻塞在这两个系统调用的某一个之上,而不是阻塞于真正的I/O系统调用之上。我们阻塞于select调用,等待数据报接口变为可读,当select返回套接口可读这一条件时,我们在吧所有数据读到进程缓冲区。
4、信号驱动I/O:我们可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们,我们称这种模型为信号驱动I/O。我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数,该系统调用立即返回,我们的进程继续工作,也就是说它没有阻塞。当数据报准备好事,内核发送一个signo信号,应用进程捕捉到信号并且进入信号处理函数,在信号处理函数中读取数据报。这个模型的优势在于等待数据报到达期间,进程不被阻塞,主循环可以继续执行。
5、异步I/O模型:由posix规范定义。一般的说,这个模型的工作机制是:告知内核启动某种操作,并让内核在整个操作完成后通知我们。这种模型与信号驱动I/O的主要区别在于:信号驱动I/O是有内核通知我们何时可以启动一个I/O操作,二异步I/O模型是有内核通知我们操作何时完成。
I/O复用中的select函数和poll函数:
int select(int maxfdp1,fd_set*readset,fd_set*write_set,fd_set*exceptset,const struct timeval *timeout)
maxfdp1 : 描述字最大值
readset : 读描述字集
writeset : 写描述字集
exceptset : 异常条件的描述字集
timeout : 等待时间
readset, writeset和exceptset
让内核测试读、写和异常条件所需的描述字。
为这三个参数的每一个指定一个或多个描述字
描述字集,是一个整数数组,每个数中的每一位对应一个描述字。
数组的第一个元素对应于描述字0-31,
数组户的第二个元素对应于描述字32—63。
poll 函数
原型:
int poll (struct pollfd *fdarray, unsigned long nfds, int timeout )
第一个参数是指向结构数组第一个元素的指针:
struct pollfd{
int fd; // descriptor
short events // events of interest on fd
short revents // events that occured on fd
}
第二个参数是套接字个数,第三个参数是等待时间 .
一个使用select的回射函数
void str_cli(file*fp,int sockfd)
{
int maxfdp1;
fdset rset;
char sendline[maxline],receive[maxline];
FD_ZERO(&rset);
for(;;)
{
fd_set(fileno(fp),&rset);
fd_set(sockfd,&rset);
maxfdp1=max(fileno(fp),sockfd)+1;
select(maxdp1,&rset,null,null,null);
if(fd_isset(sockfd,&rset){//套接口可读
...
}
if(fd_isset(fileno(fp),,&rset)标准输入可读
{
。。。。
}
}
}