python3 socketserver_Python3学习之路~8.5 SocketServer实现多并发

前面几节我们写的socket都只能实现服务端与一个客户端通信,并不能实现服务端与多客户端同时通信。接下来我们就来学习一下如何实现服务端同时与多个客户端通信,即并发。

Socket Server

socketserver就是对socket的一个再封装,主要功能就是实现并发。

socketserver模块简化了编写网络服务器的任务。

socketserver一共有以下4种类型:

class socketserver.TCPServer(server_address,RequestHandlerClass,bind_and_activate = True)

它使用Internet TCP协议,该协议在客户端和服务器之间提供连续的数据流。

class socketserver.UDPServer(server_address,RequestHandlerClass,bind_and_activate = True)

它使用数据报,这些数据报是可能无序到达或在传输过程中丢失的离散信息包。参数与TCPServer相同。

class socketserver.UnixStreamServer(server_address,RequestHandlerClass,bind_and_activate = True)

class socketserver.UnixDatagramServer(server_address,RequestHandlerClass,bind_and_activate = True)

这些是不经常使用的 类似于TCP和UDP类 的类,但使用Unix域套接字。它们不适用于非Unix平台,参数与TCPServer相同。

如下继承图中有五个类,并且分别显示了他们的继承关系,其中四个代表四种类型的同步服务器:

+------------+|BaseServer|+------------+|v+-----------++------------------+|TCPServer|------->|UnixStreamServer|+-----------++------------------+|v+-----------++--------------------+|UDPServer|------->|UnixDatagramServer|+-----------++--------------------+

注意:它们都继承了同一个基类:BaseServer。

创建一个 socketserver 至少分以下几步:

1.自己创建一个请求处理类,并且这个类要继承BaseRequestHandler类,并且还要重写父亲类里的handle()方法,此方法将用来处理传入的请求,即跟客户端所有的交互都是在handle()里完成的。

2.实例化一个SocketServer类(4种类型选其1,比如TCPServer),并且传递server address和 你上面创建的请求处理类 给这个SocketServer。

3.调用SocketServer对象的handle_request()或者serve_forever()方法来处理一个或多个请求。

server.handle_request() # 只能处理一个请求,因为处理完一个就退出了,一般不用它

server.serve_forever() # 可处理多个请求,因为永远执行

4.调用server_close()来关闭套接字。

实例1:基本的socketserver代码

ContractedBlock.gif

ExpandedBlockStart.gif

importsocketserverclassMyTCPHandler(socketserver.BaseRequestHandler):"""The request handler class for our server.

It is instantiated once per connection to the server, and must

override the handle() method to implement communication to the

client."""

defhandle(self):#self.request is the TCP socket connected to the client

self.data = self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)#just send back the same data, but upper-cased

self.request.send(self.data.upper())if __name__ == "__main__":

HOST, PORT= "localhost", 9999

#Create the server, binding to localhost on port 9999

server =socketserver.TCPServer((HOST, PORT), MyTCPHandler)#Activate the server; this will keep running until you

#interrupt the program with Ctrl-C

server.serve_forever()

socketserver1.py

上面是一个最简单的socketserver代码,只能处理一个请求,比如运行如下客户端代码,通过结果你就会发现,处理第二个请求就会失败。

ContractedBlock.gif

ExpandedBlockStart.gif

#Author:Zheng Na

#客户端

importsocket

client=socket.socket()

client.connect(('localhost',9999))whileTrue:

msg= input(">>:").strip()

client.send(msg.encode("UTF-8"))

data= client.recv(1024) #接收1024字节

print("recv from server:",data)

client.close()

socketclient.py 客户端代码

ContractedBlock.gif

ExpandedBlockStart.gif

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: aa

recvfrom server: b'AA'

>>: bb

Traceback (most recent call last):

File"D:/python-study/s14/Day08/socketclient.py", line 15, in data= client.recv(1024) #接收1024字节

ConnectionAbortedError: [WinError 10053] 您的主机中的软件中止了一个已建立的连接。

Process finished with exit code1

运行结果

实例2:处理多个请求

如果想要它处理多个请求,那么就要自己在handle()方法中加循环,跟前面我们学的socket实例一样。

ContractedBlock.gif

ExpandedBlockStart.gif

#Author:Zheng Na

importsocketserverclassMyTCPHandler(socketserver.BaseRequestHandler):defhandle(self):whileTrue:

self.data= self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)#下面3行代码防止服务器端随着客户端的断开而断开

#经过测试发现,在linux有效,Windows无效。

if notself.data:print(self.client_address,"断开了")breakself.request.send(self.data.upper())if __name__ == "__main__":

HOST, PORT= "localhost", 9999

#Create the server, binding to localhost on port 9999

server =socketserver.TCPServer((HOST, PORT), MyTCPHandler)#Activate the server; this will keep running until you

#interrupt the program with Ctrl-C

server.serve_forever()

socketserver2.py 可处理多个请求

上段代码你在linux运行是没问题的,但是在Windows上面运行的话,一旦你关闭了客户端,服务端就会报错ConnectionResetError

ContractedBlock.gif

ExpandedBlockStart.gif

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: aa

recvfrom server: b'AA'

>>: bb

recvfrom server: b'BB'

>>:

Process finished with exit code-1D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver2.py127.0.0.1wrote:

b'aa'

127.0.0.1wrote:

b'bb'

----------------------------------------Exception happened during processing of requestfrom ('127.0.0.1', 53532)

Traceback (most recent call last):

File"D:\software\Python3.6.5\lib\socketserver.py", line 317, in_handle_request_noblock

self.process_request(request, client_address)

File"D:\software\Python3.6.5\lib\socketserver.py", line 348, inprocess_request

self.finish_request(request, client_address)

File"D:\software\Python3.6.5\lib\socketserver.py", line 361, infinish_request

self.RequestHandlerClass(request, client_address, self)

File"D:\software\Python3.6.5\lib\socketserver.py", line 696, in __init__self.handle()

File"D:/python-study/s14/Day08/socketserver2.py", line 9, inhandle

self.data= self.request.recv(1024).strip()

ConnectionResetError: [WinError10054] 远程主机强迫关闭了一个现有的连接。

Windows运行结果

实例3:

如果想要在Windows上运行不报错,我们可以尝试主动抓住这个错误。

ContractedBlock.gif

ExpandedBlockStart.gif

#Author:Zheng Na

importsocketserverclassMyTCPHandler(socketserver.BaseRequestHandler):defhandle(self):whileTrue:try:

self.data= self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)#下面3行代码防止服务器端随着客户端的断开而断开

#经过测试发现,在linux有效,Windows无效。

if notself.data:print(self.client_address, "断开了")breakself.request.send(self.data.upper())exceptConnectionResetError as e:print('出现错误',e)break

if __name__ == "__main__":

HOST, PORT= "localhost", 9999server=socketserver.TCPServer((HOST, PORT), MyTCPHandler)

server.serve_forever()

socketserver3.py linux & Windows运行正常

ContractedBlock.gif

ExpandedBlockStart.gif

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: aa

recvfrom server: b'AA'

>>: bb

recvfrom server: b'BB'

>>:

Process finished with exit code-1D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver3.py127.0.0.1wrote:

b'aa'

127.0.0.1wrote:

b'bb'出现错误 [WinError10054] 远程主机强迫关闭了一个现有的连接。

Windows运行结果

此时,你就可以实现关闭一个客户端后,继续重新打开另一个客户端向服务器发送数据。

实例4:ThreadingTCPServer 实现并发

但你发现,上面的代码,依然不能同时处理多个连接,哎?那我搞这个干嘛?别急,不是不能处理多并发,如果你想,你还要启用多线程,多线程我们现在还没学,但你大体知道,有了多线程,就能同时让cpu干多件事了。

让你的socketserver并发起来, 必须选择使用以下一个多并发的类

classsocketserver.ForkingTCPServer # 多进程classsocketserver.ForkingUDPServerclasssocketserver.ThreadingTCPServer # 多线程class socketserver.ThreadingUDPServer

so 只需要把下面这句

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

换成下面这个,就可以多并发了,这样,客户端每连进一个来,服务器端就会分配一个新的线程来处理这个客户端的请求

server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

运行代码

ContractedBlock.gif

ExpandedBlockStart.gif

#Author:Zheng Na

importsocketserverclassMyTCPHandler(socketserver.BaseRequestHandler):defhandle(self):whileTrue:try:

self.data= self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)#下面3行代码防止服务器端随着客户端的断开而断开

#经过测试发现,在linux有效,Windows无效。

if notself.data:print(self.client_address, "断开了")breakself.request.send(self.data.upper())exceptConnectionResetError as e:print('出现错误',e)break

if __name__ == "__main__":

HOST, PORT= "localhost", 9999server=socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

server.serve_forever()

socketserver4_thread_concurrence.py

输出结果

ContractedBlock.gif

ExpandedBlockStart.gif

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver4_thread_concurrence.py127.0.0.1wrote:

b'from client1'

127.0.0.1wrote:

b'from client2'

127.0.0.1wrote:

b'from client3'D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: fromclient1

recvfrom server: b'FROM CLIENT1'

>>:

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: fromclient2

recvfrom server: b'FROM CLIENT2'

>>:

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py>>: fromclient3

recvfrom server: b'FROM CLIENT3'

>>:

windows同时运行3个客户端

ContractedBlock.gif

ExpandedBlockStart.gif

[root@hadoop my-test-files]#python3 socketserver4_thread_concurrence.py

127.0.0.1wrote:

b'from client1'

127.0.0.1wrote:

b'from client2'

127.0.0.1wrote:

b'from client3'[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient1

recvfrom server: b'FROM CLIENT1'

>>:

[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient2

recvfrom server: b'FROM CLIENT2'

>>:

[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient3

recvfrom server: b'FROM CLIENT3'

>>:

linux同时运行3个客户端

实例5:ForkingTCPServer 实现并发

同实例4类似,只是这次将下面这句

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

换成了下面这个

server = socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler)

运行代码

ContractedBlock.gif

ExpandedBlockStart.gif

#Author:Zheng Na

importsocketserverclassMyTCPHandler(socketserver.BaseRequestHandler):defhandle(self):whileTrue:try:

self.data= self.request.recv(1024).strip()print("{} wrote:".format(self.client_address[0]))print(self.data)#下面3行代码防止服务器端随着客户端的断开而断开

#经过测试发现,在linux有效,Windows无效。

if notself.data:print(self.client_address, "断开了")breakself.request.send(self.data.upper())exceptConnectionResetError as e:print('出现错误',e)break

if __name__ == "__main__":

HOST, PORT= "localhost", 9999server=socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler)

server.serve_forever()

socketserver5_fork_concurrence.py

输出结果

ContractedBlock.gif

ExpandedBlockStart.gif

#我的运行结果(Windows Python3.6.5):一运行就报错

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver5_fork_concurrence.py

Traceback (most recent call last):

File"D:/python-study/s14/Day08/socketserver5_fork_concurrence.py", line 27, in server=socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler)

AttributeError: module'socketserver' has no attribute 'ForkingTCPServer'Process finished with exit code1老师的运行结果:启动3个客户端后才报错

AttributeError: module'os' has no attribute 'fork'总之一句话,ForkingTCPServer在Windows上不好使

Windows 运行报错

ContractedBlock.gif

ExpandedBlockStart.gif

[root@hadoop my-test-files]#python3 socketserver5_fork_concurrence.py

127.0.0.1wrote:

b'from client1'

127.0.0.1wrote:

b'from client2'

127.0.0.1wrote:

b'from client3'[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient1

recvfrom server: b'FROM CLIENT1'

>>:

[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient2

recvfrom server: b'FROM CLIENT2'

>>:

[root@hadoop my-test-files]#python3 socketclient.py

>>: fromclient3

recvfrom server: b'FROM CLIENT3'

>>:

linux同时运行3个客户端 成功

注意:ForkingTCPServer在Windows上不好使,在linux上百分比好使(效果与ThreadingTCPServer一模一样)。

总结:TCPServer VS ThreadingTCPServer VS ForkingTCPServer

TCPServer是接收到请求后执行handle方法,如果前一个的handle没有结束,那么其他的请求将不会受理,新的客户端也无法加入。

而ThreadingTCPServer和ForkingTCPServer则允许前一连接的handle未结束也可受理新的请求和连接新的客户端,区别在于前者用建立新线程的方法运行handle,后者用新进程的方法运行handle。

classsocketserver.BaseServer(server_address, RequestHandlerClass) 主要有以下方法

ContractedBlock.gif

ExpandedBlockStart.gif

classsocketserver.BaseServer(server_address, RequestHandlerClass)

Thisis the superclass of all Server objects in the module. It defines the interface, given below, but does not implement most of the methods, which is done in subclasses. The two parameters are stored in the respective server_address andRequestHandlerClass attributes.

fileno()#返回文件描述符,这个一般是系统内部调用时用到的,我们一般用不到,知道即可

Return an integer file descriptor for the socket on which the server is listening. This function is most commonly passed to selectors, to allow monitoring multiple servers inthe same process.

handle_request()#处理单个请求,我们一般也不用

Process a single request. This function calls the following methods in order: get_request(), verify_request(), and process_request(). If the user-provided handle() method of the handler class raises an exception, the server’s handle_error() method will be called. If no request is received within timeout seconds, handle_timeout() will be called and handle_request() will return.

serve_forever(poll_interval=0.5) #一直处理请求,直到收到一个明确的shutdown()请求。每0.5秒检查一下是否有程序给我发了shutdown的信号。

Handle requests until an explicit shutdown() request. Poll for shutdown every poll_interval seconds. Ignores the timeout attribute. It also calls service_actions(), which may be used by a subclass or mixin to provide actions specific to a given service. For example, the ForkingMixIn classuses service_actions() to clean up zombie child processes.

Changedin version 3.3: Added service_actions call to the serve_forever method.

service_actions()#Python3.3引入,被serve_forever()调用。

This is called in the serve_forever() loop. This method can be overridden by subclasses ormixin classes to perform actions specific to a given service, such as cleanup actions.

Newin version 3.3.

shutdown()#告诉serve_forever()停止处理请求

Tell the serve_forever() loop to stop andwait until it does.

server_close()#关闭

Clean up the server. May be overridden.

address_family#地址簇

The family of protocols to which the server’s socket belongs. Common examples are socket.AF_INET andsocket.AF_UNIX.

RequestHandlerClass#请求处理类

The user-provided request handler class; an instance of this class is created foreach request.

server_address#地址

The address on which the server is listening. The format of addresses varies depending on the protocol family; see the documentation for the socket module for details. For Internet protocols, this is a tuple containing a string giving the address, and an integer port number: ('127.0.0.1', 80), forexample.

socket#套接字

The socket object on which the server will listen forincoming requests.

The server classes support the followingclassvariables:

allow_reuse_address#允许重用地址

Whether the server will allow the reuse of an address. This defaults to False, and can be set insubclasses to change the policy.

request_queue_size#暂时不用管它

The size of the request queue. If it takes a long time to process a single request, any requests that arrive while the server is busy are placed into a queue, up to request_queue_size requests. Once the queue is full, further requests from clients will get a “Connection denied” error. The default value is usually 5, but this can be overridden by subclasses.

socket_type#协议类型

The type of socket used by the server; socket.SOCK_STREAM andsocket.SOCK_DGRAM are two common values.

timeout#超时时间,在handle_request()中使用,由于我们不同handle_request(),所以不用管它

Timeout duration, measured in seconds, or None if no timeout is desired. If handle_request() receives no incoming requests within the timeout period, the handle_timeout() method iscalled.

There are various server methods that can be overridden by subclasses of base server classes like TCPServer; these methods aren’t useful to external users of the server object.

finish_request()

Actually processes the request by instantiating RequestHandlerClassandcalling its handle() method.

get_request()

Must accept a requestfrom the socket, and return a 2-tuple containing the new socket object to be used to communicate with the client, andthe client’s address.

handle_error(request, client_address)

This functionis called if the handle() method of a RequestHandlerClass instance raises an exception. The default action is to print the traceback to standard output and continuehandling further requests.

handle_timeout()

This functionis called when the timeout attribute has been set to a value other than None and the timeout period has passed with no requests being received. The default action for forking servers is to collect the status of any child processes that have exited, while inthreading servers this method does nothing.

process_request(request, client_address)

Calls finish_request() to create an instance of the RequestHandlerClass. If desired, this function can create a new processor thread to handle the request; the ForkingMixIn andThreadingMixIn classes do this.

server_activate()

Called by the server’s constructor to activate the server. The default behaviorfora TCP server just invokes listen() on the server’s socket. May be overridden.

server_bind()

Called by the server’s constructor to bind the socket to the desired address. May be overridden.

verify_request(request, client_address)

Mustreturn a Boolean value; if the value is True, the request will be processed, and if it’s False, the request will be denied. This function can be overridden to implement access controls for a server. The default implementation always returns True.

View Code

ThreadingTCPServer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值