普通套接字实现的服务端有什么缺陷呢?
服务端:
import socket
server = socket.socket() # 创建一个套接字,默认使用IPV4和TCP
server.bind(('0.0.0.0', 7001)) # 绑定ip和端口(注意这里是元组)到套接字,4个0表示任何地址可以访问
server.listen() # 开启TCP监听
print('等待客户端的到来.....')
while True:
conn, addr = server.accept() # 处理客户端的连接,阻塞 等待客户连接
print('来自{}的连接'.format(addr))
while True:
data = conn.recv(1024) #阻塞
if data==b'': ##如果客户端断开了,则会不断的循环,所以需要判断处理
conn.close()
break
else:
print('接受消息%s'%data.decode())
conn.send(data)
客户端:
import socket
client = socket.socket()
client.connect(('127.0.0.1', 7001))
while True:
message = input('发送消息>>>')
if message !='q':
client.send(message.encode())
data = client.recv(1024)
print('接受的消息>>{}'.format(data.decode()))
else:
client.close()
print('close client socket')
break
![](https://img.haomeiwen.com/i9286065/0b0fd149e8391fa9.png)
缺陷:
从上面可以看出普通的套接字一次只能服务一个客户端
缺陷造成原因:
- accept阻塞:当没有套接字连接请求过来的时候会一直等待着
- recv阻塞:当连接的这个客户端没有发数据过来的时候,也会一直等待着。
基本IO模型
![](https://img.haomeiwen.com/i9286065/e4163298b95dc7c7.png)
非阻塞套接字与非阻塞IO模型
非阻塞套接字和普通套接字的区别?
非阻塞套接字在accept或recv的时候不会发生阻塞,要么成功,要么失败抛出BlockingIOError异常。
非阻塞IO模型
![](https://img.haomeiwen.com/i9286065/146614ab29251373.png)
![](https://img.haomeiwen.com/i9286065/725c91e1db78bafc.png)
使用非阻塞套接字实现并发
- 什么是并发?
在一个时间段,完成某件事,就是并发
实现并发思路:将程序中阻塞的地方都避免掉,让cpu一直工作。
import socket
server = socket.socket()
server.setblocking(False) #将socket设置为非阻塞,必须在创建socket对象后就进行该操作
server.bind(('0.0.0.0', 7001)) #绑定ip和端口(注意这里是元组)到套接字,4个0表示任何地址可以访问
server.listen()
clientList = []
print('等待客户端的到来.......')
while True:
try:
conn,addr = server.accept() #处理客户端的连接,没有连接就引发BlockingIOError异常,返回值是一对 (conn, address),其中conn是一个新的套接字对象
#可以用于在连接上发送和接收数据,地址是绑定到连接另一端的套接字的地址。
conn.setblocking(False) #对等连接套接字设置非阻塞
clientList.append(conn) #保存已经生成的对等套接字
print('来自{}的连接'.format(addr))
except BlockingIOError as e:
pass
#处理连接上来的客户端发送数据
tempList = [conn for conn in clientList] #利用列表推导式生成了一个临时列表用来去for遍历,而原来的用于移除
for conn in tempList:
try:
data = conn.recv(1024)
if data:
print('接受消息:{}'.format(data.decode()))
conn.send(data)
else:
conn.close()
clientList.remove(conn) #移除断开的对等套接字
break
except BlockingIOError as e:
pass
![](https://img.haomeiwen.com/i9286065/b17b77d488badbc3.png)
非阻塞套接字实现的服务端还有什么不完美的地方吗?
- 关键一: 任何操作都是要花CPU资源的!
- 关键二: 如果数据还没有到达。那么accept, recv操作都是在做无用功!
- 关键三: 对异常BlockIOError的处理也是在做无用功!
总结:不完美的CPU利用率
IO多路复用
IO多路复用也是阻塞IO, 只是阻塞的方法是select/poll/epoll, 好处就是单个进程可以处理多个socket。用select,poll,epoll监听多个io对象,当io对象有变化(有数据)的时候,则立即通知相应程序进行读或者写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写时间就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
IO多路复用模型
![](https://img.haomeiwen.com/i9286065/66576beb522a0101.png)
epoll目前Linux上效率最高的IO多路复用 技术 !
epoll
epoll 基于惰性的事件回调机制。
惰性的事件回调是由用户自己调用的,操作系统只起到通知的作用。
![](https://img.haomeiwen.com/i9286065/68f2630bf08c2156.png)
使用步骤
- 导入IO多路复用选择器
- 注册事件和回调
- 查询
- 回调
import socket
import selectors
server = socket.socket()
server.bind(('0.0.0.0', 7001)) #绑定ip和端口(注意这里是元组)到套接字,4个0表示任何地址可以访问
server.listen()
epoll = selectors.EpollSelector() #生成epoll,epoll是linux上的
def read(conn):
data = conn.recv(1024)
if data==b'':
epoll.unregister(conn) #数据读完就不用再监控了,取消注册
conn.close()
else:
print('收到的数据>>{}'.format(data.decode()))
conn.send(data)
def accept(server):
conn,addr = server.accept()
##注册 给他3个参数1.想看什么,看的是server 2.有没有数据连接(EVENT_READ可读,EVENT_WRITE可写) 3.回调函数
epoll.register(conn, selectors.EVENT_READ,read) #注册时间及回调
epoll.register(server, selectors.EVENT_READ,accept)
while True:
events = epoll.select() #查询所有已经准备好的事件
for key,mask in events:
callback = key.data #函数的名字
sock = key.fileobj #套接字
callback(sock) #函数调用
网友评论