Python select

1、前言

  Python的select()方法直接调用操做系统的IO接口,它监控sockets,open files, and pipes(全部带fileno()方法的文件句柄)什么时候变成readable 和writeable, 或者通讯错误,select()使得同时监控多个链接变的简单,而且这比写一个长循环来等待和监控多客户端链接要高效,由于select直接经过操做系统提供的C的网络接口进行操做,而不是经过Python的解释器。python

  注意:Using Python’s file objects with select() works for Unix, but is not supported under Windows.缓存

2、select socket

  接下来经过socket server例子要以了解select 是如何经过单进程实现同时处理多个非阻塞的socket链接的 服务器

       2.1 socket server 开始监听网络

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

  2.2 3个通讯列表app

  select()方法接收并监控3个通讯列表, 第一个是全部的输入的data,就是指外部发过来的数据,第2个是监控和接收全部要发出去的data(outgoing data),第3个监控错误信息,接下来咱们须要建立2个列表来包含输入和输出信息来传给select()。 socket

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

# 全部链接进来的对象都放在inputs
inputs = [server, ]  # 本身也要监控,由于server自己也是个对象

# 须要发送数据的对象
outputs = []

  2.3 添加一个队列oop

  全部客户端的进来的链接和数据将会被server的主循环程序放在上面的list中处理,咱们如今的server端须要等待链接可写(writable)以后才能过来,而后接收数据并返回(所以不是在接收到数据以后就马上返回),由于每一个链接要把输入或输出的数据先缓存到queue里,而后再由select取出来再发出去。this

  Connections are added to and removed from these lists by the server main loop. Since this version of the server is going to wait for a socket to become writable before sending any data (instead of immediately sending the reply), each output connection needs a queue to act as a buffer for the data to be sent through it. spa

# 对外发送数据的队列,记录到字典中
message_queues = {}

  2.4 主循环 操作系统

while True:
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
    # 若是没有任何fd就绪,那程序就会一直阻塞在这里

  当你把inputs,outputs,exceptional(这里跟inputs共用)传给select()后,它返回3个新的list,咱们上面将他们分别赋值为readable,writable,exceptional, 全部在readable list中的socket链接表明有数据可接收(recv),全部在writable list中的存放着你能够对其进行发送(send)操做的socket链接,当链接通讯出现error时会把error写到exceptional列表中。

   2.5 Readable list

   Readable list 中的socket 能够有3种可能状态,第一种是若是这个socket是main "server" socket,它负责监听客户端的链接,若是这个main server socket出如今readable里,那表明这是server端已经ready来接收一个新的链接进来了,为了让这个main server能同时处理多个链接,在下面的代码里,咱们把这个main server的socket设置为非阻塞模式。  

    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()  
            # 接收到客户端的数据后,不马上返回 ,暂存在队列里,之后发送

  第二种状况是这个socket是已经创建了的链接,它把数据发了过来,这个时候你就能够经过recv()来接收它发过来的数据,而后把接收到的数据放到queue里,这样你就能够把接收到的数据再传回给客户端了。

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

  第三种状况就是这个客户端已经断开了,因此你再经过recv()接收到的数据就为空了,因此这个时候你就能够把这个跟客户端的链接关闭了。

            else:  # 若是收不到data表明什么呢? 表明客户端断开了
                print("client [%s] closed", s)

                if s in outputs:
                    # 既然客户端都断开了,我就不用再给它返回数据了,
                    # 因此这时候若是这个客户端的链接对象还在outputs列表中,就把它删掉
                    outputs.remove(s)

                inputs.remove(s)  # 这个链接必然在inputs中,也删掉
                s.close()

                # 关闭的链接在队列中也删除
                del message_queues[s]

 

  2.6 writable list

  对于writable list中的socket,也有几种状态,若是这个客户端链接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,不然就把这个链接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个链接,那就会认为这个链接还处于非活动状态 

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except queue.Empty:
            # 没有数据了,该链接对象队列为空,中止检测
            print('output queue for [%s] is empty' % s.getpeername()[0])
            outputs.remove(s)
        
        else:
            print('send %s to %s' % (next_msg, s.getpeername()[0]))
            s.send(next_msg)

  2.7 exceptional condition

  最后,若是在跟某个socket链接通讯过程当中出了错误,就把这个链接对象在inputs\outputs\message_queue中都删除,再把链接关闭掉 

    for s in exceptional:
        print('handling exceptional condition for', s.getpeername()[0])
        # 从inputs中删除
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()
        
        # 删除队列
        del message_queues[s]

  注: getpeername() / getsocketname

    getpeername能够得到服务器的地址信息和端口号,正好和getsockname得到本机地址信息和端口号彻底相反

3、完整事例

  select server  

# -*- coding: UTF-8 -*-

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

# 全部链接进来的对象都放在inputs
inputs = [server, ]  # 本身也要监控,由于server自己也是个对象

# 须要发送数据的对象
outputs = []

# 对外发送数据的队列,记录到字典中
message_queues = {}

while True:
    readable, writable, exceptional = 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('received [%s] from %s' % (data, s.getpeername()[0]))
                message_queues[s].put(data)  # 收到的数据先放到queue里,一会返回给客户端
                if s not in outputs:
                    outputs.append(s)  # 为了避免影响处理与其它客户端的链接 , 这里不马上返回数据给客户端

            else:  # 若是收不到data表明什么呢? 表明客户端断开了
                print("client [%s] closed", s)

                if s in outputs:
                    # 既然客户端都断开了,我就不用再给它返回数据了,
                    # 因此这时候若是这个客户端的链接对象还在outputs列表中,就把它删掉
                    outputs.remove(s)

                inputs.remove(s)  # 这个链接必然在inputs中,也删掉
                s.close()

                # 关闭的链接在队列中也删除
                del message_queues[s]

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except queue.Empty:
            # 没有数据了,该链接对象队列为空,中止检测
            print('output queue for [%s] is empty' % s.getpeername()[0])
            outputs.remove(s)

        else:
            print('send %s to %s' % (next_msg, s.getpeername()[0]))
            s.send(next_msg)

    for s in exceptional:
        print('handling exceptional condition for', s.getpeername()[0])
        # 从inputs中删除
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()

        # 删除队列
        del message_queues[s]

  client

# -*- coding: UTF-8 -*-
import socket

HOST = 'localhost'  # The remote host
PORT = 9999  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"), encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)
    # print(data)

    print('Received', repr(data))
相关文章
相关标签/搜索