PPC的C/C++和人工智能学习笔记
每一篇学习笔记,都只是为了更好地掌握和理解

linux开发(11)_网络编程(6)select服务端

前面用简易的tcp模型写了个服务端,今天用IO复用模型中的select来实现同样的事情。

 

简易模型在处理连接的时候,假如是一个线程,那么同时就只能处理一个连接,对于同时有多个连接的处理(长连接,每次要收发很多次数据),则采用另外开线程的方法,而这种方式对于同时有非常多的连接的时候,是不合适的,过多的线程会造成线程上下文切换的巨大开销。

 

而使用select网络模型,则可以在一个线程中通过轮询的方式同时处理最多1024个连接。(实际的大小,可以用sizeof(fd_set)*8来观察,我机器上是1024。)

 

select模型的相关函数与宏:

#include <sys/select.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,

                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);

       int  FD_ISSET(int fd, fd_set *set);

       void FD_SET(int fd, fd_set *set);

       void FD_ZERO(fd_set *set);

 

fd_set是一个结构体,它由16个8字节的long int组成,所以它一共有16*8*8 = 1024位。每一位用来表示一个文件描述符的状态(0表示没有数据,1表示有数据到来)。由此定义,也可以得知select能处理的文件描述符最多是1024个。

(1)fd_set fds; FD_ZERO(&fds); 表示fds中的每一位状态清0。零初始化fds

(2)若fd=4,执行FD_SET(fd,&fds);表示fds的第4位设1。将fd加入fds

(3)若fd=4;执行FD_CLR(fd,&fds);则将本来第4位是1的设为0。从fds中清除fd。

(4)select()函数的第一个参数是:最大文件描述符+1,因此执行select(5, &fds, NULL, NULL, NULL)线程将阻塞等待。

(5)若fds中被设置为1的文件描述符上发生可读事件,则select返回,返回值是有多少个文件描述符上有可读事件。同时,fds中有可读事件发生的对应的位被设为1,而没有可读事件发生的对应位被设为0。(所以:每次select前,都要重新设置fds。)

(6)FD_ISSET(fd,&fds),判断fd是否有事件,返回1表示有,返回0表示没有

 

select的参数:

(1)int nfds第一个参数,指集合fd_set中所有文件描述符的范围,即所有文件描述符的最大值加1。由于fd_set是用类似位图的形式来存储文件描述符的集合,猜测内核是通过从0到max_fd-1的轮询方式来检测文件描述符上是否有事件发生的,所以指定该值可以减少内核的轮询次数(而不是每次都轮询到1024)。

(2)fd_set*readfds第二个参数,指向fd_set结构的指针,该集合中的文件描述符,是要监视这些文件描述符的可读事件发生的,也就是说是否可以从这些文件中读取数据了。如果这个集合中有一个或多个文件可读,select就会返回一个大于0的值,表示有多少个文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。该参数传入NULL的话,表示不关心任何文件的可读事件。

(3)fd_set*writefds第三个参数,类似第二个参数,不同的是该集合中存放的是需要监视可写事件的文件描述符。

(4)fd_set*errorfds第四个参数,类似第二、三参数,用来监视文件错误异常文件。

(5)struct timeval* timeout是select的超时时间(timeout),1、传入NULL表示无限等待,就是将select置于阻塞状态,直到被监视的fd_set中有事件发生才返回;2、传入一个设置为0的timeval结构体,就是不再等待,相当于是一个异步函数(非阻塞),不管fd_set中的文件描述符是否有事件发生,都立刻返回继续执行,文件无变化返回0,有变化返回变化的个数;3、传入一个值大于0的timeval结构体,这就是等待的超时时间,即 select在timeout时间内阻塞,timeout时间之内有事件到来就返回了,返回值是变化的个数,否则在timeout时间到一定返回,返回值是0。

 

select函数返回值:

(1)正常情况返回一个正数,该值是监视的三个fd_set中所有有事件发生数量之和。

(2)超时返回0。

(3)发生错误返回-1。

注意:信号可能会打断select,此时返回值为-1,errno == EINTR。所以这个情况要加以判断。

 

使用select模型的服务端程序:(对应前面的测试用客户端)

不同于前面的一些修改:

 

同步、异步模式:

在简易tcp服务端中,我们使用的是同步模式+timeout,而在select模型下,我们将采用异步模式,以求更加有效地避免因为阻塞而引起的性能问题。

 

收发函数:

对于收、发指定长度的数据,在使用recv和send的时候,有时候并不能一次性地完成,在简易tcp服务端程序中,我们是使用了一个循环,一直收或者一直发,直到收到的长度(发生的长度)之和等于想收(发)的长度,以此来确保收和发的完整性。

每次select有读事件的时候,调用recv函数,假如没有读到预期的数据长度,则在一个地方记录好下次需要读的预期长度和存放数据的起始指针。写也是同样如此,唯一不同的是没有写完的fd需要添加到写fd_set中,写完则清除。

 

连接的关闭:

简易tcp服务端,是在完成指定任务后就close(socket)。

在select模型中,将采用收到对端关闭消息后才关闭socket(read返回0)。

 

一个问题:

当连接数超过1021(1024-3)的时候,会出现什么样的错误?

 

 

 

 

 

 

(2017-11-24 www.vsppc.com)

学习笔记未经允许不得转载:PPC的C/C++和人工智能学习笔记 » linux开发(11)_网络编程(6)select服务端

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址