前言

从零单排高性能问题,这次轮到异步通信了。这个领域入门有点难,需要了解UNIX五种IO模型和 TCP协议,熟练使用三大异步通信框架:Netty、NodeJS、Tornado。目前所有标榜异步的通信框架用的都不是异步IO模型,而是IO多路复 用中的epoll。因为Python提供了对Linux内核API的友好封装,所以我选择Python来学习IO多路复用。

IO多路复用

  1. select

    举一个EchoServer的例子,客户端发送任何内容,服务端会原模原样返回。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    Created on Feb 16, 2016 @author: mountain
    '''
    import socket
    import select
    from Queue import Queue #AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。
    #SOCK_STREAM指定使用面向流的TCP协议,如果要使用面向数据包的UCP协议,就指定SOCK_DGRAM。
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setblocking(False)
    #设置监听的ip和port
    server_address = ('localhost', 1234)
    server.bind(server_address)
    #设置backlog为5,client向server发起connect,server accept后建立长连接,
    #backlog指定排队等待server accept的连接数量,超过这个数量,server将拒绝连接。
    server.listen(5)
    #注册在socket上的读事件
    inputs = [server]
    #注册在socket上的写事件
    outputs = []
    #注册在socket上的异常事件
    exceptions = []
    #每个socket有一个发送消息的队列
    msg_queues = {}
    print "server is listening on %s:%s." % server_address
    while inputs:
    #第四个参数是timeout,可选,表示n秒内没有任何事件通知,就执行下面代码
    readable, writable, exceptional = select.select(inputs, outputs, exceptions)
    for sock in readable:
    #client向server发起connect也是读事件,server accept后产生socket加入读队列中
    if sock is server:
    conn, addr = sock.accept()
    conn.setblocking(False)
    inputs.append(conn)
    msg_queues[conn] = Queue()
    print "server accepts a conn."
    else:
    #读取client发过来的数据,最多读取1k byte。
    data = sock.recv(1024)
    #将收到的数据返回给client
    if data:
    msg_queues[sock].put(data)
    if sock not in outputs:
    #下次select的时候会触发写事件通知,写和读事件不太一样,前者是可写就会触发事件,并不一定要真的去写
    outputs.append(sock)
    else:
    #client传过来的消息为空,说明已断开连接
    print "server closes a conn."
    if sock in outputs:
    outputs.remove(sock)
    inputs.remove(sock)
    sock.close()
    del msg_queues[sock]
    for sock in writable:
    if not msg_queues[sock].empty():
    sock.send(msg_queues[sock].get_nowait())
    if msg_queues[sock].empty():
    outputs.remove(sock)
    for sock in exceptional:
    inputs.remove(sock)
    if sock in outputs:
    outputs.remove(sock)
    sock.close()
    del msg_queues[sock]
    [mountain@king ~/workspace/wire]$ telnet localhost 1234
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    1
    1

    select有3个缺点:

    1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
    2. 每次调用select后,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
      这点从python的例子里看不出来,因为python select api更加友好,直接返回就绪的socket列表。事实上linux内核select api返回的是就绪socket数目:
      int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    3. fd数量有限,默认1024。
  2. poll

    采用poll重新实现EchoServer,只要搞懂了select,poll也不难,只是api的参数不太一样而已。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    Created on Feb 27, 2016 @author: mountain
    '''
    import select
    import socket
    import sys
    import Queue server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setblocking(False)
    server_address = ('localhost', 1234)
    server.bind(server_address)
    server.listen(5)
    print 'server is listening on %s port %s' % server_address
    msg_queues = {}
    timeout = 1000 * 60
    #POLLIN: There is data to read
    #POLLPRI: There is urgent data to read
    #POLLOUT: Ready for output
    #POLLERR: Error condition of some sort
    #POLLHUP: Hung up
    #POLLNVAL: Invalid request: descriptor not open
    READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
    READ_WRITE = READ_ONLY | select.POLLOUT
    poller = select.poll()
    #注册需要监听的事件
    poller.register(server, READ_ONLY)
    #文件描述符和socket映射
    fd_to_socket = { server.fileno(): server}
    while True:
    events = poller.poll(timeout)
    for fd, flag in events:
    sock = fd_to_socket[fd]
    if flag & (select.POLLIN | select.POLLPRI):
    if sock is server:
    conn, client_address = sock.accept()
    conn.setblocking(False)
    fd_to_socket[conn.fileno()] = conn
    poller.register(conn, READ_ONLY)
    msg_queues[conn] = Queue.Queue()
    else:
    data = sock.recv(1024)
    if data:
    msg_queues[sock].put(data)
    poller.modify(sock, READ_WRITE)
    else:
    poller.unregister(sock)
    sock.close()
    del msg_queues[sock]
    elif flag & select.POLLHUP:
    poller.unregister(sock)
    sock.close()
    del msg_queues[sock]
    elif flag & select.POLLOUT:
    if not msg_queues[sock].empty():
    msg = msg_queues[sock].get_nowait()
    sock.send(msg)
    else:
    poller.modify(sock, READ_ONLY)
    elif flag & select.POLLERR:
    poller.unregister(sock)
    sock.close()
    del msg_queues[sock]

    poll解决了select的第三个缺点,fd数量不受限制,但是失去了select的跨平台特性,它的linux内核api是这样的:

    int poll (struct pollfd *fds, unsigned int nfds, int timeout);
    struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
    };
  3. epoll

    用法与poll几乎一样。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    Created on Feb 28, 2016 @author: mountain
    '''
    import select
    import socket
    import Queue server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setblocking(False)
    server_address = ('localhost', 1234)
    server.bind(server_address)
    server.listen(5)
    print 'server is listening on %s port %s' % server_address
    msg_queues = {}
    timeout = 60
    READ_ONLY = select.EPOLLIN | select.EPOLLPRI
    READ_WRITE = READ_ONLY | select.EPOLLOUT
    epoll = select.epoll()
    #注册需要监听的事件
    epoll.register(server, READ_ONLY)
    #文件描述符和socket映射
    fd_to_socket = { server.fileno(): server}
    while True:
    events = epoll.poll(timeout)
    for fd, flag in events:
    sock = fd_to_socket[fd]
    if flag & READ_ONLY:
    if sock is server:
    conn, client_address = sock.accept()
    conn.setblocking(False)
    fd_to_socket[conn.fileno()] = conn
    epoll.register(conn, READ_ONLY)
    msg_queues[conn] = Queue.Queue()
    else:
    data = sock.recv(1024)
    if data:
    msg_queues[sock].put(data)
    epoll.modify(sock, READ_WRITE)
    else:
    epoll.unregister(sock)
    sock.close()
    del msg_queues[sock]
    elif flag & select.EPOLLHUP:
    epoll.unregister(sock)
    sock.close()
    del msg_queues[sock]
    elif flag & select.EPOLLOUT:
    if not msg_queues[sock].empty():
    msg = msg_queues[sock].get_nowait()
    sock.send(msg)
    else:
    epoll.modify(sock, READ_ONLY)
    elif flag & select.EPOLLERR:
    epoll.unregister(sock)
    sock.close()
    del msg_queues[sock]

    epoll解决了select的三个缺点,是目前最好的IO多路复用解决方案。为了更好地理解epoll,我们来看一下linux内核api的用法。

    int epoll_create(int size)//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)//注册事件,每个fd只拷贝一次。
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)/*等待IO事件,事件发生时,
    内核调用回调函数,把就绪fd放入就绪链表中,并唤醒epoll_wait,epoll_wait只需要遍历就绪链表即可,
    而select和poll都是遍历所有fd,这效率高下立判。*/

IO多路复用深入浅出的更多相关文章

  1. IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

    IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO ...

  2. python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...

  3. {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块

    python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...

  4. 并发编程(IO多路复用)

    阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 异步IO(Asynchro ...

  5. python开发IO模型:阻塞&非阻塞&异步IO&多路复用&selectors

    一 IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非 ...

  6. (IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    参考博客: https://www.cnblogs.com/xiao987334176/p/9056511.html 内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yi ...

  7. 【IO多路复用】

    " 目录 一.IO模型介绍 二.阻塞IO(blocking IO) 三.非阻塞IO(non-blocking IO) 四.多路复用IO(IO multiplexing) 五.异步IO(Asy ...

  8. Python(七)Socket编程、IO多路复用、SocketServer

    本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" ...

  9. IO多路复用概念性

    sellect.poll.epoll三者的区别 先来了解一下什么是进程切换 为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行,这种行为为进程的切换,任务切换 ...

随机推荐

  1. java入门了解02

    1:JDK,JRE,JVM的作用关系    (一)作用            JVM:提供java跨平台            JRE:java运行环境            JDK:java开发环境 ...

  2. 使用github+hexo搭建博客笔记

    听说github上可以搭博客,而且不用自己提供空间和维护,哈哈哈 作为一名程序猿,github搭博客对我有种神奇的吸引力,赶紧动手试一试 关于如何使用hexo搭建博客网上好的教程多如牛毛,而且这篇博客 ...

  3. [编织消息框架][JAVA核心技术]动态代理应用5-javassist

    基础部份: 修改class我们用到javassist,在pom.xml添加 <properties> <javassist.version>3.18.2-GA</java ...

  4. WPF自定义控件(2)——图表设计[1]

    0.小叙闲言 除了仪表盘控件比较常用外,还有图表也经常使用,同样网上也有非常强大的图表控件,有收费的(DEVexpress),也有免费的.但我们平时在使用时,只想简单地绘一个图,控件库里面的许多功能我 ...

  5. java基础--动态代理实现与原理详细分析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式                     ...

  6. CTR预估中的贝叶斯平滑方法(二)参数估计和代码实现

    1. 前言 前面博客介绍了CTR预估中的贝叶斯平滑方法的原理http://www.cnblogs.com/bentuwuying/p/6389222.html. 这篇博客主要是介绍如何对贝叶斯平滑的参 ...

  7. 初识bd时的一些技能小贴士

    既然小豆腐如此给力,而且充分的利用主动学习的优势,已经有了迅速脑补,压倒式的优势,不过这只是表面而已,一切才刚刚开始,究竟鹿死谁手,还有待验证. 以上可以看到,小豆腐为什么拼命的要teach我们了么, ...

  8. 用 parseInt()解决的 小 bug

    在做轮播模块的时候遇到问题是:你在 连续指示小按钮 时候再去 只有 点击 下一张按钮,出现bug: 指示小按钮的 className 当前显示的 calssName 为 undefined ! // ...

  9. HDU 5556 最大独立集

    这题主要有了中间的一些连通块的限制,不太好直接用二分图最大独立集做.考虑到图比较小,可以作补图求最大团来求解. #include <iostream> #include <vecto ...

  10. 详解 Node + Redux + MongoDB 实现 Todolist

    前言 为什么要使用 Redux? 组件化的开发思想解放了繁琐低效的 DOM 操作,以 React 来说,一切皆为状态,通过状态可以控制视图的变化,然后随着应用项目的规模的不断扩大和应用功能的不断丰富, ...