前面几节我们写的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代码

import socketserver

class MyTCPHandler(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.
""" def handle(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代码,只能处理一个请求,比如运行如下客户端代码,通过结果你就会发现,处理第二个请求就会失败。

#Author:Zheng Na

# 客户端

import socket

client = socket.socket()
client.connect(('localhost',9999)) while True:
msg = input(">>: ").strip()
client.send(msg.encode("UTF-8")) data = client.recv(1024) # 接收1024字节
print("recv from server: ",data) client.close()

socketclient.py 客户端代码

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: aa
recv from server: b'AA'
>>: bb
Traceback (most recent call last):
File "D:/python-study/s14/Day08/socketclient.py", line 15, in <module>
data = client.recv(1024) # 接收1024字节
ConnectionAbortedError: [WinError 10053] 您的主机中的软件中止了一个已建立的连接。 Process finished with exit code 1

运行结果

实例2:处理多个请求

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

#Author:Zheng Na

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
while True:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# 下面3行代码防止服务器端随着客户端的断开而断开
# 经过测试发现,在linux有效,Windows无效。
if not self.data:
print(self.client_address,"断开了")
break
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()

socketserver2.py 可处理多个请求

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

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: aa
recv from server: b'AA'
>>: bb
recv from server: b'BB'
>>:
Process finished with exit code -1 D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver2.py
127.0.0.1 wrote:
b'aa'
127.0.0.1 wrote:
b'bb'
----------------------------------------
Exception happened during processing of request from ('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, in process_request
self.finish_request(request, client_address)
File "D:\software\Python3.6.5\lib\socketserver.py", line 361, in finish_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, in handle
self.data = self.request.recv(1024).strip()
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

Windows运行结果

实例3:

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

#Author:Zheng Na

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# 下面3行代码防止服务器端随着客户端的断开而断开
# 经过测试发现,在linux有效,Windows无效。
if not self.data:
print(self.client_address, "断开了")
break
self.request.send(self.data.upper())
except ConnectionResetError as e:
print('出现错误',e)
break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()

socketserver3.py linux & Windows运行正常

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: aa
recv from server: b'AA'
>>: bb
recv from server: b'BB'
>>:
Process finished with exit code -1 D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver3.py
127.0.0.1 wrote:
b'aa'
127.0.0.1 wrote:
b'bb'
出现错误 [WinError 10054] 远程主机强迫关闭了一个现有的连接。

Windows运行结果

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

实例4:ThreadingTCPServer 实现并发

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

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

class socketserver.ForkingTCPServer # 多进程

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer # 多线程

class socketserver.ThreadingUDPServer

so 只需要把下面这句

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

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

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

运行代码

#Author:Zheng Na

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# 下面3行代码防止服务器端随着客户端的断开而断开
# 经过测试发现,在linux有效,Windows无效。
if not self.data:
print(self.client_address, "断开了")
break
self.request.send(self.data.upper())
except ConnectionResetError as e:
print('出现错误',e)
break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()

socketserver4_thread_concurrence.py

输出结果

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketserver4_thread_concurrence.py
127.0.0.1 wrote:
b'from client1'
127.0.0.1 wrote:
b'from client2'
127.0.0.1 wrote:
b'from client3' D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: from client1
recv from server: b'FROM CLIENT1'
>>: D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: from client2
recv from server: b'FROM CLIENT2'
>>: D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/socketclient.py
>>: from client3
recv from server: b'FROM CLIENT3'
>>:

windows同时运行3个客户端

[root@hadoop my-test-files]# python3 socketserver4_thread_concurrence.py
127.0.0.1 wrote:
b'from client1'
127.0.0.1 wrote:
b'from client2'
127.0.0.1 wrote:
b'from client3' [root@hadoop my-test-files]# python3 socketclient.py
>>: from client1
recv from server: b'FROM CLIENT1'
>>: [root@hadoop my-test-files]# python3 socketclient.py
>>: from client2
recv from server: b'FROM CLIENT2'
>>: [root@hadoop my-test-files]# python3 socketclient.py
>>: from client3
recv from server: b'FROM CLIENT3'
>>:

linux同时运行3个客户端

实例5:ForkingTCPServer 实现并发

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

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

换成了下面这个

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

运行代码

#Author:Zheng Na

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# 下面3行代码防止服务器端随着客户端的断开而断开
# 经过测试发现,在linux有效,Windows无效。
if not self.data:
print(self.client_address, "断开了")
break
self.request.send(self.data.upper())
except ConnectionResetError as e:
print('出现错误',e)
break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()

socketserver5_fork_concurrence.py

输出结果

# 我的运行结果(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 <module>
server = socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler)
AttributeError: module 'socketserver' has no attribute 'ForkingTCPServer' Process finished with exit code 1 老师的运行结果:启动3个客户端后才报错
AttributeError: module 'os' has no attribute 'fork' 总之一句话,ForkingTCPServer在Windows上不好使

Windows 运行报错

[root@hadoop my-test-files]# python3 socketserver5_fork_concurrence.py
127.0.0.1 wrote:
b'from client1'
127.0.0.1 wrote:
b'from client2'
127.0.0.1 wrote:
b'from client3' [root@hadoop my-test-files]# python3 socketclient.py
>>: from client1
recv from server: b'FROM CLIENT1'
>>: [root@hadoop my-test-files]# python3 socketclient.py
>>: from client2
recv from server: b'FROM CLIENT2'
>>: [root@hadoop my-test-files]# python3 socketclient.py
>>: from client3
recv from server: b'FROM CLIENT3'
>>:

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

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

总结:TCPServer VS ThreadingTCPServer VS ForkingTCPServer

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

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

class socketserver.BaseServer(server_addressRequestHandlerClass) 主要有以下方法

class socketserver.BaseServer(server_address, RequestHandlerClass)
This is 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 and RequestHandlerClass 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 in the 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 class uses service_actions() to clean up zombie child processes. Changed in 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 or mixin classes to perform actions specific to a given service, such as cleanup actions. New in version 3.3. shutdown() # 告诉serve_forever()停止处理请求
Tell the serve_forever() loop to stop and wait 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 and socket.AF_UNIX. RequestHandlerClass # 请求处理类
The user-provided request handler class; an instance of this class is created for each 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), for example. socket # 套接字
The socket object on which the server will listen for incoming requests. The server classes support the following class variables: allow_reuse_address # 允许重用地址
Whether the server will allow the reuse of an address. This defaults to False, and can be set in subclasses 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 and socket.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 is called. 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 RequestHandlerClass and calling its handle() method. get_request()
Must accept a request from the socket, and return a 2-tuple containing the new socket object to be used to communicate with the client, and the client’s address. handle_error(request, client_address)
This function is called if the handle() method of a RequestHandlerClass instance raises an exception. The default action is to print the traceback to standard output and continue handling further requests. handle_timeout()
This function is 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 in threading 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 process or thread to handle the request; the ForkingMixIn and ThreadingMixIn classes do this. server_activate()
Called by the server’s constructor to activate the server. The default behavior for a 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)
Must return 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.
ThreadingTCPServer

Python3学习之路~8.5 SocketServer实现多并发的更多相关文章

  1. Python3学习之路~0 目录

    目录 Python3学习之路~2.1 列表.元组操作 Python3学习之路~2.2 简单的购物车程序 Python3学习之路~2.3 字符串操作 Python3学习之路~2.4 字典操作 Pytho ...

  2. Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

    作业: 开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  3. Python3学习之路~9.4 队列、生产者消费者模型

    一 队列queue 当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用. 队列的作用:1.解耦,使程序直接实现松耦合 2.提高处理效率 列表与队列都是有顺序的,但是他们之间有一个很大的区别 ...

  4. Python3学习之路

    python基础知识点 1.python基础知识点汇总 2.python常用数据类型 3.python之列表 4.python之字符串 5.python常用数据运算符 6.python之字典 7.py ...

  5. Python3学习之路~9.2 操作系统发展史介绍、进程与线程区别、线程语法、join、守护线程

    一 操作系统发展史介绍 参考链接:http://www.cnblogs.com/alex3714/articles/5230609.html 二 进程与线程 进程: 对各种资源管理的集合 就可以称为进 ...

  6. Python3学习之路~9.1 paramiko模块:实现ssh执行命令以及传输文件

    我们一般使用linux的时候,都是在Windows上安装一个ssh客户端连接上去.那么从一台linux如何连接到另一条linux呢?使用ssh命令即可,因为每台linux机器自己都有一个ssh客户端. ...

  7. Python3学习之路~8.4 利用socket实现文件传送+MD5校验

    利用socket实现文件传送,大约分为如下几步: 1.读取文件名2.检测文件是否存在3.打开文件(别忘了最后关闭文件)4.检测文件大小5.发送文件大小给客户端6.等客户端确认7.开始边读边发数据8.m ...

  8. Python3学习之路~7.5 异常处理

    1.异常基础 在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户,而是现实一个提示的页面,通俗来说就是不让用户看见大黄页!!! try: pass except Excepti ...

  9. Python3学习之路~5.12 hashlib & hmac & md5 & sha模块

    hashlib模块用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 import md5 h ...

随机推荐

  1. NOIP基本算法

    NOIP基本算法 1.二分 poj 2018 Best Cow Fences ▪ http://poj.org/problem?id=2018 ▪ 题意:给定一个正整数数列

  2. JVM 组成以及各部分作用

    1.Java虚拟机的体系结构 2.jvm在系统中的布局 3.jvm装载一个类 当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class ...

  3. Codeforces 750E New Year and Old Subsequence 线段树 + dp (看题解)

    New Year and Old Subsequence 第一感觉是离线之后分治求dp, 但是感觉如果要把左边的dp值和右边的dp值合起来, 感觉很麻烦而且时间复杂度不怎么对.. 然后就gun取看题解 ...

  4. Django搭建博客文章---模型层

    页面展示所需字段 1.文章标题---文本类型 2.文章摘要---文本类型 3.文章内容--文本类型 4.唯一的ID标记---int数字类型(自增.主键) 5.发布日期--日期类型 模型层定义字段 1. ...

  5. Selenium的webdriver的常用方法,鼠标事件

    就来认识 WebDriver 中最常用的几个方法: get():跳转到的地址clear(): 清除文本. send_keys (value): 模拟按键输入. click(): 单击元素. 示例: f ...

  6. vue v-cloak知识点

    1.使用 v-cloak 属性可以解决插值表达式闪烁问题;     2.v-text默认是没有闪烁的问题,同时会覆盖元素中原本的内容,但是v-cloak只会替换 自己的这个占位符,不会替换所有的字符 ...

  7. 使用httpclient访问NLP应用接口例子

    参考网址: http://yuzhinlp.com/docs.html 接入前须知 接入条件 1.进入网站首页,点击注册成为语知科技用户 2.注册完成后,系统将提供语知科技用户唯一标识APIKey,并 ...

  8. Kali Linux常用服务配置教程获取IP地址

    Kali Linux常用服务配置教程获取IP地址 下面以Kali Linux为例,演示获取IP地址的方法 (1)设置网络接口为自动获取IP地址.在Kali Linux的收藏夹中单击图标,将显示所有的程 ...

  9. rem实现移动端自适应页面

    一.把px转换成rem方案 1.cssrem插件 2.css预处理器 3.rem-unit插件 4.px2rem插件 rem就是相对于根元素的font-size来做计算,设置好根结点字体大小,子节点用 ...

  10. centos+git+gitolite 安装和部署

    一.部署环境 系统:CentOS 6.4x64 最小化安装 IP:192.168.52.131 git默认使用SSH协议,在服务器上基本上不用怎么配置就能直接使用.但是如果面向团队服务,需要控制权限的 ...