服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:
(1)同步阻塞IO(Blocking IO):即传统的IO模型。
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
(3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
(4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
IO多路复用
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
Linux
Linux中的 select,poll,epoll 都是IO多路复用的机制。他们之间的区别参考另一篇博文 http://www.cnblogs.com/wjx1/p/5082640.html
Python
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
SocketServer模块
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进 程” 专门负责处理当前客户端的所有请求。
ThreadingTCPServer
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
1、ThreadingTCPServer基础
使用ThreadingTCPServer:
创建一个继承自 SocketServer.BaseRequestHandler 的类
类中必须定义一个名称为 handle 的方法
启动ThreadingTCPServer
#!/usr/bin/env python#-*- coding:utf-8 -*-
importSocketServerclassMyServer(SocketServer.BaseRequestHandler):defhandle(self):#print self.request,self.client_address,self.server
conn =self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag=TruewhileFlag:
data= conn.recv(1024)if data == 'exit':
Flag=Falseelif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')else:
conn.sendall('请重新输入.')if __name__ == '__main__':
server= SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
SocketServer实现服务器
SocketServer实现服务器
#!/usr/bin/env python#-*- coding:utf-8 -*-
importsocket
ip_port= ('127.0.0.1',8009)
sk=socket.socket()
sk.connect(ip_port)
sk.settimeout(5)whileTrue:
data= sk.recv(1024)print 'receive:',data
inp= raw_input('please input:')
sk.sendall(inp)if inp == 'exit':breaksk.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方法)
实例:
#!/usr/bin/env python#-*- coding:utf-8 -*-
importSocketServerclassMyServer(SocketServer.BaseRequestHandler):defhandle(self):#print self.request,self.client_address,self.server
conn =self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag=TruewhileFlag:
data= conn.recv(1024)if data == 'exit':
Flag=Falseelif 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 -*-
importsocket
ip_port= ('127.0.0.1',8009)
sk=socket.socket()
sk.connect(ip_port)
sk.settimeout(5)whileTrue:
data= sk.recv(1024)print 'receive:',data
inp= raw_input('please input:')
sk.sendall(inp)if inp == 'exit':breaksk.close()
客户端
客户端
源码精简:
importsocketimportthreadingimportselectdefprocess(request, client_address):printrequest,client_address
conn=request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
flag=Truewhileflag:
data= conn.recv(1024)if data == 'exit':
flag=Falseelif 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)whileTrue:
r, w, e= select.select([sk,],[],[],1)print 'looping'
if sk inr: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 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
ForkingTCPServer
ForkingTCPServer和ThreadingTCPServer的使用和执行流程基本一致,只不过在内部分别为请求者建立 “线程” 和 “进程”。
基本使用:
#!/usr/bin/env python#-*- coding:utf-8 -*-
importSocketServerclassMyServer(SocketServer.BaseRequestHandler):defhandle(self):#print self.request,self.client_address,self.server
conn =self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag=TruewhileFlag:
data= conn.recv(1024)if data == 'exit':
Flag=Falseelif 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 -*-
importsocket
ip_port= ('127.0.0.1',8009)
sk=socket.socket()
sk.connect(ip_port)
sk.settimeout(5)whileTrue:
data= sk.recv(1024)print 'receive:',data
inp= raw_input('please input:')
sk.sendall(inp)if inp == 'exit':breaksk.close()
客户端
客户端
以上ForkingTCPServer只是将 ThreadingTCPServer 实例中的代码: