1.socketserver介绍
socketserver
是标准库中的一个高级模块,socketserver
模块是python提供的内置的用于快捷开发服务端程序的一个服务器框架,通过封装大量实现的方式减少开发人员工作量的同时能快捷开发出具有较高质量的服务端程序;
该模块中类的继承关系如下:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
socketserver模块主要包含的非Unix服务器类有:TCPserver
、UCPserver
、ThreadingTCPServer(ThreadingMixIn, TCPServer)
、ThreadingUDPServer(ThreadingMixIn, UDPServer)
、ForkingTCPServer(ForkingMixIn, TCPServer)
、ForkingUDPServer(ForkingMixIn, UDPServer)
,其中的ThreadingMixIn、ForkingMixIn分别用来实现线程级别、进程级别的异步
2.Socketserver模块四个基本的服务器类分别是:
-
TCPServer
,基本的网络同步TCP服务器; -
UDPServer
,基本的网络同步UDP服务器; -
UnixStreamServer
,使用UNIX域套接字实现面向数据流协议的服务器,继承自TCPServer; -
UnixDatagramServer
,使用UNIX域套接字实现数据报协议的服务器,继承自UDPServer;
其中常用的是TCPServer
和UDPServer
,且这四个类型都是同步地处理请求,也就是说一个请求没有完成之前是不会处理下一个请求的,但是这种模式不适合生产环境。
所以这个模块还提供了两种支持异步处理的类,ForkingMixIn
和ThreadingMixIn
,继承自这两个类的服务端在处理新的客户端连接时不会阻塞,而是创建新的进程或线程来专门处理客户端的请求:
-
ForkingMixIn
,将UNIX进程分支添加到服务器的混合方法,使用该方法可以让服务器服务多个客户,(为每一个客户端请求派生一个新的进程去专门处理); -
ThreadingMixIn
,修改服务器的混合类,可以使用多线程服务多个客户端,(为每一个客户端请求派生一个新的线程去专门处理);
3.如何使用socketserver编写服务器端
我们先编写服务器端的代码,首先导入了socketserver模块,然后自定义一个Handler类,这个类是继承了socketserver模块中的BaseRequestHandler。
我们可以按住Ctrl键然后鼠标点击这个类,看一下这个类的方法有哪些,除了一个__init__()
方法,是不是还有三个没有实现的setup()、handle()、finish()方法呀,那这里就在Handler类中覆盖BaseRequestHandler类的handle()方法,打印一些信息。
然后选择一个合适的Server类,初始化且绑定地址和新建的Handler处理类,最后调用server实例的serve_forever()
方法,启动server。
import socketserver
import threading
from pprint import pprint
class Handler(socketserver.BaseRequestHandler):
def handle(self):
print('当前的server类型: {}'.format(self.server))
print('当前的socket连接对象: {}'.format(self.request))
print('当前的客户端地址: {}'.format(self.client_address))
print('线程列表: {}'.format(threading.enumerate()))
print('当前的线程:{}'.format(threading.current_thread()))
server = socketserver.TCPServer(('127.0.0.1', 9000), Handler)
server.serve_forever()
我们运行一下这个代码,发现控制台并没有输出,那我们可以到命令窗口查看一下TCP是否连接,命令行为:netstat -anp tcp | grep 9000
,然后显示的是正在监听并没有连接,我们可以直接使用浏览器请求9000端口,因为HTTP是在TCP之上实现的一种协议,然后在浏览器中访问127.0.0.1: 9000
,控制台中打印出很多的信息:
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63944)>
当前的客户端地址: ('127.0.0.1', 63944)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63945)>
当前的客户端地址: ('127.0.0.1', 63945)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63948)>
当前的客户端地址: ('127.0.0.1', 63948)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63949)>
当前的客户端地址: ('127.0.0.1', 63949)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63950)>
当前的客户端地址: ('127.0.0.1', 63950)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
当前的server类型: <socketserver.TCPServer object at 0x000000000277DD68>
当前的socket连接对象: <socket.socket fd=224, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 63951)>
当前的客户端地址: ('127.0.0.1', 63951)
线程列表: [<_MainThread(MainThread, started 7612)>]
当前的线程:<_MainThread(MainThread, started 7612)>
那我们总结一下,socketserver模块创建服务器步骤:
- 第一步:创建一个请求处理类继承于BaseRequestHandler类, 且覆盖父类的handler方法,重写的这个方法会处理传入的请求;
- 第二步: 实例化一个服务器类,传递这个服务器的地址和你第一步创建的请求处理类到你实例化的服务器;
- 第三步: 调用server实例的serve_forever()方法(可以处理多个请求)或者handle_request()方法(只处理一个请求)来处理服务器中的一个或者多个请求; 最后调用server_close()来关闭socket链接;
4.BaseRequestHandle类的方法介绍
-
setup()
:该方法在handle()之前调用,默认什么都不做,如果希望服务器实现更多连接设置,则无需调用该方法; -
handle()
:调用该方法执行实际的请求操作,调用函数可以不带任何参数,默认什么都不做; -
finish()
: 环境清理,在handle()之后执行清除操作,默认什么都不做,如果setup()和handle()方法都不生成异常,则无需调用该方法;
5.多客户端的socketserver实现
############################## 服务器端 ##############################
import socketserver
import threading
class Handler(socketserver.BaseRequestHandler):
clients = {}
def setup(self):
super().setup()
self.event = threading.Event()
self.clients[self.client_address] = self.request
def handle(self):
super().setup()
while not self.event.is_set():
data = self.request.recv(1024).decode()
if data == 'quit':
break
msg = "{} 说 {}".format(self.client_address, data).encode()
print(self.clients)
if self.client_address in self.clients.keys():
self.clients[self.client_address].send(msg)
# if request self.clients.values():
# request.send(msg)
print('server end')
def finish(self):
super().finish()
self.clients.pop(self.client_address)
self.event.set()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9000), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
while True:
cmd = input('请输入您想说的话: ').strip()
if cmd == 'quit':
print(cmd)
server.shutdown()
server.server_close()
break
else:
print('您可以输入quit来停止服务器! ')
############################## 客户端 ##############################
import socket
# 创建TCP连接
socket_instance = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_instance.connect(('127.0.0.1', 9000))
while True:
cmd = input("请输入您想说的话:")
socket_instance.send(cmd.encode())
data = socket_instance.recv(1024).decode()
print(data)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。