• Python select、selectors模块


     1. select模块

    针对select,要先理解其他几个概念:

    文件描述符:

    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

    内核空间:

    Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

    内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。

    内核空间和用户空间一般通过系统调用进行通信。

     

    select就是针对许多文件描述符(简称fd)进行监控,它有三个参数:

    rlist -- wait until ready for reading
    wlist -- wait until ready for writing
    xlist -- wait for an "exceptional condition"

    第一个参数监控 进来的 数据的fd列表,select监控这个列表,等待这些fd发送过来数据,一旦数据发送过来了(可以读取了),就返回一个可读的fd列表

    第二个参数监控 出去的 数据的fd列表,select监控这个列表,等待这些fd发送出去数据,一旦fd准备好发送了(可以写入了),就返回一个可写的fd列表

    第三个参数监控fd列表,返回出异常的fd列表

    服务端:

    import select
    import socket
    import sys
    import queue
    
    # 生成socket对象
    server = socket.socket()
    # 设置非阻塞模式
    server.setblocking(False)
    
    # 绑定地址,设置监听
    server.bind(('localhost',9999))
    server.listen(5)
    
    # 将自己也放进待监测列表里
    inputs = [server, ]
    outputs = []
    message_queues = {}
    
    while True:
        '''
        关于socket可读可写的判断,可以参考博客:https://blog.csdn.net/majianfei1023/article/details/45788591
        '''
        rlist, wlist, elist = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里
    
        for r in rlist:  # 遍历已经可以准备读取数据的 fd
            if r is server: # 如果这个 fd 是server,即 server 有数据待接收读取,说明有新的客户端连接过来了
                conn, client_addr = r.accept()
                print("new connection from",client_addr)
                conn.setblocking(False)
                inputs.append(conn) # 将这个新的客户端连接添加到检测的列表中
                message_queues[conn] = queue.Queue() # 用队列存储客户端发送来的数据,等待服务器统一返回数据
    
            else:          # 这个可读的 r 不是服务器,那就是某个客户端。就是说客户端发送数据过来了,这些数据处于待读取状态
                try:       # 异常处理,这是为了防止客户端异常断开报错(比如手动关掉客户端黑窗口,服务器也会跟着报错退出)
                    data = r.recv(1024)
                    if data:    # 根据判断data是否为空,判断客户端是否断开
                        print("收到来自[%s]的数据:" % r.getpeername()[0], data)
                        message_queues[r].put(data)   # 收到的数据先放到queue里,一会返回给客户端
                        if r not in outputs:
                            outputs.append(r)     # 放进可写的fd列表中,表明这些 fd 已经准备好去发送数据了。
                    else:   # 如果数据为空,表明客户端断开了
                        print('客户端断开了')
                        if r in outputs:
                            outputs.remove(r)    #  清理已断开的连接
                        inputs.remove(r)         # 清理已断开的连接
                        del message_queues[r]    # 清理已断开的连接
                except ConnectionResetError:     # 如果报错,说明客户端断开了
                    print("客户端异常断开了", r)
                    if r in outputs:
                        outputs.remove(r)   # 清理已断开的连接
                    inputs.remove(r)        # 清理已断开的连接
                    del message_queues[r]  # 清理已断开的连接
    
        for w in wlist:       # 遍历可写的 fd 列表,即准备好发送数据的那些fd
            # 判断队列是否为空
            try :
                next_msg = message_queues[w].get_nowait()
            except queue.Empty:
                # print("client [%s]" % w.getpeername()[0], "queue is empty..")
                outputs.remove(w)
            # 队列不为空,就把队列中的数据改成大写,原样发回去
            else:
                # print("sending msg to [%s]"% w.getpeername()[0], next_msg)
                w.send(next_msg.upper())
    
        for e in elist:   # 处理报错的 fd
            e.close()
            print("Error occured in ",e.getpeername())
            inputs.remove(e)
            if e in outputs:
                outputs.remove(e)
            del message_queues[e]

    客户端:

    import socket
    import sys
    
    sock = socket.socket()
    sock.connect(('localhost',9999))
    while True:
        c = input('>>>:').strip()
        sock.send(c.encode())
        data = sock.recv(1024)
        print(data.decode())
    
    sock.close()

    2. selectors模块

    官方文档:https://docs.python.org/3/library/selectors.html

    服务端:

    import selectors
    import socket
    
    # 根据平台自动选择最佳的IO多路机制,比如linux就会选择epoll,windows会选择select
    sel = selectors.DefaultSelector()
    
    def accept(sock, mask):
        # 建立客户端连接
        conn, addr = sock.accept()
        print('accepted', conn, 'from', addr)
        # 设置非阻塞模式
        conn.setblocking(False)
        # 再次注册一个连接,将其加入监测列表中,
        sel.register(conn, selectors.EVENT_READ, read)
    
    def read(conn, mask):
        try:   # 抛出客户端强制关闭的异常(如手动关闭客户端黑窗口)
            data = conn.recv(1000)  # Should be ready
            if data:
                print('echoing', repr(data), 'to', conn)
                conn.send(data)  # Hope it won't block
            else:
                print('Client closed.', conn)
                # 将conn从监测列表删除
                sel.unregister(conn)
                conn.close()
        except ConnectionResetError:
            print('Client forcibly closed.', conn)
            # 将conn从监测列表删除
            sel.unregister(conn)
            conn.close()
    
    # 创建socket对象
    sock = socket.socket()
    
    # 绑定端口,设置监听
    sock.bind(('localhost', 1234))
    sock.listen(100)
    
    # 设置为非阻塞模式
    sock.setblocking(False)
    
    # 注册一个文件对象,监测它的IO事件,data是和文件对象相关的数据(此处放置了一个 accept 函数的内存地址)
    # register(fileobj, events, data=None)
    sel.register(sock, selectors.EVENT_READ, accept)
    
    while True:
        '''
        sel.select()
        看似是select方法,实际上会根据平台自动选择使用select还是epoll
        它返回一个(key, events)元组, key是一个namedtuple类型的元组,可以使用 key.name 获取元组的数据
        key 的内容(fileobj,fd,events,data):
            fileobj    已经注册的文件对象
            fd         也就是第一个参数的那个文件对象的更底层的文件描述符
            events     等待的IO事件
            data       可选项。可以存一些和fileobj有关的数据,如 sessioin 的 id
        '''
        events = sel.select()     # 监测有无活动对象,没有就阻塞在这里等待
        for key, mask in events:  # 有活动对象了
            callback = key.data     # key.data 是注册时传递的 accept 函数
            callback(key.fileobj, mask)   # key.fileobj 就是传递的 socket 对象

    客户端:

    import socket
    tin=socket.socket()
    tin.connect(('localhost',1234))
    while True:
        inp=input('>>>>')
        tin.send(inp.encode('utf8'))
        data=tin.recv(1024)
        print(data.decode('utf8'))

    参考:

    https://blog.csdn.net/zhangskd/article/details/6956638

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

  • 相关阅读:
    个人介绍
    C++ 之 第四课 C++中的运算符、表达式
    Delphi 之 第六课 过程与函数
    Delphi 之 第五课 流程语句
    VB 之 第三课 VB API 字体函数的应用
    C++ 之 第三课 C++数据类型
    Delphi 之 第四讲 自定义数据类型
    Delphi 之 第三课 详解数据类型
    C++ 之 第二课 C++类、函数的讲解
    VB API 第二课 之 字符串大小写转换
  • 原文地址:https://www.cnblogs.com/wztshine/p/12091062.html
Copyright © 2020-2023  润新知