I/O多路复用

I/O多路复用(I/O multiplexing),单个进程,就可以实现并发处理多个网络连接。目前有三种实现方式:select(全平台)、poll(linux)、epoll(linux)。前两者的基本原理是,轮询其监听的所有socket,一旦有数据到达,就将目标soket取出,就通知用户处理。epoll的实现略有不同,它为每个socket绑定一个回调函数,一旦socket有数据到达,就会触发回调函数执行。推荐使用epoll,其效率和并发能力是最好的,

select模块

下面我们看一下用select模块实现并发处理tcp连接:
服务端:

import socket, select

ser = socket.socket()
ser.bind(('0.0.0.0',8080))
ser.listen(5)
# ser.setblocking(False) 设置套接字I/O阻塞和非阻塞,最终效果是一样的,因为整个程序被select阻塞着
# 一旦select监听到套接字ser有数据到达,就会取出ser,执行其方法accept(),执行时数据已经到达了,已经是非阻塞的了。
inputs = [ser,] # 把服务端套接字放入监听列表

print('waiting for connection...')
while True:
    i, o, e = select.select(inputs,[],[])
    # select可以监听多种对象([inputs],[outputs],[errors],timeout),对应括号内的参数。
    # 轮询inputs内的监听对象,一旦该有数据到达,取出该对象放到i里面,开始下面的处理
    for obj in i:   
        if obj == ser:  # 服务端socket
            conn, addr = ser.accept()
            print('got a connection: ',addr)
            inputs.append(conn) # 将客户端连接socket加入监听列表

        else:   # conn
            try:
                msg = obj.recv(1024)
                if not msg: 
                    print('客户端正常断开') 
                    inputs.remove(obj)
                    continue
                print(msg.decode('utf-8'))
                back_msg = input('anser client%s >>'%i.index(obj))
                obj.send(back_msg.encode('utf-8'))

            except Exception:
                print('客户端异常断开')
                inputs.remove(obj)

客户端:

import socket

cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.connect_ex(('127.0.0.1',8080))

while True:
    try:
        msg = input('>> ')
        if not msg: continue
        if msg == 'quit':break

        cli.send(msg.encode('utf-8'))
        msg = cli.recv(1024)
        print(msg.decode('utf-8'))

    except Exception: # 处理服务端异常断开
        break

从服务端代码可以看出,如果多个客户端连接,同时发送消息到服务端,那么服务端一次显示一个消息并阻塞等待回复消息,然后for循环显示下一个套接字的消息,再阻塞回复。

selectors模块

这个模块更为高级,可以根据操作系统自动选择模式,比如epoll(优先选择),select,并且提供了统一的操作接口。
服务端:

import selectors
import socket

sel_obj = selectors.DefaultSelector()   # 创建selectors监听对象

def accept(tcpser): # 定义套接字的回调函数
    conn, addr = tcpser.accept()
    print('got a connection from',addr)
    conn.setblocking(False)
    sel_obj.register(conn, selectors.EVENT_READ, communicate)  # 注册conn到监听对象

def communicate(conn): # 定义套接字的回调函数
    try:
        msg = conn.recv(1024)
        if not msg:
            print('-- lost connection with client --')
            sel_obj.unregister(conn)   # 连接套接字断开,那么从监听对象中注销
            conn.close() # 关闭套接字,释放OS资源
        else:
            print('message: ',msg.decode('utf-8'))
            back_msg = input('anser: ').strip()
            conn.send(back_msg.encode('utf-8'))

    except Exception:# 客户端异常断开
        print('-- client error --')
        sel_obj.unregister(conn)
        conn.close()

tcpser = socket.socket()
tcpser.bind(('0.0.0.0',8080))
tcpser.listen(5)
tcpser.setblocking(False)
sel_obj.register(tcpser, selectors.EVENT_READ, accept)
# 注册到监听对象:套接字对象和其执行函数绑定,括号内第一个参数是套接字对象,第三个是执行函数,中间固定的。
# 相当于上面select模块的栗子,将服务端套接字加入监听列表

print('waiting for connection...')
while True:
    events = sel_obj.select() # 不论是select对象,还是epoll对象,调用监听,接口都是obj.select(),
    # 监听返回值是有数据到达的套接字对象,放在events里
    print(events)
    for key, mask in events:    # 请自动忽略mask...
        # 通过key.data可以取到套接字对象绑定的方法比如tcpser绑定accept, conn绑定communicate(recv+send)
        # 通过key.fileobj可以取到套接字对象
        callback_func = key.data   # 拿到回调函数
        callback_func(key.fileobj)    # 执行函数

客户端同select的栗子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值