Python网络编程进阶
纸上得来终觉浅,绝知此事要躬行。

使用socket进行TCP和UDP编写的程序,其实只能说是一种玩具。因为服务端一次只能接收一个客户端的连接,再多的客户端连接会被阻塞掉。深层原因是,代码中调用了accept、recv、send等方法,都会发生阻塞,导致无法响应多个客户端连接。而现实环境中大都是多客户端甚至多服务端,那怎么让服务端同时响应多个客户端的请求呢?
1. 编程基础知识
真正的程序 = I/O 多路复用 + setblocking 非阻塞模式
能够实现的基础原因
- 操作系统提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1之类无用功。写操作也类似。
- 操作系统的这个功能通过支持I/O多路复用的系统调用来使用,这些系统调用的函数都可以同时监视多个文件描述符的读写就绪状况,这样,多个文件描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的复用指的是复用同一个线程。
I/O 多路复用适用场景
- [1] 当客户需要处理多个描述符时,一般是交互式输入和网络套接字,那就必须使用I/O多路复用技术。
- [2] 当一个客户同时处理多个套接字时,而这种情况是可能的,一般就要使用I/O多路复用技术,虽然很少出现。
- [3] 如果一个 TCP 服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O多路复用技术。
- [4] 如果一个服务器即要处理 TCP,又要处理 UDP,一般就要使用I/O多路复用技术。
- [5] 如果一个服务器要处理多个服务或多个协议,一般就要使用I/O多路复用技术。
I/O 多路复用核心优势
- I/O多路复用和多进程/多线程技术相比,其最大的优势就是系统开销比较小。因为系统不必创建进程和线程,也不必维护这些进程和线程,通过复用机制将一个进程或线程多次重复使用,从而大大减小了额外的开销。
支持 I/O 多路复用的系统调用
- select
- select支持的文件描述符数量太小了,默认为1024,但可以自行调整,超过1024可能导致性能下降。
- 每次调用select都需要把fd(文件句柄)集合从用户态拷贝到内核态,同时每次调用select都需要在内核遍历传递进来的所有fd,这个过程在fd很多的时候性能消耗很大。
- poll
- 只是解决了文件描述符数量的限制
- epoll
- Linux系统特有的支持I/O多路复用的系统调用
- 支持一个进程打开大数量的 socket 描述符
- I/O效率不随fd数量的增长而线性下降
- 使用mmap加速内核与用户空间的消息传递
- 支持边缘触发和水平触发
- kqueue
- FreeBSD系统特有的支持I/O多路复用的系统调用