网络编程socket 结合IO多路复用select epool 机制实现单线程并发TCP服务器

select版-TCP服务器

1. select 原理

在多路复用的模型中,比较常用的有select模型和epoll模型。这两个都是系统接口,由操作系统提供。当然,Python的select模块进行了更高级的封装。

网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。

这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。

2、select版本基于socket模块的TCP并发服务器代码示例

说明:服务端采用socket.AF_INET socket.SOCK_STREAM ;即IP/TCP协议

    # 使用select ,阻塞等待监控哪些套接字有新数据
    # select模块的select()是个函数,接收四个参数rlist, wlist, xlist, timeout=None
    # 分别表示准备监控读套接字列表,准备监控写套接字列表,准备监控异常套接字列表,超时时间
    # 返回值为可读套接字列表,可写套接字列表, 异常套接字列表
    # 有任何套接字有变化,就会有返回值,否则就一直阻塞住
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#  @Time: 2020/7/4 16:56
#  @Author:zhangmingda
#  @File: socket_select_study.py
#  @Software: PyCharm
#  Description:select io多路复用实现单线程并发TCP服务器

import socket
import sys
import select

listenAddr = ('', 8080)
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpServer.bind(listenAddr)
tcpServer.listen(5) # 设置可以并发建立的连接数

# 准备让select 模块监控是否有数据可以收的套接字列表
inputs = [tcpServer, ] # sys.stdin linux下还可以加入sys.stdin作为监控键盘的输入
# 准备一个装已建立连接的字典,键为连接实例,值为对应的客户端IP地址信息
established = {}

running = True
while True:
    # 使用select ,阻塞等待监控哪些套接字有新数据
    # select模块的select()是个函数,接收四个参数rlist, wlist, xlist, timeout=None
    # 分别表示准备监控读套接字列表,准备监控写套接字列表,准备监控异常套接字列表,超时时间
    # 返回值为可读套接字列表,可写套接字列表, 异常套接字列表
    # 有任何套接字有变化,就会有返回值,否则就一直阻塞住
    print("使用select IO多路负用监控套接字状态")
    readable, writeable, exceptional = select.select(inputs, [], [])
    print("注意:被监控的套接字有变化")

    # 循环判断收数据的套接字是否有数据到达
    for sock in readable:
        # 当收到数据的套接字为tcpServer时,说明时新的一个客户端到了
        if sock == tcpServer:
            # 获取套接字中活动的连接对象和客户端地址
            conn, addr = tcpServer.accept()
            # 将活动的套接字对象添加到监控列表中
            inputs.append(conn)
            print("建立了一个新TCP连接:", addr)
            established[conn] = addr

        # 收到数据的时 标准输入,即键盘
        elif sock == sys.stdin:
            # 获取输入内容
            cmd = sys.stdin.readline()
            print("获取到键盘输入指令:%s 退出" % cmd)
            running = False
            break


        # 收到的数据不是上面两个,则肯定时已建立链接的套接字有新数据或者断开连接了
        else:
            # 读取客户端发来的数据
            data = sock.recv(1024)
            # 如果数据存在,就原封返回去:做个echo服务器
            if data:
                print("收到并返回:",data.decode('gb2312'))
                sock.send(data)
            # 如果不存在数据,说明连接状态异常了,断开连接并从监控列表移除连接对象
            else:
                inputs.remove(sock)
                sock.close()
                # 移除记录的客户端地址信息
                print(established.get(sock), "客户已断开")
                established.pop(sock)

    # while 循环必须有个跳出循环的条件,否则while下面的代码有获取不到while上面变量的风险
    if not running:
        break

tcpServer.close()

客户端使用"网络调试助手.exe"发包

网络编程socket 结合IO多路复用select epool 机制实现单线程并发TCP服务器

 

windows 下pycharm服务端输出效果:

 网络编程socket 结合IO多路复用select epool 机制实现单线程并发TCP服务器

 

 Linux下监控键盘输入结果

网络编程socket 结合IO多路复用select epool 机制实现单线程并发TCP服务器

 

上一篇:linux新安装了php,但是使用mysqli连接数据库一直超时


下一篇:Socket网络编程,实现server/client