用select模块实现的socket server

简介:

socket服务端

之前笔记里面记录的比较乱,最后我写了一个类,试着封装成一个模块的样子。
使用的时候通过继承生成一个子类,然后调用run执行。
你应该需要重构其中的部分方法,另外可能还需要在子类中创建新的方法。
至少需要重构onrecv方法,接收到数据后的处理。
另外要发数据,调用send_data接口,把conn连接和bytes类型的data传入。

import logging
import queue
import select
import socket

class Server(object):
    def __init__(self, host, port, data_size=1024):
        self.host = host
        self.port = port
        self.data_size = data_size

        self.server = socket.socket()
        self.server.setblocking(False)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server.bind((self.host, self.port))
        self.server.listen()
        logging.critical("监听已经开启")

        self.inputs = [self.server, ]
        self.outputs = []
        self.data_queue = {}

    def run(self):
        while True:
            self.loop()

    def loop(self):
        """调用一次select并处理
        run方法中就是循环调用执行此方法
        可以重构此方法:
        调用执行父类中的这个方法后,加入一次循环中需要执行的其他步骤
        """
        readable, writeable, exceptional = select.select(self.inputs, self.outputs, self.inputs)
        logging.debug("select返回: {readable:%s, writeable:%s, exceptional:%s}"
                      % (readable, writeable, exceptional))
        for r in readable:
            if r is self.server:
                conn, addr = r.accept()
                logging.info("接收到新的客户端连接: {conn: %s, addr: %s}" % (conn, addr))
                conn.setblocking(False)
                self.inputs.append(conn)
                self.data_queue[conn] = queue.Queue()
            else:
                try:
                    data = r.recv(self.data_size)
                except ConnectionResetError as e:
                    logging.error("recv时捕获到异常: %s" % e)
                    self.clean(r)
                    if r in writeable: writeable.remove(r)
                except Exception as e:
                    logging.critical("recv时捕获未知异常: %s" % e)
                    self.clean(r)
                else:
                    if data:  # 如果有收到数据
                        logging.debug(data)
                        self.onrecv(r, data)
                    else:  # 可能是收到了空,就是客户端断开了
                        logging.info("客户端已断开: %s" % r)
                        self.clean(r)
                        # 只在writeable之前才从writeable列表中清除
                        if r in writeable: writeable.remove(r)

        for w in writeable:
            try:
                data = self.data_queue[w].get_nowait()
            except KeyError as e:  # 客户端突然断开,这个连接可能会同时出现在读和写列表中,而读中已经将它删掉了
                logging.error("获取消息队列时捕获到异常: %s" % e)
                # self.clean(w)  # 这里就是被删了才出的异常
            except queue.Empty:  # 如果队列空了才说明消息都发完了,从读列表中remove
                # if w in self.outputs: self.outputs.remove(w)
                self.outputs.remove(w)  # 这里应该不用判断,一定在列表里。因为如果不在会包KeyError
            except Exception as e:
                logging.critical("获取消息队列时捕获未知异常: %s" % e)
                self.clean(w)
            else:
                if data:
                    try:
                        w.sendall(data)  # 可能一次发不完,所以send之后不从读列表中remove。下次会发现队列为空
                    except ConnectionResetError as e:
                        logging.error("send时捕获到异常: %s" % e)
                    except Exception as e:
                        logging.critical("send时捕获未知异常: %s" % e)

        for e in exceptional:
            logging.critical("异常列表有返回: %s" % e)
            self.clean(e)

    def clean(self, conn):
        """清理客户端连接信息
        连接断开时处理以下4步
        1. 从读列表中去掉,不再去监听这个连接
        2. 从写列表中去掉,如果还有没发出的消息,那么也不再发了
        3. 关闭这个连接
        4. 从消息队列字典中中删除这个队列,可能还有未发送的消息
        可以重构此方法:
        如果还有其他在子类中定义的列表或字典需要清理,
        那么重构此方法,调用执行父类的次方法后,添加自定义的内容
        """
        if conn in self.inputs: self.inputs.remove(conn)
        if conn in self.outputs: self.outputs.remove(conn)
        conn.close()
        if conn in self.data_queue: del self.data_queue[conn]

    def onrecv(self, conn, data):
        """收到消息后调用执行此方法
        需要重构此方法
        否则是调用send_data方法,原样发回
        """
        self.send_data(conn, data)

    def send_data(self, conn, data):
        """发送数据"""
        if conn not in self.outputs: self.outputs.append(conn)
        self.data_queue[conn].put(data)

if __name__ == '__main__':
    Server('localhost', 9999).run()

测试客户端

下面是测试并发的客户端程序,通过多线程实现并发。

import socket
import threading

HOST = 'localhost'
PORT = 9999
def client(i):
    client = socket.socket()
    client.connect((HOST, PORT))
    for j in range(500):
        msg = "hello %s %s" % (i, j)
        client.send(msg.encode('utf-8'))
        data = client.recv(1024)
        print('Received:', data.decode('utf-8'))
    client.close()

if __name__ == '__main__':
    for i in range(50):
        t = threading.Thread(target=client, args=(i,))
        t.start()












本文转自骑士救兵51CTO博客,原文链接:http://blog.51cto.com/steed/2056177,如需转载请自行联系原作者
相关文章
|
网络协议 Python
python中socket模块的导入和使用基础
【4月更文挑战第3天】Python的`socket`模块是网络编程的基础,用于创建套接字、绑定地址和端口、监听连接及数据传输。首先,使用`import socket`导入模块。接着,通过`socket.socket()`创建套接字,指定地址族(如`AF_INET`)和类型(如`SOCK_STREAM`)。然后,使用`bind()`方法绑定地址和端口,`listen()`方法监听连接。服务器端通过`accept()`接受连接,`recv()`接收数据,`send()`发送响应。客户端则用`connect()`连接服务器,`send()`发送数据,`recv()`接收响应。
|
API C++
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(1)
前言   本文旨在学习socket网络编程这一块的内容,epoll是重中之重,后续文章写reactor模型是建立在epoll之上的。
335 0
|
监控 安全 Linux
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(3)
高并发服务器模型-poll poll介绍   poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。
309 0
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
1283 3
|
存储 网络协议 Linux
聊一聊 Python 的 socket,以及 select、poll、epoll 又是怎么一回事?
聊一聊 Python 的 socket,以及 select、poll、epoll 又是怎么一回事?
1002 2
|
关系型数据库 MySQL 数据库
docker启动mysql多实例连接报错Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’
docker启动mysql多实例连接报错Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’
1523 0
|
网络协议 Python
在Python中,我们使用`socket`模块来进行网络通信。首先,我们需要导入这个模块。
在Python中,我们使用`socket`模块来进行网络通信。首先,我们需要导入这个模块。
socket长连接的用处与模块图
socket长连接的用处与模块图
211 0
|
网络协议 Unix Linux
Python网络编程基础(Socket编程)select模块的使用
【4月更文挑战第12天】在网络编程中,IO操作(输入/输出操作)通常是性能瓶颈之一。为了提高程序的响应速度和吞吐量,我们可以采用非阻塞IO或异步IO来处理IO操作。这些技术可以使程序在等待IO操作时不会被阻塞,从而能够继续执行其他任务。
|
关系型数据库 MySQL 数据库
cant connect to local MySQL server through socket /tmp/mysql.sock (2)
cant connect to local MySQL server through socket /tmp/mysql.sock (2)
770 0
cant connect to local MySQL server through socket /tmp/mysql.sock (2)