概述

这篇文章是讲解如何使用socketserver建立一个异步TCP服务器,其中Python版本为3.5.1。

socketserver主要的类

socketserver模块中的类主要有以下几个:
1、BaseServer 包含服务器的核心功能与混合类(mix-in)的钩子功能。这个类主要用于派生,不要直接生成这个类的类对象,可以考虑使用TCPServer和UDPServer类。
2、TCPServer:基本的网络同步TCP服务器
3、UDPServer:基本的网络同步UDP服务器
4、ForkingMixIn:实现了核心的进程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
5、ThreadingMixIn:实现了核心的线程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
6、ForkingTCPServer: ForkingMixIn与TCPServer的组合
7、ForkingUDPServer:ForkingMixIn与UDPServer的组合
8、BaseRequestHandler:基本的请求处理类
9、StreamRequestHandler:TCP请求处理类的一个实现
10、DataStreamRequestHandler:UDP请求处理类的一个实现

BaseRequestHandler类

BaseRequestHandler类的实例h可以实现以下方法:

1、h.handle() 调用该方法执行实际的请求操作。调用该函数可以不带任何参数,但是几个实例变量包含有用的值。h.request包含请求,h.client_address包含客户端地址,h.server包含调用处理程序的实例。对于TCP之类的数据流服务,h.request属性是套接字对象。对于数据报服务,它是包含收到数据的字节字符串。

2、h.setup() 该方法在handle()之前调用。默认情况下,它不执行任何操作。如果希望服务器实现更多连接设置(如建立SSL连接),可以在这里实现。

3、h.finish() 调用本方法可以在执行完handle()之后执行清除操作。默认情况下,它不执行任何操作。如果setup()和handle()方法都不生成异常,则无需调用该方法。

官方例程

首先上官方给出的例程

[python] view plain copy

<span style="font-size:14px;">import socket  

import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
server.server_close()</span>

client函数是建立一个客户端,可以不用管它。主要部分是在于主函数,ThreadedTCPServer类和ThreadedTCPRequestHandler类。ThreadedTCPServer类继承了BaseRequestHandler类,ThreadedTCPRequestHandler继承了ThreadingMixIn和TCPServer

正常输入如下:

$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3

增加功能

上面部分主要是讲解官方的例程,下面这一部分是博主自己增加的功能。

1、获取客户端的ip和port

如果想在TCP建立连接后打印「<ip>:<port> is connect!」信息出来,并获取客户端的ip地址和端口信息,可以在ThreadedTCPRequestHandler类里面改写setup函数。
 [python] view plain copy

 client_addr = []  

 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  

     def setup(self):
ip = self.client_address[0].strip() # 获取客户端的ip
port = self.client_address[1] # 获取客户端的port
print(ip+":"+str(port)+" is connect!")
client_addr.append(self.client_address) # 保存到队列中 def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
 
在主函数中添加下面语句,即可打印出连接过的客户端信息:
 [python] view plain copy

 print("\nclient_addr:"+str(client_addr))  
 

2、保持TCP长连接

官方例程中是建立了TCP连接后就马上断开,如果想建立长连接,可以在handle函数中添加while循环,同时修改代码为:先判断缓冲区是否有数据,有数据才进行响应;改写finish函数,可以看到finish的信息并没有打印出来。如果注释掉while循环语句,可以看到finish的信息会打印出来。
 [python] view plain copy

 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  

     def setup(self):
ip = self.client_address[0].strip() # 获取客户端的ip
port = self.client_address[1] # 获取客户端的port
print(ip+":"+str(port)+" is connect!")
client_addr.append(self.client_address) # 保存到队列中 def handle(self):
while True: # while循环
data = str(self.request.recv(1024), 'ascii')
if data: # 判断是否接收到数据
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response) def finish(self):
print("client is disconnect!")
 
 
感谢评论区歇业的渔夫的建议,while True 循环建立长连接的方式非常占用CPU资源,最好在循环里面增加一个time.sleep(0.1)的休眠。

3、服务器给客户端发送请求

现在的例程是在ThreadedTCPRequestHandler类里面调用self.request.sendall方法来给客户端发送数据,而且只能被动发送数据,如果我想主动给客户端发送数据,又该怎么办呢?下面是实现服务器主动给客户端发送请求的功能。
 
TCP连接想要发送数据,只要找到相关的方法直接调用即可,于是我对ThreadedTCPServer这个类的实例server的方法找了好久,也没有找到发送的方法。后来我查资料注意到了一句话:「对于TCP之类的数据流服务,h.request属性是套接字对象。」我觉得我可以这样做:使用这个套接字对象发送数据。经过尝试后,验证成功。下面只放上核心代码:
 [python] view plain copy

 client_addr = []
client_socket = [] class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): def setup(self):
ip = self.client_address[0].strip() # 获取客户端的ip
port = self.client_address[1] # 获取客户端的port
print(ip+":"+str(port)+" is connect!")
client_addr.append(self.client_address) # 保存到队列中
client_socket.append(self.request) # 保存套接字socket def handle(self):
while True: # while循环
data = str(self.request.recv(1024), 'ascii')
if data: # 判断是否接收到数据
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response) def finish(self):
print("client is disconnect!")
client_addr.remove(self.client_address)
client_socket.remove(self.request)
 

之后在主函数中通过client_socket队列调用sendall或sendto方法即可。例如我在主函数这样写(已经注释掉client函数调用):

 [python] view plain copy

 message = bytes("clientTest\n", "ascii")
while True:
time.sleep(2)
if client_addr:
client_socket[0].sendall(message)
 

修改服务器ip地址为空及端口为8080,使用socket调试工具连接该服务器,即可每隔2s接收到「clientTest」字符串。

 
 

4、服务器接收客户端数据超时后断开

下面继续添加新的功能,假设客户端每隔一段时间发送数据给服务器(心跳包),如果在一定时间内服务器没有接受到心跳包,表明客户端已经断开了连接,这个时候服务器可以主动断开客户端的连接了。那么我们在原有的代码增加此功能。实际上,只需要修改ThreadedTCPRequestHandler类即可。
 [python] view plain copy

 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  

     ip = ""
port = 0
timeOut = 6 # 设置超时时间变量 def setup(self):
self.ip = self.client_address[0].strip() # 获取客户端的ip
self.port = self.client_address[1] # 获取客户端的port
self.request.settimeout(self.timeOut) # 对socket设置超时时间
print(self.ip+":"+str(self.port)+"连接到服务器!")
client_addr.append(self.client_address) # 保存到队列中
client_socket.append(self.request) # 保存套接字socket def handle(self):
while True: # while循环
try:
data = str(self.request.recv(1024), 'ascii')
except socket.timeout: # 如果接收超时会抛出socket.timeout异常
print(self.ip+":"+str(self.port)+"接收超时!即将断开连接!")
break # 记得跳出while循环 if data: # 判断是否接收到数据
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response) def finish(self):
print(self.ip+":"+str(self.port)+"断开连接!")
client_addr.remove(self.client_address)
client_socket.remove(self.request)
 

使用socket调试工具连接该服务器后,不发送任何数据,过了6秒钟后,服务器端主要打印如下数据:

192.168.10.53:26408连接到服务器!
192.168.10.53:26408接收超时!即将断开连接!
192.168.10.53:26408断开连接!

【Python】使用socketserver建立一个异步TCP服务器的更多相关文章

  1. 深入理解Tornado——一个异步web服务器

    本人的第一次翻译,转载请注明出处:http://www.cnblogs.com/yiwenshengmei/archive/2011/06/08/understanding_tornado.html原 ...

  2. 如何快速建立一个测试资源Web服务器及异步获取资源(Unity3D)

    背景 1.最近看了几位专栏作家的文章,几篇提到了资源通过网络的动态获取.如何建立一个快速的测试环境,不免是一个问题,也就最简单的就是假设http服务器了,微软系的当然首选的IIS了,别的也能用阿帕奇或 ...

  3. Swoole学习(七)Swoole之异步TCP服务器的创建

    环境:Centos6.4,PHP环境:PHP7 <?php //创建TCP服务器 /** * $host 是swoole需要监听的ip,如果要监听本地,不对外服务,那么就是127.0.0.1;如 ...

  4. netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器

    目录 简介 搭建netty服务器 DNS服务器的消息处理 DNS客户端消息请求 总结 简介 在前面的文章中,我们提到了使用netty构建tcp和udp的客户端向已经公布的DNS服务器进行域名请求服务. ...

  5. 06.swoole学习笔记--异步tcp服务器

    <?php //创建tcp服务器 $host='0.0.0.0'; $port=; $serv=new swoole_server($host,$port); //设置异步进程工作数 $serv ...

  6. 建立一个node.js服务器(使用express搭建第一个Web环境)

    一.官网下载node.js 下载地址:https://nodejs.org/en/download/ 根据向导,下一步安装就可以了! 二.使用express搭建Web环境 express是一个开源的n ...

  7. Socket 初识 用Socket建立一个简易Web服务器

    摘自<Asp.Net 本质论>作者:郝冠军 //在.Net中.system.Net命名空间提供了网络编程的大多数数据据类型以及常用操作,其中常用的类型如下: /* IPAddress 类表 ...

  8. 创建一个简单tcp服务器需要的流程

    1.socket创建一个套接字 2.bind绑定ip和port 3.listen使套接字变为可以被动链接 4.accept等待客户端的链接 5.recv/send接收发送数据

  9. Python网络编程(3)——SocketServer模块与简单并发服务器

    主要类型 该模块有四个比较主要的类,其中常用的是 TCPServer 和 UDPServer. 1. TCPServer 2. UDPServer 3. UnixStreamServer,类似于TCP ...

随机推荐

  1. Laravel通过用户名和密码查询

    一.如果要检查要验证的用户数据是否正确,可以使用: if (Auth::validate($credentials)) { // } 二.但是如果您想通过用户和密码从数据库中获取用户,您可以使用: / ...

  2. Linux - mysql 异常:登录不上mysql数据库

    问题描述 重启虚拟机之后,用命令 mysql -u root -p 登录不上 mysql 数据库,页面显示: 但是,用命令 service mysqld status 可以查看状态 解决方案 1.查看 ...

  3. 'ssh-keygen' 不是内部或外部命令,也不是可运行的程序

    右键我的电脑,点击环境变量,设置系统配置里面的Path 新增一个 D:\ruanjiananzhuangdizhi\Git\usr\bin 路径就可以了

  4. jQuery---版本问题

    jQuery的版本 官网下载地址:http://jquery.com/download/ jQuery版本有很多,分为1.x 2.x 3.x 大版本分类: 1.x版本:能够兼容IE678浏览器 2.x ...

  5. javascript脚本混淆

    javascript脚本混淆  脚本病毒是一个一直以来就存在,且长期活跃着的一种与PE病毒完全不同的一类病毒类型,其制作的门槛低.混淆加密方式的千变万化,容易传播.容易躲避检测,不为广大网民熟知等诸多 ...

  6. [object object]

    第一个object代表用户自定义的对象的属性. 第二个object代表用户自定义的对象的方法. 是valueOf返回的一个字符串另外你打错了吧应该是[object Object]表示对象的类型是obj ...

  7. 【音乐欣赏】《I Don't Even Care About You》 - Missio

    曲名:I Don't Even Care About You 作者:Missio [00:31.18]Depressed again [00:34.66]Morning comes too fast ...

  8. conn (php)

    <?php$host="localhost";$db_user="root"; //数据库用户$db_pass=""; //数据库密码 ...

  9. vue+muse-ui

    1.可以很好的配合vue2.0开发 2.安装: npm install  muse-ui --save 3.引入: 在main.js中加入 import Vue from 'vue' import M ...

  10. c++指针实例

    #include <iostream> using namespace std; int main () { ; // 实际变量的声明 int* ip; // 指针变量的声明 ip = & ...