利用select实现伪并发的socket
使用socket模块可以实现程序之间的通信,但是server在同一时刻只能和一个客户端进行通信,如果要实现一个server端可以和多个客户端进行通信可以使用
1.多线程
2.多进程
3.select I/O多路复用
来实现服务器端和多个客户端进行通信,本文将会介绍使用select实现伪并发。
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。I/O多路复用的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select /epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
小试牛刀:利用select监听终端输入
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
import select
import time while True:
readable,writeable,errable = select.select([sys.stdin,],[],[],5)
print readable
print type(readable)
if sys.stdin in readable:
print "you input: ",sys.stdin.readline()
监听终端输入
执行结果:
select.select()方法的参数解释:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 参数: 可接受四个参数(前三个必须)
返回值:三个列表 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
5、当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
由此解释以上代码的执行结果:
1.程序将sys.stdin句柄写入到了select.select()方法的第一个句柄序列中,所以sys.stdin句柄将会被select监听着
2.一旦sys.stdin这个句柄发生了变化,select.select()方法将会返回一个列表,这个列表中包含了变化的句柄,这个列表就是readable
3.判断sys.stdin是否在这个列表中,在的话就执行下边的语句
4.select.select的第四个参数是超时时间,如果3秒内没有文件句柄发送变化,就返回空的列表
5.超时时间select.select方法的第四个参数为3
利用selct监听socket
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import select
import socket sk = socket.socket()
ip_port = ("127.0.0.1",7777)
sk.bind(ip_port)
sk.listen(5)
sk.setblocking(False) while True:
readable,writeable,errable = select.select([sk,],[],[],1)
for s in readable:
cnn,add = s.accept()
cnn.sendall("欢迎")
# data = cnn.recv(1024)
print add
select监听socket
利用select监听多个端口
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import select
import socket sk1 = socket.socket()
ip_port = ("127.0.0.1",7777)
sk1.bind(ip_port)
sk1.listen(5)
sk1.setblocking(False) sk2 = socket.socket()
ip_port = ("127.0.0.1",7778)
sk2.bind(ip_port)
sk2.listen(5)
sk2.setblocking(False) while True:
readable,writeable,errable = select.select([sk1,sk2,],[],[],1)
for s in readable:
cnn,add = s.accept()
cnn.sendall(repr(add))
print add
select监听多个端口的socket
----------------------------------------------------------我是分割线---------------------------------------------------------------------
以上的内容其实只是为了演示select能够监听变化文件描述符的功能,下面的才是使用select的妙处所在
利用select同时和多个客户端进行交互
#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#创建socket对象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#设置监听的IP与端口
sk.bind(('127.0.0.1',6666))
#设置client最大等待连接数
sk.listen(5)
sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错
inputs = [sk,] #将sk这个对象加入到列表中,并且赋值给inputs
#原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,连接上之后,还是服务端的socket 有关吗?
#是不是的把他改为动态的? while True:
readable_list, writeable_list, error_list = select.select(inputs,[],[],1) #把第一个参数设为列表动态的添加
time.sleep(2) #测试使用
print "inputs list :",inputs #打印inputs列表,查看执行变化
print "file descriptor :",readable_list #打印readable_list ,查看执行变化 for r in readable_list:
if r == sk: #这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk
conn,address = r.accept()
inputs.append(conn)
print address
else:
#如果是客户端,接受和返回数据
client_data = r.recv(1024)
r.sendall(client_data) select socket server - server
server
#!/usr/bin/env python
#-*- coding:utf-8 -*- import socket client = socket.socket()
client.connect(('127.0.0.1',6666))
client.settimeout(5) while True:
client_input = raw_input('please input message:').strip()
client.sendall(client_input)
server_data = client.recv(1024)
print server_data select socket server - client
client
交互过程详解:
#1 默认,sk这个对象文件句柄就在inputs列表中select监听客户端的请求,当有客户端请求过来 client1 ---> server
#用户捕获了变化readable_list = [sk,] 那么循环是有值得,判断r = sk 说明是一个新的请求链接,然后把client链接加入到inputs里 inputs = [sk,conn1,]
#如果现在什么都不做,那么select无法捕获到变化:readable_list = []
#执行看下:
inputs list : [<socket._socketobject object at 0x0000000002C66798>] #默认inputs list 就有一个server socket sk 对象
file descriptor : [<socket._socketobject object at 0x0000000002C66798>] #当有客户端请求过来时候,sk发生了变化,select捕获到了
('127.0.0.1', 62495)
inputs list : [<socket._socketobject object at 0x0000000002C66798>, <socket._socketobject object at 0x0000000002C66800>] #第二次循环的时候,inputs = [sk,conn1,]
file descriptor : [] #第二次循环的时候readable_list = [] 因为客户端没有做任何操作,没有捕获到变化所以为空 #2 又有一个新的链接过来了,谁变化了? sk 他变化了,有人向他发起了一个请求链接,那么现在inputs = [sk,conn1,conn2] readable_list = [sk]
#本次循环完成之后再循环的时候 inputs = [sk,conn1,conn2,] readable_list = [] 因为我们没有继续做操作 #第一个链接
inputs list : [<socket._socketobject object at 0x0000000002C56798>] #默认只有一个对象
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>]
file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #当捕获到,判断是否是新链接,如果是加入到inputs列表中监控
('127.0.0.1', 62539)
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>] #inputs列表变更为了[sk,conn1]
file descriptor : [] #因为没有后续的操作,这里没有捕获到异常所以列表为空 #第二个链接
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>] #第一个链接没有做任何操作
file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #第二个链接过来了被捕获到,判断是否为新链接
('127.0.0.1', 62548)
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>] #加入到inputs列表中
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>]
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>]
file descriptor : []
process
优化点一:当某一个客户端断开连接之后,该客户端的socket描述符还是在服务端的监听列表中的,能不能将已经断开连接的客户端的socket描述符删除掉?
#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#创建socket对象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#设置监听的IP与端口
sk.bind(('127.0.0.1',6666))
#设置client最大等待连接数
sk.listen(5)
sk.setblocking(False) #这里设置setblocking为Falseaccept将不在阻塞,但是如果没有收到请求就会报错
inputs = [sk,] #将sk这个对象加入到列表中,并且赋值给inputs
#原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,连接上之后,还是服务端的socket 有关吗?
#是不是的把他改为动态的? while True:
readable_list, writeable_list, error_list = select.select(inputs,[],[],1) #把第一个参数设为列表动态的添加
time.sleep(2) #测试使用
print "inputs list :",inputs #打印inputs列表,查看执行变化
print "file descriptor :",readable_list #打印readable_list ,查看执行变化 for r in readable_list:
if r == sk: #这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk
conn,address = r.accept()
inputs.append(conn)
print address
else:
#如果是客户端,接受和返回数据
client_data = r.recv(1024)
if client_data:
r.sendall(client_data)
else:
inputs.remove(r)#如果没有收到客户端端数据,则移除客户端句柄 因为,不管是正常关闭还是异常关闭,client端的系统底层都会发送一个消息 select socket server - server release client-connect
server
优化点二:假如说我们需要将客户端发来的数据写入到数据库中,不同客户端发来的消息要存放在不同的表中,那怎么办?
难点:
1.我们并不知道消息和客户端的对应关系
2.假如说在同一时间发送了大量的消息,来不及写怎么办?
解决这一问题我们需要引入一个新的数据结构-------> Queue(队列)
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t'
import select
import socket
import Queue
import time sk = socket.socket()
sk.bind(('127.0.0.1',6666))
sk.listen(5)
sk.setblocking(False) #定义非阻塞
inputs = [sk,] #定义一个列表,select第一个参数监听句柄序列,当有变动是,捕获并把socket server加入到句柄序列中
outputs = [] #定义一个列表,select第二个参数监听句柄序列,当有值时就捕获,并加入到句柄序列
message = {}
#message的样板信息
#message = {
# 'c1':队列,[这里存放着用户C1发过来的消息]例如:[message1,message2]
# 'c2':队列,[这里存放着用户C2发过来的消息]例如:[message1,message2]
#} while True:
readable_list, writeable_list, error_list = select.select(inputs,outputs,[],1)
#文件描述符可读 readable_list 只有第一个参数变化时候才捕获,并赋值给readable_list
#文件描述符可写 writeable_list 只要有值,第二个参数就捕获并赋值给writeable_list
#time.sleep(2)
print 'inputs:',inputs
print 'output:'
print 'readable_list:',readable_list
print 'writeable_list:',writeable_list
print 'message',message
for r in readable_list: #当readable_list有值得时候循环
if r == sk: #判断是否为链接请求变化的是否是socket server
conn,addr = r.accept() #获取请求
inputs.append(conn) #把客户端对象(句柄)加入到inputs里
message[conn] = Queue.Queue() #并在字典里为这个客户端连接建立一个消息队列
else:
client_data = r.recv(1024) #如果请求的不是sk是客户端接收消息
if client_data:#如果有数据
outputs.append(r)#把用户加入到outpus里触发select第二个参数
message[r].put(client_data)#在指定队列中插入数据
else:
inputs.remove(r)#没有数据,删除监听链接
del message[r] #当数据为空的时候删除队列~~
for w in writeable_list:#如果第二个参数有数据
try:
data = message[w].get_nowait()#去指定队列取数据 并且不阻塞
w.sendall(data) #返回请求输入给client端
except Queue.Empty:#反之触发异常
pass
outputs.remove(w) #因为第二个参数有值得时候就触发捕获值,所以使用完之后需要移除它
#del message[r]
print '%s' %('-' * 40) select socket server - server read | write separation
读写分离的版本
总结:
使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。
但这个模型依旧有着很多问题。首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。很 多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了queue,Solaris提供了/dev/poll,…。如果需要实现 更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,所以使用类似于epoll的接口实现具 有较好跨平台能力的服务器会比较困难。
其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。如下例,庞大的执行体1的将直接导致响应事件2的执行体迟迟得不到执行,并在很大程度上降低了事件探测的及时性。
I/O多路复用的使用场景
#(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
#(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
#(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
#(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
#(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
'''与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''
参考资料:
http://www.cnblogs.com/luotianshuai/p/5098408.html
利用select实现伪并发的socket的更多相关文章
- 使用select实现多并发的socket的功能
select是一个io多路复用的io模型,也叫做事件驱动的io模型,我们今天用select来实现一个多并发的socket的聊天的程序 先看下server端的代码 import socket impor ...
- python利用select实现的Socket Server
# 利用python的select模块实现简单的Socket Sever #实现多用户访问,再次基础上可以实现FTP Server应用程序 # 发布目的,在于解决了客户端强行终止时,服务器端也跟着程序 ...
- 第10章 同步设备I/O和异步设备I/O(4)_利用I/O完成端口实现Socket通信
I/O完成端口原理见上一篇(可点击这里) 10.5.4.4 利用I/O完成端口实现Socket通信 (1)Accept和AcceptEx流程的比较 ①采用accept方式的流程示意图如下(普通的阻塞函 ...
- UNIX网络编程——select函数的并发限制和 poll 函数应用举例
一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, ...
- select函数的并发限制和 poll 函数应用举例
一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, ...
- 模拟ssh远程执行命令,粘包问题,基于socketserver实现并发的socket
06.27自我总结 1.模拟ssh远程执行命令 利用套接字编来进行远程执行命令 服务端 from socket import * import subprocess server = socket(A ...
- C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)
介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...
- 模拟python中的Yield伪并发
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行. #Yield伪并发 _author_= ...
- sql判断以逗号分隔的字符串中是否包含某个字符串--------MYSQL中利用select查询某字段中包含以逗号分隔的字符串的记录方法
sql判断以逗号分隔的字符串中是否包含某个字符串---------------https://blog.csdn.net/wttykj/article/details/78520933 MYSQL中利 ...
随机推荐
- PHP面向对象(OOP)----分页类
> 同验证码类,分页也是在个人博客,论坛等网站中不可缺少的方式,通过分页可以在一个界面展示固定条数的数据,而不至于将所有数据全部罗列到一起,实现分页的原理其实就是对数据库查询输出加了一个limi ...
- Python实现多线程HTTP下载器
本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为 ...
- Ajax 入门之 GET 与 POST 的不同 (2)
在之前的随笔中,本着怀旧的态度总结了一篇 兼容不同浏览器 建立XHR对象的方法: 在建立好XHR对象之后,客户端需要做的就是,将数据以某种方式传递到服务器,以获得相应的响应,在这里, Ajax技术总 ...
- 面向UI编程:ui.js 1.1 使用观察者模式完成组件之间数据流转,彻底分离组件之间的耦合,完成组件的高内聚
开头想明确一些概念,因为有些概念不明确会导致很多问题,比如你写这个框架为什么不去解决啥啥啥的问题,哎,心累. 什么是框架? 百度的解释:框架(Framework)是整个或部分系统的可重用设计,表现为一 ...
- Odoo安装
打开终端机0. sudo passwd root #设定超级使用者密码1. sudo apt-get update #更新软件源2. sudo apt-get dist-upgrade #更新软件包, ...
- OSS.Social微信项目标准库介绍
经过本周的努力,昨晚终于完成OSS.Social微信项目的标准库支持,当前项目你已经可以同时在.net framework和.net core 中进行调用,调用方法也发生了部分变化,这里我简单分享下, ...
- 复杂的1秒--图解Google搜索技术
谷歌(Google),一个非常成功,但又十分神秘,而且带有几分理想化色彩的互联网搜索巨人,它还是一家相当了不起的广告公司,谷歌首页上的那个搜索按钮是其年赢利200亿美元的杀手级应用,也是Interne ...
- CI Weekly #14 | 如何搭建合适的持续交付开发流程?
时隔 10 个月,flow.ci 开始正式收费上线.为感谢对我们的内测支持,所有内测用户可继续免费使用基础版 30 天,截止至 3 月 15 日失效.欢迎随时告诉我们你对收费版 flow.ci 的反馈 ...
- Cassandra 学习笔记 - 1 - 关于Cassandra
摘要 - Cassandra 的历史 Cassandra能做什么 Apache Cassandra最早是Facebook为了改进他们的Inbox搜索功能,由Avanash Lakshman和Prash ...
- jsp内置对象的方法
JSP内置对象的方法:out:out.print();request:request对象主要用于出列客户端请求. 常用方法: String getParameter(String name) ...