目录
阻塞IO
非阻塞 IO
select
epoll
总结一下 。
阻塞IO服务端为了处理客户端的连接和请求的数据,写了如下代码 。
这段代码会执行得磕磕绊绊,就像这样 。
文章插图
可以看到,服务端的线程阻塞在了两个地方,一个是 accept 函数,一个是 read 函数 。
如果再把 read 函数的细节展开 , 我们会发现其阻塞在了两个阶段 。
文章插图
这就是传统的阻塞 IO 。
整体流程如下图 。
文章插图
所以,如果这个连接的客户端一直不发数据,那么服务端线程将会一直阻塞在 read 函数上不返回,也无法接受其他客户端连接 。
这肯定是不行的 。
非阻塞 IO为了解决上面的问题,其关键在于改造这个 read 函数 。
有一种聪明的办法是,每次都创建一个新的进程或线程,去调用 read 函数,并做业务处理 。
- while(1) {
- connfd = accept(listenfd);// 阻塞建立连接
- pthread_create(doWork);// 创建一个新的线程
- }
- void doWork() {
- int n = read(connfd, buf);// 阻塞读数据
- doSomeThing(buf);// 利用读到的数据做些什么
- close(connfd);// 关闭连接,循环等待下一个连接
- }
这样,当给一个客户端建立好连接后,就可以立刻等待新的客户端连接 , 而不用阻塞在原客户端的 read 请求上 。文章插图
不过 , 这不叫非阻塞 IO,只不过用了多线程的手段使得主线程没有卡在 read 函数上不往下走罢了 。操作系统为我们提供的 read 函数仍然是阻塞的 。
所以真正的非阻塞 IO,不能是通过我们用户层的小把戏 , 而是要恳请操作系统为我们提供一个非阻塞的 read 函数 。
这个 read 函数的效果是 , 如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待 。
操作系统提供了这样的功能,只需要在调用 read 前,将文件描述符设置为非阻塞即可 。
- fcntl(connfd, F_SETFL, O_NONBLOCK);
- int n = read(connfd, buffer) != SUCCESS);
这样 , 就需要用户线程循环调用 read , 直到返回值不为 -1,再开始处理业务 。文章插图
这里我们注意到一个细节 。
非阻塞的 read,指的是在数据到达前,即数据还未到达网卡,或者到达网卡但还没有拷贝到内核缓冲区之前,这个阶段是非阻塞的 。
当数据已到达内核缓冲区,此时调用 read 函数仍然是阻塞的,需要等待数据从内核缓冲区拷贝到用户缓冲区,才能返回 。
整体流程如下图
文章插图
也就是说这不是真正意义上的非阻塞IO 。
IO 多路复用
为每个客户端创建一个线程 , 服务器端的线程资源很容易被耗光 。
文章插图
当然还有个聪明的办法 , 我们可以每 accept 一个客户端连接后 , 将这个文件描述符(connfd)放到一个数组里 。
fdlist.add(connfd);
然后弄一个新的线程去不断遍历这个数组,调用每一个元素的非阻塞 read 方法 。- while(1) {
- for(fd <-- fdlist) {
- if(read(fd) != -1) {
- doSomeThing();
- }
- }
- }
这样,我们就成功用一个线程处理了多个客户端连接 。推荐阅读
- iPhone8怎么刷机(苹果x强制恢复出厂)
- 超人怎么死的(超人怎么复活)
- 记一次多个Java Agent同时使用的类增强冲突问题及分析
- 光遇啵啵先祖裤子怎么搭配
- fastposter v2.10.0 简单易用的海报生成器
- 电脑鼠标箭头不动怎么解决(一键修复usb无法识别鼠标)
- 鼠标突然不动了怎么解决(鼠标失灵一秒恢复)
- 二代火影是在木叶几年死的(火影多少集复活木叶四影)
- .Net 7里的函数.Ctor和.CCtor是干啥用的呢?你知道吗
- 原神8.13复原械画部件之二图文攻略