前面学习的例子都是单线程的 socket 收发; 如果有多个用户同时接入, 那么除了第一个连入的, 后面的都会处于挂起等待的状态, 直到当前连接的客户端断开为止.
通过使用 socketserver, 我们可以实现并发的连接.
socketserver 的使用很简单:
首先看个简单的例子
服务端:
自己定义一个类, 继承 socketserver.baserequesthandler;
然后定义一个方法 handle()
然后通过 socketserver.threadingTCPServer 指定套接字和自己定义的类, 每次当客户端连入的时候, 会自动实例化一个对象, 然后通过 server_forever()不断循环读写数据.
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- # Author Yuan Li
- import socketserver
- class mysocketserver(socketserver.BaseRequestHandler):
- def handle(self):
- conn = self.request
- conn.sendall(bytes("Welcome to the Test system.", encoding='utf-8'))
- while True:
- try:
- data = conn.recv(1024)
- if len(data) == 0: break
- print("[%s] sends %s" % (self.client_address, data.decode()))
- conn.sendall(data.upper())
- except Exception:
- break
- if __name__ == '__main__':
- server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), mysocketserver)
- server.serve_forever()
客户端:
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- # Author Yuan Li
- import socket
- ip_port = ('127.0.0.1', 8009)
- s = socket.socket()
- s.connect(ip_port)
- data = s.recv(1024)
- print(data.decode())
- while True:
- send_data = input("Data>>>")
- s.send(bytes(send_data, encoding='utf-8'))
- recv_data = s.recv(1024)
- print(recv_data.decode())
上面的效果是多个客户端可以同时连入服务器, 输入字母, 返回大写字母.
客户端没啥好说的, 这个和单线程的操作一样; 但是服务器咋一看很混乱. 我们可以通过剖析源码来弄清他的执行过程.
这个类的基本结构是如下所示的, 我们按照顺序来跑一次看看他怎么调用的
1. 首先执行的这句话, 很明显 ThredingTCPServer 是一个类, 点进去看看他的实例化过程
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), mysocketserver)
2. 点着 Ctrl 键, 点击这个类, PyCharm 会自动打开对应的源码, 可以看见这个类又继承了两个父类 ThredingMixIn 和 TCPServer
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
因为他的内容是 pass, 啥也没做, 根据继承的顺序, 我们继续往上 (从左到右) 找 init 构造函数;
3. ThreadingMixIn 里面没有构造函数, 那就继续往右找, TCPServer 里面倒是有构造函数, 但是他又调用了他父类 BaseServer 的构造函数, 顺着看上去, 发现他就是封装了几个值在里面, 注意 self.RequestHandlerClass = RequestHandlerClass 把我们自己定义的类传进去了
- def __init__(self, server_address, RequestHandlerClass):
- """Constructor. May be extended, do not override."""
- self.server_address = server_address
- self.RequestHandlerClass = RequestHandlerClass
- self.__is_shut_down = threading.Event()
- self.__shutdown_request = False
4. 接下来, 在 TCPServer 的构造函数里面, 他执行了 bind,listen 的操作, 这个和单线程的操作是一样的. 到此为止, 一个初始化的过程基本就完成了.
5. 接下来, 执行了 server.serve_forever()的操作, 我们看看内部是怎么调用的. 在这个函数里面, 使用了 selector 的 IO 多路复用的技术, 循环的读取一个文件的操作. 接着调用了_handle_request_noblock()函数
- try:
- # XXX: Consider using another file descriptor or connecting to the
- # socket to wake this up instead of polling. Polling reduces our
- # responsiveness to a shutdown request and wastes CPU at all other
- # times.
- with _ServerSelector() as selector:
- selector.register(self, selectors.EVENT_READ)
- while not self.__shutdown_request:
- ready = selector.select(poll_interval)
- if ready:
- self._handle_request_noblock()
- self.service_actions()
6. 每次调用函数的时候都记住查找的顺序, 从下往上, 从左往右, 最后在最上面的 BaseServer 再次找到这个函数, 这个函数里面又调用了 process_request 函数
- """
- try:
- request, client_address = self.get_request()
- except OSError:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- 一定要记住继承的顺序!! 顺序!! 顺序!!
- 因为 baseserver 自己有 process_request 的方法, ThreadingTCPServer 也有同名的方法, 当他调用的时候, 按照顺序, 是执行的 ThreadingTCPServer 里面的方法!!
- 可以看见他开了一个多线程
- def process_request(self, request, client_address):
- """Start a new thread to process the request."""
- t = threading.Thread(target = self.process_request_thread,
- args = (request, client_address))
- t.daemon = self.daemon_threads
- t.start()
- 在他调用的 process_request_thread 里面, 他又调用了 finsih_request
- def process_request_thread(self, request, client_address):
- """Same as in BaseServer but as a thread.
- In addition, exception handling is done here.
- """
- try:
- self.finish_request(request, client_address)
- self.shutdown_request(request)
- except:
- self.handle_error(request, client_address)
- self.shutdown_request(request)
- finish_request 里面有对我们自定义的类做了一个实例化的操作
- def finish_request(self, request, client_address):
- """Finish one request by instantiating RequestHandlerClass."""
- self.RequestHandlerClass(request, client_address, self)
因为我们自定义的类没有构造函数, 他会去父类寻找, 父类里面会尝试执行 handle()方法, 这就是为什么我们需要在自定义的类里面定义一个同名的方法, 然后把所有需要执行的内容都放在这里.
- def __init__(self, request, client_address, server):
- self.request = request
- self.client_address = client_address
- self.server = server
- self.setup()
- try:
- self.handle()
到此, socketserver 一个完整的过程就结束了
来源: http://www.bubuko.com/infodetail-3474827.html