Unix网络编程 - 套接字联⽹
1 简介¶
2 传输层¶
3 套接字编程简介¶
4 基本TCP套接字编程¶
5 TCP客户/服务器程序示例¶
6 I/O复用¶
IO模型¶
Unix下可用的5种I/O模型的基本区别:
阻塞I/O
(blocking IO)非阻塞I/O
(nonblocking IO)I/O多路复用
( IO multiplexing)信号驱动I/O
( signal driven IO)异步I/O
(asynchronous IO)
对于⼀个套接字上的输⼊操作,第⼀步通常涉及等待数据从⽹络中 到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第⼆步 就是把数据从内核缓冲区复制到应⽤进程缓冲区。
阻塞IO¶
默认情形下,所有套接字都是阻塞的。以 数据报套接字作为例⼦,一个典型的读操作流程大概是这样:
当用户进程调用了 recvfrom
这个系统调用, kernel
就开始了 IO 的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的 UDP
包。这个时候 kernel
就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当 kernel
一直等到数据准备好了,它就会将数据从 kernel
中拷贝到用户内存,然后 kernel
返回结果,用户进程才解除 block
的状态,重新运行起来。
blocking IO的特点就是在IO执行的两个阶段都被block了
非阻塞 I/O¶
Linux
下,可以通过设置 socket
使其变为 non-blocking
。当对一个 non-blocking socket
执行读操作时,流程是这个样子:
当用户进程发出 read
操作时,如果 kernel
中的数据还没有准备好,那么它并不会 block
用户进程,而是立刻返回一个 error
。从用户进程角度讲 ,它发起一个 read
操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error
时,它就知道数据还没有准备好,于是它可以再次发送 read
操作。一旦 kernel
中的数据准备好了,并且又再次收到了用户进程的 system call
,那么它马上就将数据拷贝到了用户内存,然后返回。
nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有
IO多路复用¶
IO多路复用就是我们说的 select,poll,epoll
,有些地方也称这种IO方式为 event driven IO
。select/epoll
的好处就在于单个 process
就可以同时处理多个网络连接的 IO 。它的基本原理就是 select,poll,epoll
这个 function
会不断的轮询所负责的所有 socket
,当某个 socket
有数据到达了,就通知用户进程。
当用户进程调用了 select,那么整个进程会被 block,而同时, kernel
会监视所有 select
负责的 socket
,当任何一个 socket
中的数据准备好了, select
就会返回。这个时候用户进程再调用 read
操作,将数据从 kernel
拷贝到用户进程。
I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,
select()
函数就可以返回。
这个图和 blocking IO
的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个 system call
(select
和 recvfrom
),而 blocking IO
只调用了一个 system call
(recvfrom
)。但是,用 select
的优势在于它可以同时处理多个 connection
。
所以,如果处理的连接数不是很高的话,使用 select/epoll
的 web server
不一定比使用 multi-threading + blocking IO
的 web server
性能更好,可能延迟还更大。select/epoll
的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
在IO多路复用实际使用中,对于每一个socket,一般都设置成为 non-blocking
,但是,如上图所示,整个用户的 process
其实是一直被block的。只不过 process
是被 select
这个函数 block
,而不是被 socket IO
给 block
。
信号驱动I/O¶
异步 I/O¶
用户进程发起 read
操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel
的角度,当它受到一个 asynchronous read
之后,首先它会立刻返回,所以不会对用户进程产生任何 block
。然后,kernel
会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel
会给用户进程发送一个 signal
,告诉它 read
操作完成了。
不同I/O模型对比¶
https://rickhw.github.io/2019/02/27/ComputerScience/IO-Models/
blocking vs non-blocking:
- 调用
blocking IO
会一直block
住对应的进程直到操作完成, non-blocking IO
在kernel
还准备数据的情况下会立刻返回。
synchronous IO vs asynchronous IO:
在说明synchronous IO
和asynchronous IO
的区别之前,需要先给出两者的定义。 POSIX
的定义是这样子的:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于 synchronous IO
做 IO operation
的时候会将 process
阻塞。按照这个定义,之前所述的 blocking IO,non-blocking IO,IO multiplexing
都属于 synchronous IO
。
有人会说,non-blocking IO
并没有被 block
啊。这里有个非常 狡猾 的地方,定义中所指的 IO operation
是指真实的 IO 操作,就是例子中的 recvfrom
这个 system call
。non-blocking IO
在执行 recvfrom
这个 system call
的时候,如果 kernel
的数据没有准备好,这时候不会 block
进程。但是,当 kernel
中数据准备好的时候,recvfrom
会将数据从 kernel
拷贝到用户内存中,这个时候进程是被 block
了,在这段时间内,进程是被 block
的。
而 asynchronous IO
则不一样,当进程发起 IO
操作之后,就直接返回再也不理睬了,直到 kernel
发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被 block
。
-
Linux IO模式及 select、poll、epoll详解, https://segmentfault.com/a/1190000003063859 ↩