论事件驱动与多路IO复用

通常,我们写服务器处理模型的程序时,有以下几种模型

  • (1)每收到一个请求,创建一个新的进程,来处理该请求;
  • (2)每收到一个请求,创建一个新的线程,来处理该请求;
  • (3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求

    上面的几种方式,各有千秋,

    第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。

    第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。

    第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。

综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式

事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。

更多姿势

http://www.cnblogs.com/alex3714/articles/5248247.html

I/O操作由操作系统完成,遇到IO操作,主进程交给操作系统处理IO,处理完成通过回调函数告诉主进程。

IO多路复用

番外篇

http://www.cnblogs.com/alex3714/articles/5876749.html

select、poll、epoll、本质为IO多路复用。

select 版本

简单版:

import select
import socket
import queue

server = socket.socket()
server.bind(("localhost", 9990))
server.listen(1000)
server.setblocking(False)  # 不阻塞
msg_dic = {}
inputs = [server,]
outputs = []
while True:
    readable, writeable, exceptional = select.select(inputs, outputs, inputs)
    print(readable, writeable, exceptional)
    for r in readable:
        if r is server:
            conn, addr = server.accept()
            print("来了个新连接", addr)
            inputs.append(conn)
        else:
            try:
                data = r.recv(1024)
                print("收到的数据", data)
                conn.send(data)
            except ConnectionResetError:
                print("[%s]客户端断开了" % r)
                inputs.remove(r)

高级装逼版

import select
import socket
import queue

server = socket.socket()
server.setblocking(0)

server_addr = (‘localhost‘, 10000)

print(‘starting up on %s port %s‘ % server_addr)
server.bind(server_addr)

server.listen(5)

inputs = [server, ]  # 自己也要监测呀,因为server本身也是个fd
outputs = []

message_queues = {}

while True:
    print("waiting for next event...")

    readable, writeable, exeptional = select.select(inputs, outputs, inputs)  # 如果没有任何fd就绪,那程序就会一直阻塞在这里

    for s in readable:  # 每个s就是一个socket

        if s is server:  # 别忘记,上面我们server自己也当做一个fd放在了inputs列表里,传给了select,如果这个s是server,代表server这个fd就绪了,
            # 就是有活动了, 什么情况下它才有活动? 当然 是有新连接进来的时候 呀
            # 新连接进来了,接受这个连接
            conn, client_addr = s.accept()
            print("new connection from", client_addr)
            conn.setblocking(0)
            inputs.append(conn)  # 为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据, 把它放到inputs里, 下一次loop时,这个新连接
            # 就会被交给select去监听,如果这个连接的客户端发来了数据 ,那这个连接的fd在server端就会变成就续的,select就会把这个连接返回,返回到
            # readable 列表里,然后你就可以loop readable列表,取出这个连接,开始接收数据了, 下面就是这么干 的

            message_queues[conn] = queue.Queue()  # 接收到客户端的数据后,不立刻返回 ,暂存在队列里,以后发送

        else:  # s不是server的话,那就只能是一个 与客户端建立的连接的fd了
            # 客户端的数据过来了,在这接收
            data = s.recv(1024)
            if data:
                print("收到来自[%s]的数据:" % s.getpeername()[0], data)
                message_queues[s].put(data)  # 收到的数据先放到queue里,一会返回给客户端
                if s not  in outputs:
                    outputs.append(s)  # 为了不影响处理与其它客户端的连接 , 这里不立刻返回数据给客户端

            else:  # 如果收不到data代表什么呢? 代表客户端断开了呀
                print("客户端断开了",s)

                if s in outputs:
                    outputs.remove(s)  # 清理已断开的连接

                inputs.remove(s)  # 清理已断开的连接

                del message_queues[s]  # 清理已断开的连接

    for s in writeable:
        try:
            next_msg = message_queues[s].get_nowait()

        except queue.Empty:
            print("client [%s]" %s.getpeername()[0], "queue is empty..")
            outputs.remove(s)

        else:
            print("sending msg to [%s]" % s.getpeername()[0], next_msg)
            s.send(next_msg.upper())

    for s in exeptional:
        print("handling exception for ", s.getpeername())
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()
        del message_queues[s]

secoketor模块

import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print("accepted", conn, "from", addr, mask)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)  # 新连接注册read回调函数

def read(conn, mask):
    data = conn.recv(1024)
    if data:
        print("echoing", repr(data), "to", conn)
        conn.send(data)
    else:
        print("closing", conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(("localhost", 10000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)  # 来新连接调accept
while True:
    events = sel.select()  # 默认阻塞,有活动连接就返回活动的连接列表 epoll/select
    for key, mask in events:
        callback = key.data  # accept
        callback(key.fileobj, mask)  # key.fileobj=  文件句柄

客户端

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
import socket
import sys

# send_buf_size = 8192  # 设置发送缓冲域大小
# get_buf_size = 8192  # 设置接收缓冲域大小

messages = [b‘This is the message. ‘,
            b‘It will be sent ‘,
            b‘in parts.‘,
            ]
server_address = (‘192.168.31.102‘, 10000)

# Create a TCP/IP socket
socks = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(10000)]

# Connect the socket to the port where the server is listening
print(‘connecting to %s port %s‘ % server_address)

for s in socks:
    # s.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
    # s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, send_buf_size)
    # s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, get_buf_size)
    # s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.connect(server_address)

for message in messages:

    # Send messages on both sockets
    for s in socks:
        print(‘%s: sending "%s"‘ % (s.getsockname(), message))
        s.send(message)

    # Read responses on both sockets
    for s in socks:
        data = s.recv(1024)
        print(‘%s: received "%s"‘ % (s.getsockname(), data))
        if not data:
            print(sys.stderr, ‘closing socket‘, s.getsockname())
时间: 2024-08-30 12:04:31

论事件驱动与多路IO复用的相关文章

Redis03——Redis之单线程+多路IO复用技术

Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(线程池) 阻塞lO:给女神发一条短信, 说我来找你了,然后就默默的一直等着 女神下楼,这个期间除了等待你不会做其他事情,属于备胎做法. 非阻塞IO:给女神发短信,如果不回,接着再发,一直 发到女神下楼,这个期间你除了发短信等待不会做其他事情,属于专-做法. IO多路复用:是找一个宿

并发程序设计3:多路IO复用技术(2)

上一节(https://www.cnblogs.com/yuanwebpage/p/12362876.html)记录了多路IO复用的第一种方式select函数,以及其相应的缺点.本节记录多路IO复用的第二种方式epoll(在windows系统下叫IOCP). 1. epoll相关函数 epoll函数克服了select函数的相关缺点,其优点如下: (1) 只需向OS注册一次文件描述符集合,不用每次循环传递: (2) epoll函数会将发生变化的文件描述符单独集中起来,这样每次遍历时只需要遍历发生变

linux多路IO复用中的select和epoll

select,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间. select调用的过程: (1)从用户空间拷贝fd_set到内核空间 (2)注册回调函数__poll

TCP编程:select提高服务器处理能力 [socket多路IO复用]

服务器: #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/wait.h>

Reactor模式,或者叫反应器模式 - 为什么用多路io复用提供吞吐量

Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或者叫召唤兽. 并发系统常使用reactor模式,代替常用的多线程的处理方式,节省系统的资源,提高系统的吞吐量. 先用比较直观的方式来介绍一下这种方式的优点,通过和常用的多线程方式比较一下,可能更好理解. 以一个餐饮为例,每一个人来就餐就是一个事件,他会先看一下菜单,然后点餐.就像一个网站会有很多的请

基于select类型多路IO复用,实现简单socket并发

还有很多缺限,如客户断开无限重复 以下转至老师博客: server: #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = "alex" import select import socket import sys import queue server = socket.socket() server.setblocking(0) server_addr = ('localhost',5000) print('start

多路IO复用模型--select, poll, epoll

select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct tim

(转)IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

本文来自:https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral,非常感谢. 如果面试问到IO操作,这篇文章提到的问题,基本是必问,百度的面试官问我三个问题 (1)什么是NIO(Non-blocked IO),AIO,BIO (2) java IO 与 NIO(New IO)的区别 (3)select 与 epoll,poll区别 我胡乱说了一气,自己边说边觉得完蛋了.果然,二面

应用层协议实现系列(一)——HTTPserver之仿nginx多进程和多路IO的实现

近期在尝试自己写一个Httpserver,在粗略研究了nginx的代码之后,决定仿照nginx中的部分设计自己实现一个高并发的HTTPserver,在这里分享给大家. 眼下使用的较多的Httpserver就是apache和nginx,apache的主要特点就是稳定,而nginx的主要特点是承载的并发量高.在这里从实现原理上做一个分析: apache採用的是多进程server模型,即server每监听到一个连接时,会创建一个新的进程去处理连接,进程与进程之间是独立的,因此就算进程在处理连接的过程中