Python 网络编程之 SocketServer 2016-03-28 # Python 网络编程之 SocketServer [TOC] [SocketServer](https://docs.python.org/2/library/socketserver.html) 是Python Standard Libray 中的其中之一,在Python3 中改名为socketserver .这个包主要是封装了一些网络服务的接口,方便使用.比如写一个Tcp 协议的Server 不需要从socket 开始.究竟有哪些用处呢?一般都怎么使用呢?这就是本文要介绍的. ### socket 回顾 [socket](https://docs.python.org/2/library/socket.html) 也是Python 的Standard Libray 中的,它封装了计算机之间的通信,具体怎么实现的不是本文要说的.这里简单介绍下socket 这个模块中的一些常用方法. 1. 简单function ```Python socket.gethostname() ``` 获取本机hostname ,这拿到的是本机的 ```Python socket.getfqdn('midday.me') ``` 获得远程机器的hostname ``` socket.gethostbyname(socket.gethostname()) ``` 这句能获取Ip 地址,但是比较遗憾的是通常拿到的是'127.0.0.1' 在阿里云上也是获得的内网Ip.怎样用socket 获得外网Ip 我还真没找到(知道的求指教) ``` socket.gethostbyaddr('144.168.58.213') ``` 返回一个tuple (hostname, aliaslist, ipaddrlist) 其中的元素都是list 对象 在Linux 下面我们有时候想知道服务器某个端口是什么服务 ``` socket.getservbyport('80') ``` 这些都可以用的时候看文档, 2.socket 简单Server ``` import socket HOST = '' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) conn, addr = s.accept() print 'Connected by', addr while 1: data = conn.recv(1024) if not data: break conn.sendall(data) conn.close() ``` 这个例子来自官方文档,第4行初始化了一个socket 对象,socket.AF_INET 制定了使用IPV4网络协议套接字类型.第8等待客户端的链接,也是会阻塞,当有客户端脸上服务器的时候会继续往下执行.11行等待从客户端接收数据,在没有接收到数据之前会阻塞在这句,13行把接收的数据返回给客户端. ``` import socket HOST = 'daring.cwi.nl' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) s.sendall('Hello, world') data = s.recv(1024) s.close() print 'Received', repr(data) ``` 这是一个客户端和服务端很类似 ### SocketServer #### 鸟瞰结构 SocketServer 的源码加上注释总共737行,其实还是相对简单的.里面会有实现了几个比较核心的Class 类之间对的关系如下图 ```seq BaseServer -> TCPServer:'' TCPServer->UnixStreamServe:'' TCPServer->UDPServer:'' UDPServer->UnixDatagramServer:'' ``` 这个图不是很能展现他们之间的关系,去官网看清楚很多. 本文不会太多涉及UDP 协议,所以比较多的是了解TCPServer,TCPServer 继承自BaseServer. 在SocketServer.py的代码中可以看到还有很多其他的类提供 ``` __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", "StreamRequestHandler","DatagramRequestHandler", "ThreadingMixIn", "ForkingMixIn"] ``` 除了上面介绍的5个还有几个Hanldler ,和处理异步或多线程的类,先去读下BaseServer 的源码对设计的整个过程有所了解,由于代码较长,且注释较多,不在此处贴出. ``` def __init__(self, server_address, RequestHandlerClass): self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False ``` 这是__init__ 方法,能看到有地址信息,通常是一个tuple 如('0.0.0.0','55556' ),RequestHandlerClass 可想而知了就和上面的几个Handler 有关系了.其实逻辑就是Server 如何处理请求的实现是在Handler 中的,每个Handler 都有handle 方法,我们在继承Handler 的时候通常都要实现或重写handle 方法,来达到不同的目的.要知道Handler handle 方法怎么被调用的一定要来看BaseServer 因为在子类中基本就看不到了. BaseServer 让我比较觉得奇怪的是,在BaseServer 调用self.get_connect()方法,但是它本身并没有定义这个方法.当然在子类中会实现. #### TCPServer TCPServer就是BaseServer 的直接子类,其实它的代码很简单的,主要是在初始化的时候和上面的socket 一样产生了 一个socket 对象,然后定义了几个方法供子类实现,和调用. ``` import SocketServer class MyTCPHandler(SocketServer.BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() print "{} wrote:".format(self.client_address[0]) print self.data self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) server.serve_forever() ``` 看到了处理请求的方式都是在Handler 中完成的,就没有了和socket 打交到的部分,是因为TcpServer 帮我们封装了.记得Tornado 中需要实现一个Handler 的get 或post 等方法来实现一个server 其实我想大致的实现方式和这里类似. 当然如果要做得复杂一点你可以自己实现一个Server 继承TCPServer ### 异步Server 上面看到的所有Server 都是阻塞的,每次只能接收一个request 其他的都得等着,这肯定不是真实环境能用的,解决这个问题就出现了很多种解决方法. #### ForkingMixIn&ThreadingMixIn 代码中的注释是这样的 "Mix-in class to handle each request in a new process"好像意思就是在不同的进程中处理每一个请求,具体是怎么实现的呢? ``` class ForkingServer(SocketServer.ForkingMixIn, SocketServer.TCPServer, ): ``` 上面这个ForkingServer 就是在使用的时候的实现方法,ForkingMinIn具体是如何实现的需去看源码 ``` def process_request(self, request, client_address): self.collect_children() pid = os.fork() if pid: if self.active_children is None: self.active_children = set() self.active_children.add(pid) self.close_request(request) return else: try: self.finish_request(request, client_address) self.shutdown_request(request) os._exit(0) except: try: self.handle_error(request, client_address) self.shutdown_request(request) finally: os._exit(1) ``` 为了美观我去掉了源码中的注释,process_request 这个方法就是BaseServer 中的一个同名方法,当我们实现一个子类继承自BaseServer 和ForkingMinIn的时候就相当于BaseServer的pprocess_request 方法被重写了,比较有意思的是它使用了self.finish_request()这些方法都是BaseServer 中的方法,看Python 也是很好的OOP语言吗,读着总能想起当初Java 的影子. 这里有个os.fork()为什么就能实现多进程呢? 这个问题留到线程和进程板块来说. ThreadingMixIn是用了threading模块来实现的,类似. #### select 说道异步我们不能不说的是Python 的select 模块,它不是多线程,也不是多进程,它是单进程,却有着很好的性能,使用系统的异步IO .那么怎么使用select 来实现一个Server呢? 这个也足足能写很长一篇来说,留到下次.SocketServer 是封装了很多东西,但是当想做一个聊天室的时候想想直接使用socket 是会比较直接很多,加上select 就能做到异步.select 在很多地方都又用,是一个很重要的模块,最近看了[pika](https://pika.readthedocs.org/en/0.10.0/)的一些东西,就有用到,这是一个能Python 封装RabbitMQ API 的第三方库,也正是看了这个模块才决定好好收拾下这部分的内容. Rocky 2016/03/28 重庆