一、多路复用
在实际编程中,经常会用到如下过程:从一个缓冲区读出数据,然后再将读到的数据写到另外一个缓冲区中。在这种情况ixa,如果使用阻塞I/O,而且又必须读两个描述符那又会怎么样呢?如果我们仍旧使用阻塞I/O,那么就可能常时间应为这个描述符上没有数据而一直阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时的处理,在这样的情况发生时,应用程序的性能显然会大大受到影响,所以为了处理这种情况也就需要另一种技术了。
1、多路复用的基本原理
通常的做法是判断一下哪件事情所需要满足的条件先发生。如果某一个事情所要满足的条件先发生了,那我们去就去先处理这个事情。这样当然会提高办事的效率。
同样,在解决上面的I/O问题是,也可以采用同样的处理方式。处理这种特殊问题的一种方法是:设置两个进程,每个进程处理一条数据通路。如果使用两个进程,则可使每个进程都执行阻塞read。但是也产生了这两个进程间相互配额和问题。如果紫禁城读到了缓冲区结尾,那么该紫禁城终止,然后父进程接收到SIGCHLD信号。但是,如若父进程终止,那么父进程应通知子进程停止工作。为此我们可以使用一个信号。这使程序变得更加复杂。
另一个方式是仍旧使用一个进程执行该程序,但调用非阻塞I/O读书数据,其基本思想是,将两个输入面舒服都设置为阻塞的,对第一个描述符发一个read。如果该输入上有数据,则读数据并处理他。如果没有数据read立即返回。然后对第二个描述符做同样的处理。在此之后,等待若干秒再读第一个描述符,这种形式的循环成为轮询。这种反的不足之处是浪费CPU时间。大多是时间 实际上是无数据可读,但是仍不断反复执行read,这浪费了CPU时间。在每次循环后要等多长时间再执行下一轮询也是很难确定的。轮询技术在支持非阻塞I/O的系统上都可使用,但在多任务系统中应该避免使用。
一种比较好的技术就是采用平常生活中会使用的方法:使用I/O多路复用。其基本思想跟处理平常事物的一致;先构造一张有关描述符的表,然后调用一个函数,他要到这些描述符中的一个以准备好进行I/O时才返回。在返回时,他告诉进程哪一个描述符已准备好可以进行I/O。
2、select 函数
select 函数就是完成这一功能的系统调用。调用格式为:
#include <sys/types>
#include <sys/time.h>
#include <unistd.h>
int select(int n,fd_set *readfds,fd_set *writefds,fd_set * exceptfds,struct timeval *timeout);
该系统调用会告诉系统核:
$我们所关心的描述符
$对每个描述符所关心的条件:读一个给定的描述符、写一个给定的描述符或当一个描述符上发生一场条件
$希望等待多长时间:永远等待、等待一个固定量时间或完全不等待。从select 调用返回后,系统告诉我们:
$已准备好的描述符的数量:
$哪一个描述符已经准备好、写、或异常条件。
select 函数中的3个参数中的任何一个都可以是空指针,当某一位为空指针时表示对相应条件并不关系。这时实际上select函数听偶了一定时间精度的定时,并且这样的定时可以精确到毫秒级。例如,下面程序可以实现1.5秒的定时:
struct timeval tv;
tv.tv_sec=1;
tv.tv_userc=500;
select(0,NULL,NULL,NULL,&tv);
select 应用实例
服务器端的程序如下
#include <stdio.h>
#include <sys/types.h>
#include <sys.socket.h>
#include <sys/un.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#define SOCKETNAME "mysocket"
int main(void){
char buf[1024]
int s;
int ns;
int ns2;
int len;
int maxfd;
int nread;
int nready;
struct sockaddr_name;
fd_set fds;
unlink(SOCKETNAME);
if((s==scotet(AF_UNIX,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
memset(&name,0,sizeof(struct sockaddr_un));
name.sun_family=AF_UNIX;
strcpy(name.sun_path,SOCKETNAME);
len=sizeof(name.sun_family)+strlen(name.sun_path);
if(bind(s,(struct sockaddr*)&name,len)<0){
perror("bind");
exit(1);
}
if(listen(s,5)<0){
perror("listen");
exit(1);
}
if((ns=accept(s),(stuct sockaddr *)&name,&len))<0){
perror("accept");
exit(1);
}
if((ns2=accept(s,(struct sockaddr *)&name,&len))<0);
perror("accept");
exit(1);
}
maxfd=(ns>ns2?ns:ns2)+1;
while(1){
FD_ZERO(&fds;
FD_SET(ns,&fds);
FD_SET(ns2,&fds);
nready=select(maxfd,&fds,(fd_set*)0,(ds_set*)0,(struct timeval*)0);
if(FD_ISSET(ns,&fds)){
nread=recv(ns,buf,sizeof(buf),0);
if(nread<1){
close(ns);
close(ns2);
exit(0);
}
send(ns2,buf,nread,0);
}
if(FD_ISSET(ns2,fds)){
nread=recv(ns2,buf,sizeof(buf),0);
if(nread<1){
close(ns);
close(ns2);
exit(0);
}
send(ns,buf,nread,0);
}
)
}
客户端程序
#include <sys.types.h>
#include <sys.socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <string.h>
#include <fcntl.h>
#define SOCKETNAME "mysocket"
int main(void)
{
int s;
int len;
int nared;
int maxfd;
char buf[1024];
fd_ser fds;
struct sockadd_un_name;
if((s=socket(AF_UNIX,SOCK_STREAM,0))<0)
{
peroor("socket!\n");
exit(1);
}
// create the address of the server.
memset(&name,0,siezof(struct sockadd_un));
name.sun_family=AF_UNIX;
strcpy(name.sun_path,SOCKETNAME);
len=sizeof(name.sun_family)+strlen(name.sun_path);
//connect ot the server
if(connect(s,(struct sockaddr *)&name,len)<0)
{
perrpr("connect");
exit(1);
}
maxfd=s+1;
while(1)
{
FD_ZERO(&fds);
FD_SET(s,&fds);
FD_SET(0,&fds);
//wait for some input
nready=select(maxfd,&fds,(fd_set*)0,(fd_set *)0,(struct timeval*)0);
//if either device has some input; read it and copy it to the other
if(FD_ISSET(s,&fds)){
nread=recv(s,buf,sizeof(buf),0);
if(nread<1){close(s);exit(0);}
write(1,buf,nread);
}
if(FD_ISSET(0,&fds))
{
nread =read(0,buf,sizeof(buf));
if(nread<1){close(s); exit(0);}
send(s,buf,nread,0);
}// end if
}//end while(1)
}//end main
编译程序