网络IO与select,poll,epoll

        网络 IO,会涉及到两个阶段。阶段1:涉及到用户空间调用 IO 的进程或者线程,阶段2:涉及到内核空间的内核系统。比如发生 IO 操作 read 时,它会经历两个阶段:等待数据准备就绪,将数据从内核拷贝到进程或者线程中。

        对应于两个阶段的不同情况,出现了以下4种IO。

1.阻塞 IO(blocking IO)

        用于通信的socket在两个阶段都被阻塞。在第一阶段会因为等待网络数据而被阻塞,在第二阶段,因为内核数据还没准备好,也会被阻塞。这种IO可以用来编写简单的一问一答服务器。即客户端发送一个请求,服务器回答一个请求。

所谓阻塞型接口是指系统调用(一般是 IO 接口) 不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。除非特别指定,几乎所有的 IO 接口 ( 包括 socket 接口 ) 都是阻塞型的。

        这种阻塞型服务器的问题就是,在执行某一个请求的时候,因为会被阻塞,所以其无法响应其它请求,一个简单的处理方法是,通过使用多进程或者多线程来为每一个客户端服务,如一个简单的多线程web服务器

具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的 CPU 资源,譬如需要进行大规模或长时间的数据运算或文件访问, 则进程较为安全。通常,使用 pthread_create ()创建新线程,fork()创建新进程。

        但是使用多线程或者多进程存在如下问题:1占据系统资源,2降低系统对外界响应效率,3线程与进程容易进入假死状态。

        通过使用线程池和连接池可以减少系统开销。 “线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”是维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。

        但线程池和连接池都有其容量上限,超出范围后会面临同样的问题。

多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

2.非阻塞 IO(non-blocking IO)

Linux 下,可以通过设置 socket 使其变为非阻塞。比如对于一个非阻塞socket执行读操作,当系统内核数据没有准备好时,读操作直接返回并不会阻塞用户的进程或者线程。在非阻塞式 IO 中,用户进程其实是需要不断的主动询问内核数据是否准备好。

在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义:

1.返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;

2.recv() 返回 0,表示连接已经正常断开;

3.recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;

4.recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。

非阻塞的接口相比于阻塞型接口在于前者在被调用之后立即返回。

 使用fcntl( fd, F_SETFL, O_NONBLOCK )可以将某个fd设置为非阻塞。

        通过在一个循环里使用非阻塞的recv,就可以在一个线程里实现对多个客户端请求的数据接收。但是,这种循环的方式会造成CPU资源的浪费,因为CPU时间浪费在循环查询数据是否准备好了。

        基于此,通过使用多路复用IO可以将查询是否有某个客户端数据准备好这件事变得高效。

3.多路复用 IO

        复用,顾名思义,就是一物多用。在这里,select函数可以在一个进程里同时处理多个网络连接的IO。

select这个函数会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。

         用read操作举例:当用户进程调用了 select,整个进程会被阻塞,同时,内核会“监视”所 有 select 负责的 socket,当任何一个 socket 中的数据准备完毕,select 就会返回。此时用户进程再调用 read 操作,将数据从内核拷贝到用户进程。

        select 函数,该函数用于探测多个文件句柄的状态变化。

        FD_ZERO(int fd, fd_set* fds)   //清0

        FD_SET(int fd, fd_set* fds)      //置位

        FD_ISSET(int fd, fd_set* fds)  //判断是否有状态变化

        FD_CLR(int fd, fd_set* fds)       //清理

        int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)   //readfds、writefds 和 exceptfds 同时作为输入参数和输出参数,输入时,这些参数携带需要查询的信息, 返回后,这些参数携带结果。

        使用select可以实现基于事件驱动的服务器。基本思路是,服务器维护readfds、writefds 和 exceptfds这几个参数,通过readfds获取客户端的请求(包括建立连接和请求数据),通过FD_ISSET()检查是哪一个或几个客户端有请求,然后进行处理。通过使用writefds,进行类似的发送处理。整个程序看起来像是在一个进程里的死循环。

        上述模型存在如下问题:

        1.由于select接口是轮询处理的,效率不如epoll这些接口。

        2.事件探测和事件响应混在一起,因为是串行,某一个客户端的低效,会影响整体运行效率。

        为了解决这些问题,出现了异步IO。

4.异步 IO(Asynchronous I/O)

        异步IO不会阻塞用户进程。内核通过使用信号机制通知用户进程处理结果。之前介绍的阻塞、非阻塞和多路服用都属于同步IO,他们在某种意义上都会阻塞进程。

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值