SocketServer模块
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。
Socketserver模块简化了写network servers的任务.
四个基本服务器类:
class socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True)
使用TCP协议,如果bind_and_activate=True, 构建器自动invoke server_bind() 和server_activate().
class socketserver.
UDPServer
(server_address, RequestHandlerClass, bind_and_activate=True)
使用 datagrams, 在运输过程中可能出现无序或丢失的离散信息包。
class socketserver.
UnixStreamServer
(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.
UnixDatagramServer
(server_address, RequestHandlerClass,bind_and_activate=True)
使用 Unix domain sockets; 在Unix平台上不能使用.
这四个类同步处理请求;每个请求必须在下一个请求开始之前完成。如果每个请求需要很长的时间才能完成,这是不合适的,因为它需要大量的计算,或者因为它返回了大量客户端缓慢处理的数据。
解决方案是创建一个单独的进程或线程来处理每个请求;类中的FordJixMin和THeulyMIXIN混合可以用来支持异步行为
继承图中有五个类,其中四个代表四种类型的同步服务器
+------------+
| BaseServer |
+------------+ | v +-----------+ +------------------+ | TCPServer |---生成---->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |---生成---->| UnixDatagramServer | +-----------+ +--------------------+
IP和Unix treamserver最大的区别就是地址簇。
class socketserver.
ForkingMixIn
class socketserver.
ThreadingMixIn
每种服务器的Forking 和threading版本可以用mix-in类来创建.比如
ThreadingUDPServer可以创建如下
:
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
pass
mix-in class 放在第一位,因为它重写了在UDPServer中定义的方法。设置各种属性也会改变底层服务器机制的行为。
class socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
这些类在mix-in中预定义了
Request Handler Objects
class socketserver.
BaseRequestHandler
是所有request handler对象的超类. 定义了接口. 一个具体的 handler之类必须要定义一个新的 handle() 方法并且可以重写任何 其他的方法,为每个请求创建一个子类新的实例.
setup
() #在 handle()之前调用,
方法执行所需的任何初始化操作。默认实现不执行任何操作。handle
() #他的功能必须完成服务请求所需的所有工作。默认实现不执行任何操作。有几个实例属性可供使用;请求可作为Self.Read;客户端地址为Self.client地址;服务器实例为Self.Server,以防需要访问每个服务器信息。对于数据报或流服务,Self.Read的类型不同。对于流服务,Self.Read是Socket对象;对于数据报服务,Self.Read是一对字符串和套接字。
finish
() #在handle()之后调用,方法执行所需的任何清理操作。默认实现不执行任何操作。如果SETUP()引发异常,则不调用此函数。
Socketserver.TCPServerExample
服务器端
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.sendall( 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()
客户端
import socket
import sys
HOST, PORT = "localhost" , 9999
data = " " .join(sys.argv[ 1 :])
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try :
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n" , "utf-8" ))
# Receive data from the server and shut down
received = str (sock.recv( 1024 ), "utf-8" )
finally :
sock.close()
print ( "Sent: {}" . format (data))
print ( "Received: {}" . format (received))
上面这个例子你会发现,依然不能实现多并发,在server端做一下更改就可以了
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
更改为:
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
ThreadingTCPServer
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
1、ThreadingTCPServer基础
使用ThreadingTCPServer:
- 创建一个继承自 SocketServer.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer
class MyServer(SocketServer.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break
sk.close()
2、ThreadingTCPServer源码剖析
ThreadingTCPServer的类图关系如下:
内部调用流程为:
- 启动服务端程序
- 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
- 执行BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
- 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
- 当客户端连接到达服务器
- 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
- 执行 ThreadingMixIn.process_request_thread 方法
- 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
ThreadingTCPServer相关源码:
BaseServer
TCPServer
ThreadingMixIn
ThreadingTCPServer
RequestHandler相关源码
class BaseRequestHandler:
"""Base class for request handler classes.
This class is instantiated for each request to be handled. The
constructor sets the instance variables request, client_address
and server, and then calls the handle() method. To implement a
specific service, all you need to do is to derive a class which
defines a handle() method.
The handle() method can find the request as self.request, the
client address as self.client_address, and the server (in case it
needs access to per-server information) as self.server. Since a
separate instance is created for each request, the handle() method
can define arbitrary other instance variariables.
"""
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()
def setup(self):
pass
def handle(self):
pass
def finish(self):
pass
实例:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer
class MyServer(SocketServer.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break
sk.close()
源码精简:
import socket
import threading
import select
def process(request, client_address):
print request,client_address
conn = request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
flag = True
while flag:
data = conn.recv(1024)
if data == 'exit':
flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(('127.0.0.1',8002))
sk.listen(5)
while True:
r, w, e = select.select([sk,],[],[],1)
print 'looping'
if sk in r:
print 'get request'
request, client_address = sk.accept()
t = threading.Thread(target=process, args=(request, client_address))
t.daemon = False
t.start()
sk.close()
如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading
ForkingTCPServer
ForkingTCPServer和ThreadingTCPServer的使用和执行流程基本一致,只不过在内部分别为请求者建立 “线程” 和 “进程”。
基本使用:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer
class MyServer(SocketServer.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break
sk.close()
以上ForkingTCPServer只是将 ThreadingTCPServer 实例中的代码:
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyRequestHandler)
变更为:
server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyRequestHandler)
SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 os.fork
源码剖析参考 ThreadingTCPServer