这里总结下一个服务端与多个客户端之间的通信。
先看demo:
#/usr/bin/env python #_*_coding:utf-8_*_ __author__ = 'ganzl' import time import socket import select sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.bind(('127.0.0.1',6666)) sk.listen(5) sk.setblocking(False) inputs = [sk,] #sk就是一个监听的sorcket,放入inputs这个列表中。 while True: readable_list, writeable_list, error_list = select.select(inputs,[],[],1) #把第一个参数设为列表动态的添加 time.sleep(2) print "inputs :",inputs #打印inputs列表,查看执行变化 print "readable_list:",readable_list #打印readable_list ,查看执行变化 for r in readable_list: if r == sk: #sk是一个描述符, conn,address = r.accept() inputs.append(conn)#conn是连接socket之后的那根通信管道。 #sk监控的是socket描述符是否有变化,conn监控的是连接后的socket之间两者服务器之间的通信是否有变化 print address else: #如果是客户端,接受和返回数据 client_data = r.recv(1024) if client_data: r.sendall(client_data) else: inputs.remove(r)#如果没有收到客户端端数据,则移除客户端句柄 因为,不管是正常关闭还是异常关闭,client端的系统底层都会发送一个消息 #异常关闭的时候会发送一个空格。
客户端(1) #!/usr/bin/env python #_*_coding:utf-8 _*_ __author__ = 'ganzl' import socket import time #创建socket对象 ck = socket.socket() #通过IP&端口连接server ck.connect(('127.0.0.1',6666)) #发送消息给服务端 time.sleep(10) ck.sendall('hello server, this is ganzl---test3---1-') ck.sendall('hello server, this is ganzl---test3----') #接收服务端的消息并打印 server_answer = ck.recv(1024) print server_answer #关闭连接 ck.close()
客户端(2) #!/usr/bin/env python #_*_coding:utf-8 _*_ __author__ = 'ganzl' import socket ck = socket.socket() ck.connect(('127.0.0.1',6666)) ck.sendall('hello server, this is ganzl----2----') server_answer = ck.recv(1024) print server_answer ck.close()
客户端(3)
通过http访问:http://127.0.0.1:6666/
------------------------------------------------------------------------
以上就是用到了IO的多路复用中的select模块。
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。其他两个方法暂时还没弄懂,空了再看下源码,再百度,再请教其他人。。。
select方法总结:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 参数: 可接受四个参数(前三个必须) 返回值:三个列表 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
写上面这个demo的目的是啥?其实就是SocketServe源码中的最几句核心代码。
SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
用一个SocketServer的实际例子来过过代码。方便以后查看,理解。
SocketServer的demo:
服务端:
#!/usr/bin/env python #_*_coding:utf-8 _*_ __author__ = 'ganzl' 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 _*_ __author__ = 'ganzl' 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()
代码实现多个客户端可以同时跟服务器端通信的原因如下:(我自己的类继承了SocketServer.BaseRequestHandler这个类,而它封装了一些基础的类,我自己重写它的方法就好了)
我就不文字描述。 图片就是太难看。有时间再弄个好看的
上图的精简demo如下:
源码精简:(SocketServer) 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()
这里socket用到的都是线程。一个进程中产生的多线程。至于进程线程协程,过段时间搞搞明白。