Linux网络编程读书笔记(5)

来源:互联网 发布:java api接口管理系统 编辑:程序博客网 时间:2024/05/29 17:10

第五章  无堵塞套接字和单进程轮询服务器

·5.1 无堵塞套接字

       堵塞套接字在等待输入/输出时会进入睡眠,不能继续其他的操作。在并发服务器模式下这一缺点并不明显,但在一些复杂应用中可能需要在单进程中为多个连接服务,这时堵塞套接字会大大降低效率。另外,进程可能一直被堵塞。比如服务器端崩溃,而客户端并不知道,此时客户端进程将一直堵塞。

       无堵塞套接字会对读、写、建立连接、接收连接过程产生影响。总的来说就是不等待所有资源到齐,而立即操作并返回,这点在和处理无堵塞套接字上有一些区别。如果本已堵塞而由于使用无堵塞套接字,那么errno将返回EWOULDBLOCK,通过下面语句可以判断:

ret = accept(...);

if (ret < 0 & errno != EWOULDBLOCK) //如果错误返回并且错误原因不是无堵塞

 

       无堵塞套接字的两种实现:

int (flags = fcntl(sock_fd, F_GETFL, 0) < 0) error_proc();

flags |= O_NONBLOCK;

if (fcntl(sock_fd, F_SETFL, flags) < 0) error_proc();  //这种方法是POSIX标准定义方式

 

int b_on = 1;  //ioctl函数中使用FIONBIO命令

ioctl(sock_fd, FIONBIO,&b_on);

 

·5.2 单进程轮询服务器模式

make_null(serv_slot, maxlen);  //serv_slot[]是连接套接字描述符数组,本进程为其提供服务

listen(listen_fd, MAXSIZE);   //建立倾听套接字

do{

//从完全倾听队列中接收一个连接套接字描述符

       conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, sizeof(cli_addr));

       if (conn_fd < 0 && errno != EWOULDBLOCK)

error_proc();                     //错误处理

       else  if (conn_fd >= 0)                //接收到新的连接套接字描述符

              create_new_connect(conn_fd, serv_slot, &maxlen);   //建立新连接

for (i = 0; i < maxlen; ++i)      //0maxlen都是有效连接,进程轮流为其服务

        serve_for(serv_slot[i]);             //本进程为第i个连接服务

} while (continue);

       使用单进程轮询服务器模式仍然无法避免客户端的某些意外(比如非正常断线)或恶意行为造成失效,而且如果客户数量增加,服务器端的相应时延也会加大。因此我们仍然使用并发服务器模式来提供并行的服务,因为一个服务器子进程失效不会影响到其他进程的工作。

 

 

第六章  带外数据与多路复用、信号驱动的输入/输出模型

·6.1 多路复用的输入/输出模型

       多路复用的概念:进程不是主动询问套接字情况,而是希望对监视的套接字向系统登记,而后采用被动的态度等待。当监视的套接字上发生了事件,进程去检查发生的状况然后做相应的处理。在这种工作方式下,进程是在已经知道在套接字上发生了事件才去检测,在没有发生事件的时候进入睡眠状态。

 

头文件:<sys/time.h> <unistd.h>  [<signal.h>(pselect使用)]         

主要函数:

int select (int maxfd, fd_set *rdset, fd_set *wrset, fdset *exset, struct timeval *timeout);

[maxfd是需要监视的最大文件描述符值+1,即系统监视从0maxfd-1的文件描述符;

rdset, wrset, exset是对应需要检测的可读、可写和异常文件描述符集合;

timeout内没有发生事件,函数返回0]

 

int pselect(int maxfd, fd_set *rdset, fd_set *wrset, fdset *exset, struct timespec *timeout,const sigset_t sigmask);  //POSIX中对select函数的增强,参数sigmask是执行后对堵塞信号恢复

 

文件描述符集合:

FD_ZERO(fd_set *fdset);            //清空初始化

FD_SET(int fd, fd_set *fdset);        //增加

FD_CLR(int fd, fd_set *fdset);        //删除

FD_ISSET(int fd, fd_set *fdset);       //判断包含

 

·套接字的读、写和异常就绪条件

       读就绪:倾听套接字完全连接队列建立新连接;连接套接字的读缓冲区超过读下限、读管道关闭和套接字异常。

       写就绪:连接套接字的写缓冲区空闲小于某下限、写管道被关闭和套接字异常。

       异常就绪:套接字上到达外带数据。异常就绪连带触发读、写就绪。

上面列出部分常用就绪条件,具体参考帮助手册。

 

基本用法:

FD_ZERO(&r_set);               //初始化

FD_SET(listen_fd, &r_set);         //加入可读文件描述符集合

ret = select(listen_fd+1, &r_set, NULL, NULL, NULL); //对倾听套接字进行就绪判断

 

 

·6.2 信号驱动的输入/输出模型

       信号驱动通常用于接收紧急数据。进程先向系统登记,然后系统检测到数据到达后会向接收者发生SIGIO信号,然后接收者在信号处理器中接收数据。这种方式通常用在接收紧急的控制数据场合。

 

数据接收者设置:

#include <fcntl.h>

int fcntl(int fd, int cmd,...);  

//使用命令F_SETOWN,第三个参数如果是正整数表示进程号,负整数表示进程组接收

 

 

·6.3 系统I/O模型的总结

       本书讲述了“堵塞方式、非堵塞方式、多路复用和信号驱动”四种I/O模型。

       1 堵塞方式:

              广泛使用在并发服务器上,当套接字不满足操作条件立即堵塞等待资源。

       2 非堵塞方式:

              广泛使用在单进程轮询服务器上,浪费较大CPU资源使用场合较少。

       3 多路复用方式:

              广泛使用在单进程进行多客户端服务上,比非堵塞方式在轮询中节约CPU时间。

       4 信号驱动方式:

              广泛使用在接收紧急数据场合。

 

·6.4 带外数据的接收和发送

       带外数据就是指在正常数据流信道之外传输的数据,通常用在对远端进程的同步和控制。它和信号驱动方式几乎相同,但是发送的是SIGURG信号,不是SIGIO

 

头文件:<sys/types.h>, <sys/socket.h>

主要函数:

int send(int sockfd, void *buf, int len, int flags);  //使用MSG_OOB控制选项发送带外数据

int recv(int sockfd, void *buf, int len, int flags);  //使用MSG_OOB控制选项接收带外数据

 

       带外数据一次只允许发送一个字节,如send(sock_fd, “bc”, 2, MSG_OOB)TCP只认为最后一个是带外数据,之前都是普通数据。在特殊情况下,带外数据包优先被接收方接受。

接收方在缺省情况下(使用ioctl函数可以改变)使用一个字节的外带数据缓冲区接收外带数据,并且外带数据段和普通套接字数据段字符集不同,可以区分外带数据和普通数据。     

如果接收方收到多个带外数据段,TCP会和先前一次收到的数据段中数据做比较,如果其值相同则认为它们是同个带外数据段。由于发送方可能发送多个外带数据段,接收外带数据是必须做容错处理。

接收方同一时刻只允许有一个字节的外带数据,先到者如果没有被及时处理,那么任何后来的外带数据段都将覆盖它。带外数据被覆盖后成为普通数据。

       收到外带数据将触发异常就绪。直到读指针大于带外数据标示(紧急)指针后解除异常。

       服务器端接收外带数据可以使用:

1 多路复用方式,要点:

检测异常就绪和读就绪套接字先后顺序不同,结果也不同。

2 异步信号驱动方式,要点:

设计SIGURG信号处理器,处理前后注意屏蔽/堵塞信号。

3 检测带外数据标记方式,要点:

       //套接字设置成SO_OOBINLINE,即外带数据看成普通数据存放,on=1

  setsockopt(conn_fd,SOL_SOCKET,SO_OOBINLINE,&on,sizeof(on));

  ioctrl(conn_fd, SIOCATMARK,&n_data); //检测读指针是否和带外数据标示指针重合

  if (n_data == 1) //带外数据到达

 

注意:被覆盖的带外数据将保留继续保留在读缓冲区里,而后当成普通数据读入。如果使用过的带外数据没有及时得从缓冲区里删除,该带外数据可能会被当场普通数据读入,如sleep()系统调用可能导致这类需要紧急处理的过程产生诡异的行为!