Python使用socketServer包搭建简易服务器过程详解
官方提供了socketserver包去方便我们快速的搭建一个服务器框架。
server类
socketserver包提供5个Server类,这些单独使用这些Server类都只能完成同步的操作,他是一个单线程的,不能同时处理各个客户端的请求,只能按照顺序依次处理。
+------------+ |BaseServer| +------------+ | v +-----------++------------------+ |TCPServer|------->|UnixStreamServer| +-----------++------------------+ | v +-----------++--------------------+ |UDPServer|------->|UnixDatagramServer| +-----------++--------------------+
两个Mixin类
+--------------++----------------+ |ForkingMixIn||ThreadingMixIn| +--------------++----------------+
各自实现了多进程和多线程的功能(ForkingMixIn在Windows不支持)
于是将这些同步类和Mixin类组合就实现了异步服务类的效果。
classThreadingUDPServer(ThreadingMixIn,UDPServer):pass
classThreadingTCPServer(ThreadingMixIn,TCPServer):passclassForkingUDPServer(ForkingMixIn,UDPServer):pass
classForkingTCPServer(ForkingMixIn,TCPServer):pass
基本使用
由于server需要同时处理来自多个客户端的请求,需要提供异步的支持,所以通常使用上面的异步类创建服务器。在Windows系统中没有提供os.fork()接口,Windows无法使用多进程的ForkingUDPServer和ForkingTCPServer,只能使用ThreadingTCPServer或者ThreadingUDPServer;而Linux和Unix多线程和多进程版本都可以使用。
服务器主要负责接受客户端的连接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。
importsocketserver
#创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server=socketserver.ThreadingTCPServer(("127.0.0.1",8000),BaseRequestHandler)
server.serve_forever()#启动服务器,
只需要上面两行代码就可以创建开启一个服务,运行上面代码后常看本机8000端口,发现有程序正在监听。
C:\Users\user>netstat-anptcp|findstr8000
TCP127.0.0.1:80000.0.0.0:0LISTENING
ThreadingTCPServer可以对我们的请求进行接受,但是并不会进行处理请求,处理请求的类是上面指定BaseRequestHandler类,该类可以定义handle方法来处理接受的请求。
BaseRequestHandler的源码
classBaseRequestHandler: def__init__(self,request,client_address,server): self.request=request self.client_address=client_address self.server=server self.setup() try: self.handle() finally: self.finish() defsetup(self): pass defhandle(self): pass deffinish(self): pass
在server=socketserver.ThreadingTCPServer(("127.0.0.1",8000),BaseRequestHandler)中,BaseRequestHandler将作为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(连接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象便可以与客户端交互了。
简单示例
importsocketserver
importthreading
classMyRequestHandler(socketserver.BaseRequestHandler):
"""BaseRequestHandler的实例化方法中,获得了三个属性
self.request=request#该线程中与客户端交互的socket对象。
self.client_address#该线程处理的客户端地址
self.server=server#服务器对象
"""
defhandle(self):
whileTrue:
msg=self.request.recv()#接受客户端的数据
ifmsg==b"quit"ormsg=="":#退出
break
print(msg.decode())
self.request.send(msg)#将消息发送回客户端
deffinish(self):
self.request.close()#关闭套接字
if__name__=="__main__":
#创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server=socketserver.ThreadingTCPServer(("127.0.0.1",8000),MyRequestHandler)
server.serve_forever()#启动服务器
我们创建了一个ThreadingTCPServer服务器,然后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息返回客户端。最后我们在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码一定会执行。
上一篇使用socket实现了一个群聊服务器,这个里使用socketServer将更加方便的实现
classMyRequestHandle(BaseRequestHandler):
clients={}#在类属性中记录所有与客户端连接socket。
lock=threading.Lock()#互斥锁,各个线程共用
defsetup(self):#新的用户连接时,预处理,将这个新的连接加入到clients中,考虑线程安全,需要加锁
withself.lock:
self.clients[self.client_address]=self.request
defhandle(self):#处理客户端的请求主逻辑
whileTrue:
data=self.request.recv(1024).strip()#接受数据
ifdata==b"quit"ordata==b"":#客户端退出
withself.lock:
self.server.clients.pop(self.client_address)
self.request.close()
break
print("{}-{}:{}".format(*self.client_address,data.decode()))
withself.lock:
for_,cinself.server.clients.items():#群发
c.send(data)
deffinish(self):
withserver.lock:
for_,cinserver.clients.items():
c.close()
server.server_close()defmain():
server=ThreadingTCPServer(("127.0.0.1",8000),MyRequestHandle)
#将创建的所有线程设置为daemon线程,这样控台主程序退出时,这个服务器的所有线程将会被结束
server.daemon_threads=True
if__name__=="__main__":
main()
上面requestHandlerclass中的handle方法和finish方式对应了上一篇中TCP服务器的recv方法和stop方法,他们处理请求的逻辑是相同的。只是上面使用了socketserver的代码变少了,处理的逻辑也变少了,TCPserver帮我们完成了大量的工作,这利于软件的快速开发。
内置的两个RequestHandlerClass
StreamHandlerRequest
StreamHandlerRequest顾名思义是一种流式的求情处理类,对应TCP协议的面向字节流的传输形式。我们从源代码分析。(去除了一些次要代码)
classStreamRequestHandler(BaseRequestHandler):
rbufsize=-1#读缓存
wbufsize=0#写缓存
timeout=None#超时时间
#IP/TCP拥塞控制的Nagle算法算法。
disable_nagle_algorithm=False
defsetup(self):#实现了setup,
self.connection=self.request
ifself.timeoutisnotNone:
self.connection.settimeout(self.timeout)
ifself.disable_nagle_algorithm:
self.connection.setsockopt(socket.IPPROTO_TCP,
socket.TCP_NODELAY,True)
#使用makefile方法获得了一个只读文件对象rfile
self.rfile=self.connection.makefile('rb',self.rbufsize)
#获得一个只写的文件对象wfile
ifself.wbufsize==0:
self.wfile=_SocketWriter(self.connection)
else:
self.wfile=self.connection.makefile('wb',self.wbufsize)
deffinish(self):#负责将这个wfile和rfile方法关闭。
ifnotself.wfile.closed:
try:
self.wfile.flush()
exceptsocket.error:
pass
self.wfile.close()
self.rfile.close()
使用StreamRequestHandler方法可以将这个socket包装成一个类文件对象,方便我们使用一套文件对象的方法处理这个socket,它没有实现handle方法,我仍然需要我们实现。我们可以这样使用它
classMyHandle(StreamRequestHandler):
#如果需要使用setup和finish方法,需要调用父类方法,否则该方法将会被覆盖。
defsetup(self):
super().setup()
#添加自己的需求
defhandle(self):
#这里我们可以使用wfile和rfile来处理socket消息了,例如之前使用self.request.recv()方法等同于self.rfile.read()
#而self.wfile.write等同于self.request.send(),在handle方法中完成业务逻辑即可
deffinish(self):
super().finish()
server=ThreadingTCPServer("127.0.0.1",MyHandle)
server.serve_forever()
StreamRequestHandler主要定义了两个新的wfile对象和rfile对象,来分别对这个socket进行读写操作,当我们业务需要时,比如需要使用文件接口方法时,选择继承于StreamRequestHandler构建我们自己处理请求类来完成业务逻辑将会更加的方便。
DatagramRequestHandler
DatagramRequestHandler字面意思是数据报请求处理,也就是基于UDPServer的服务器才能使用该请求处理类
classDatagramRequestHandler(BaseRequestHandler): defsetup(self): fromioimportBytesIO #udp的self.request包含两部分(data,socket)它来自于 #data,client_addr=self.socket.recvfrom(self.max_packet_size) #return(data,self.socket),client_addr #(data,self.socket)就是这个self.request,在这里将其解构,data为recvfrom接收的数据 self.packet,self.socket=self.request #该数据包封装为BytesIO,同样为一个类文件对象。 self.rfile=BytesIO(self.packet) self.wfile=BytesIO() deffinish(self): self.socket.sendto(self.wfile.getvalue(),self.client_address)
从源码可以看出,DatagramRequestHandler将数据包封装为一个rfile,并实例化一个ByteIO对象用于写入数据,写入的数据可以通过self.socket这个套接字发送。这样可以使用rfile和wfile这两个类文件对象的read或者write接口来进行一些IO方面的操作。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。