IO模型是操作系统通用的概念,主要分为
和
- 阻塞 (blocking)
、
- 非阻塞(
- non
- -
- blocking
- )
和
- 同步 (synchronous)
。
- 异步 (asynchronous)
列出了几种不同IO模型的特点和区别,主要有:
- unix网络编程卷一
这些区别主要体现在对
对象的
- socket
或
- read
操作所经过的两个阶段:
- write
绝大多数操作系统默认情况下的
都是
- socket
的,除非单独设置了
- blocking
属性,处理
- non - blocking
的流程如下图:
- recvfrom
用户进程执行
操作首先会执行第一个阶段:等待数据准备好,紧着这执行第二个操作:从内核拷贝数据到进程中。这两个操作都是阻塞的。在
- sock
- .
- recvfrom
- ()
中我们建立一个服务端的
- python
:
- socket
- #/bin/bash/python3:
- import socket
- server
- =
- socket
- .
- socket
- ()
- # 默认阻塞
- # server.setblocking(True)
- # 绑定到本地5464端口
- server
- .
- bind
- ((
- '0.0.0.0'
- ,
- 5464
- ))
- # 开启监听
- server
- .
- listen
- (
- 100
- )
- def
- handle
- (
- conn
- ):
- try:
- # 会阻塞直到有数据到来
- chunk
- =
- conn
- .
- recv
- (
- 1024
- )
- (
- repr
- (
- chunk
- ))
- # 会阻塞直到数据发送完毕
- conn
- .
- send
- (
- "Server:Good Bye."
- .
- encode
- (
- "utf8"
- ))
- conn
- .
- close
- ()
- except
- socket
- .
- error
- :
- pass
- def loop_forever():
- while
- True
- :
- try:
- # 会阻塞直到有连接到来
- client
- ,
- addr
- =
- server
- .
- accept
- ()
- (
- addr
- )
- handle
- (
- client
- )
- except
- socket
- .
- error
- :
- pass
- if
- __name__
- ==
- '__main__'
- :
- loop_forever()
代码中所有涉及到
、
- accept()
、
- read()
等操作都会阻塞线程直到有相应的数据到来。
- write()
非阻塞IO需要对
单独设置
- socket
属性:
- non - blocking
- import socket
- server
- =
- socket
- .
- socket
- ()
- server
- .
- bind
- ((
- "0.0.0.0"
- ,
- 5464
- ))
- # 设置为非阻塞
- server
- .
- setblocking
- (
- False
- )
- server
- .
- listen
- (
- 100
- )
- def
- handle
- (
- conn
- ):
- try:
- 不会阻塞,没数据时立即返回并抛出
- BlockingIOError
- 异常
- chunk
- =
- conn
- .
- recv
- (
- 1024
- )
- (
- repr
- (
- chunk
- ))
- conn
- .
- send
- (
- "Server:Good Bye."
- .
- encode
- (
- "utf8"
- ))
- conn
- .
- close
- ()
- except
- BlockingIOError
- :
- pass
- def loop_forever():
- while
- True
- :
- try:
- # 不会阻塞,没数据时立即返回并抛出BlockingIOError异常
- client
- ,
- addr
- =
- server
- .
- accept
- ()
- (
- addr
- )
- handle
- (
- client
- )
- except
- BlockingIOError
- :
- pass
- if
- __name__
- ==
- '__main__'
- :
- loop_forever()
跟
代码主要的区别在于:
- 阻塞IO
非阻塞属性
- socket
- BlockingIOError
对于第三条,执行代码测试完,建议马上把执行脚本停掉,要不然你电脑的风扇要呼呼的转啦!!!
现代操作系统实现
主要有
- IO多路复
、
- select
、
- poll
等几种方式。这些模块都能够同时监听一组
- epoll
,内核负责不断轮询或检测所负责的所有
- socket
,当其中某一个
- socket
满足可读或者可写了就唤醒用户进程进行处理。
- socket
三者区别:
数量有限制,一般小于
- socket
所定义的数量,操作系统采用轮询进行可读可写检查,数量较大时效率比较低,需要内存拷贝。
- FD_SETSIZE
区别不大,但是它没有数量上的限制,因为它是通过链表进行存储的,效率同
- select
一样随
- select
数量线性增长。
- socket
相比同样没有数量上的限制;但是它不是通过轮询的方式检测,而是通过事件触发的方式,不会每次都会遍历一遍所有的
- poll
,它会要求满足事件要求的
- socket
主动通知内核然后通知用户进程进行处理;用户空间和内核共享一块内存不需要内存拷贝。
- socket
实际应用中要根据具体场景进行选择。下面通过IO多路复用实现一个简单的http server:
- from
- selectors
- import
- DefaultSelector
- ,
- EVENT_READ
- import socket
- import collections
- # 会取最优的类型
- sel
- =
- DefaultSelector
- ()
- # 有客户端连接
- def
- accept
- (*
- args
- ):
- conn
- ,
- addr
- =
- server
- .
- accept
- ()
- (
- '[server] conn accept:'
- ,
- conn
- .
- fileno
- ())
- conn
- .
- setblocking
- (
- False
- )
- # 注册客户端监听
- sel
- .
- register
- (
- conn
- ,
- EVENT_READ
- ,
- read
- )
- def
- read
- (
- conn
- ,
- mask
- ):
- # 读取客户端数据
- chunk
- =
- conn
- .
- recv
- (
- 1024
- )
- if
- chunk
- :
- (
- '[server] receive data fileno:'
- ,
- conn
- .
- fileno
- (),
- repr
- (
- chunk
- ))
- conn
- .
- send
- (
- 'HTTP/1.0 200 OK\r\n\r\n<h1>hello client</h1>'
- .
- encode
- (
- 'utf8'
- ))
- else:
- # 没数据注销客户端监听
- (
- '[server] conn close:'
- ,
- conn
- .
- fileno
- ())
- sel
- .
- unregister
- (
- conn
- )
- conn
- .
- close
- ()
- Address
- =
- collections
- .
- namedtuple
- (
- 'Address'
- ,
- [
- 'host'
- ,
- 'port'
- ])
- server_addr
- =
- Address
- (
- 'localhost'
- ,
- 5465
- )
- server
- =
- socket
- .
- socket
- ()
- server
- .
- setblocking
- (
- False
- )
- server
- .
- bind
- (
- server_addr
- )
- server
- .
- listen
- (
- 100
- )
- (
- '[server] listening on port'
- ,
- server_addr
- )
- # 注册server可读事件,可读时会回调第三个函数
- sel
- .
- register
- (
- server
- ,
- EVENT_READ
- ,
- accept
- )
- def loop_forever():
- while
- True
- :
- # 没事件时会阻塞线程
- events
- =
- sel
- .
- select
- ()
- # 返回可读事件
- for
- e_key
- ,
- e_mask
- in
- events
- :
- callback
- =
- e_key
- .
- data
- callback
- (
- e_key
- .
- fileobj
- ,
- e_mask
- )
- if
- __name__
- ==
- '__main__'
- :
- loop_forever()
代码中
模块的
- selectors
会根据操作系统按照下面的顺序取具体的实现:
- DefaultSelector()
- #Choose the best implementation,
- roughly:
- #epoll | kqueue | devpoll > poll > select.#select() also can 't accept a FD > FD_SETSIZE (usually around 1024)
- if
- 'KqueueSelector '
- in
- globals
- ():
- DefaultSelector
- =
- KqueueSelector
- elif
- 'EpollSelector '
- in
- globals
- ():
- DefaultSelector
- =
- EpollSelector
- elif
- 'DevpollSelector '
- in
- globals
- ():
- DefaultSelector
- =
- DevpollSelector
- elif
- 'PollSelector '
- in
- globals
- ():
- DefaultSelector
- =
- PollSelector
- else:
- DefaultSelector
- =
- SelectSelector
- '
和
- blocking IO
的区别在于前者会阻塞线程直到有数据到来,而后者有无数据都会直接返回,无数据时抛出
- non - blocking IO
- BlockingIOError
和
- synchronous IO
,前者在IO操作时会阻塞进程,后者直接返回。我们上面所列的几种情况都属于前者,
- asynchronous IO
虽然说在
- non - blocking IO
或
- read
的时候不会阻塞线程,但是这个概念里的
- write
是整个IO操作都不阻塞,也就是第二个阶段从内核拷贝数据到用户进程也不会
- 阻塞
,满足这两个条件的才叫异步IO。
- 阻塞
和
- non - blocking IO
,前者一般不会被阻塞,但是还是需要用户进程去检查
- asynchronous IO
是否可读写,并且在从内核拷贝数据的时候用户进程是阻塞的;而异步IO完全把IO操作交给操作系统,等到操作系统处理完后再通过发送事件的方式通知用户进程直接取数据就好了。
- socket
动手实践一下理解会更好
欢迎关注微信公众号:Python实验课
来源: https://juejin.im/entry/5a057c55f265da43346f628b