一、套接字
1.1 socket模块
套接字是网络编程中的一个基本组件,通常包括服务器端套接字和客户端套接字。编程
建立服务器端过程以下:服务器
1 import socket 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 try: 8 s.bind((host, port)) 9 except Exception, e: 10 print e 11 s.close() 12 13 s.listen(5) 14 while True: 15 c, addr = s.accept() 16 print 'Got connection from', addr 17 c.send('Thank you for connecting') 18 c.close()
建立客户端过程以下:网络
1 import socket, time 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 8 s.connect((host, port)) 9 print s.recv(1024) 10 while True: 11 command = 'hello\n' 12 s.send(command) 13 time.sleep(1)
该过程的通信方式是一问一答的形式,因此又称为阻塞或者同步网络编程。
1.2 SocketServer和它的朋友们
SocketServer模块是标准库中不少服务器框架的基础,这些服务器框架包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer,全部的这些服务器框架都为基础服务器增长了特定的功能。
SocketServer包含4个基本的类:针对TCP套接字流的TCPServer;针对UDP数据报套接字的UDPServer;以及针对性不强的UnixStreamServer和UnixDatagramServer。
在使用SocketServer服务器框架时,每当服务器收到一个请求,就会实例化一个请求处理程序,而且它的各类处理方法(handler methods)会在处理请求时被调用。基本的BaseRequestHandler类把全部的操做都放到了处理器的一个叫作handle的方法中,这个方法会被服务器调用。而后在这个方法内就能够访问属性self.request中的客户端套接字。若是使用的是流(TCPServer),那么可使用StreamRequestHandler类,建立它的两个属性,self.rfile(用于读取)和self.wfile(用于写入)。app
1 from SocketServer import TCPServer, StreamRequestHandler 2 3 class Handler(StreamRequestHandler): 4 5 def handle(self): 6 addr = self.request.getpeername() 7 8 print 'Got connection from', addr 9 self.wfile.write('Thank you for connecting') 10 server = TCPServer(('', 1234), Handler) 11 server.serve_forever()
二、多链接
同时能够有多个客户机链接服务端进行请求处理。有3个主要的方法能实现这个目的:分叉(forking)、线程(threading)以及异步I/O(asynchronous I/O)。它们有各自的缺点:分叉占资源,若是有太多的客户端时,分叉不能很好地分叉;线程处理能致使同步问题。
什么是分叉:当分叉一个进程(一个运行的程序)时,基本上是复制了它,而且分叉后的两个进程都从当前的执行点继续运行,而且每一个进程都有本身的内存副本(好比变量)。一个进程(原来的那个)成为父进程,另外一个(复制的)成为子进程。由于分叉的进程是并行运行的,客户端之间没必要相互等待。但分叉有点消耗资源(每一个分叉出来的进程都须要本身的内存),这就存在了另外一个选择:线程。
什么是线程:线程是轻量级的进程或子进程,全部的线程都存在于相同的(真正的)进程中,共享内存。资源消耗的降低伴随着一个缺陷:由于线程共享内存,因此必须确保它们的变量不会冲突,或者是在同一时间修改同一内容,这就会形成混乱。
使用SocketServer框架建立分叉或者线程服务器太简单了,几乎不须要解释。注意,Windows不支持分叉。
使用分叉技术的服务器:框架
1 from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler 2 3 class Server(ForkingMixIn, TCPServer): pass 4 5 class Handler(StreamRequestHandler): 6 7 def handle(self): 8 addr = self.request.getpeername() 9 print 'Got connection from', addr 10 self.wfile.write('Thank you for connecting') 11 12 server = Server(('', 1234), Handler) 13 server.serve_forever()
使用线程处理的服务器:异步
from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler class Server(ThreadingMixIn, TCPServer): pass class Handler(StreamRequestHandler): def handle(self): addr = self.request.getpeername() print 'Got connection from', addr self.wfile.write('Thank you for connecting') server = Server(('', 1234), Handler) server.serve_forever()
什么是异步I/O:当一个服务器与一个客户端通讯时,来自客户端的数据多是不连续的。若是使用分叉或线程处理,那就不是问题。当一个程序在等待数据,另外一个并行的程序能够继续处理它们本身的客户端。另外的处理方法是只处理在给定时间内真正要进行通讯的客户端。
这是asyncore/asynchat框架采用的方法,这种功能的基础是select函数,若是poll函数可用,那也能够是它,这两个函数都来自select模块。其中poll函数的伸缩性要更好,但只能用在UNIX系统中。
Select函数有3个序列做为它的必选参数,还有一个可选的超时时间做为第4个参数。这3个序列是套接字文件描述符,用于输入、输出以及异常状况。若是没有给定超时时间,select会阻塞,处于等待状态,直到一个文件描述符已经为行动作好了准备;若是给定超时时间,select最多阻塞给定的超时时间;若是给定的超时时间是0,select就不阻塞。Select的返回值是3个序列,一个长度为3的元组,每一个表明相应参数的一个活动子集。socket
1 import socket, select 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 s.bind((host, port)) 8 9 s.listen(5) 10 inputs = [s] 11 while True: 12 rs, ws, es = select.select(inputs, [], []) 13 for r in rs: 14 if r is s: 15 c, addr = s.accept() 16 print 'Got connection from', addr 17 inputs.append(c) 18 else: 19 try: 20 data = r.recv(1024) 21 disconnected = not data 22 except socket.error: 23 disconnected = True 24 if disconnected: 25 print r.getpeername(), 'disconnected' 26 inputs.remove(r) 27 else: 28 print data
poll方法使用起来比select简单。在调用poll时,会获得一个poll对象。而后使用poll对象的register方法注册一个文件描述符(或者是带有fileno方法的对象)。注册后可使用unregister方法移除注册的对象。注册完套接字对象以后能够调用poll方法(带有一个可选的超时时间参数)并获得一个(fd, event)格式列表(多是空的),其中fd是文件描述符,event则告诉你发生了什么。event对象是一个位掩码,可使用按位与操做来判断事件的类型.
select模块中的polling事件常量
事件名 描述
POLLIN 读取来自文件描述符的数据
POLLPRI 读取来自文件描述符的紧急数据
POLLOUT 文件描述符已经准备好数据,写入时不会发生阻塞
POLLERR 与文件描述符有关的错误状况
POLLHUP 挂起,链接丢失
POLLNVAL 无效请求,链接没有打开async
1 import socket, select 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 s.bind((host, port)) 8 9 fdmap = {s.fileno(): s} 10 11 s.listen(5) 12 p = select.poll() 13 p.register(s) 14 while True: 15 events = p.poll() 16 for fd, event in events: 17 if fdmap[fd] is s: 18 c, addr = s.accept() 19 print 'Got connection from', addr 20 p.register(c) 21 fdmap[c.fileno()] = c 22 elif event & select.POLLIN: 23 data = fdmap[fd].recv(1024) 24 if not data: # No data -- connection closed 25 print fdmap[fd].getpeername(), 'disconnected' 26 p.unregister(fd) 27 del fdmap[fd] 28 else: 29 print data