0
点赞
收藏
分享

微信扫一扫

socket(普通套接字、非阻塞套接字、IO多路复用)


何为socket

socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

建立套接字

服务器:

import socket
import time
server=socket.socket()
print(server)#<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
server.bind(("",2222))
server.listen(5)
print(server)#<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 2222)>
print("开始监听")
while True:
con,adr=server.accept()
data=con.recv(1024)
print(data.decode())
while True:
msg=input(">>>")
con.send(msg.encode())
data=con.recv(1024)

客户端:

import socket
import time
c = socket.socket()
c.connect(("127.0.0.1",2222))
print("连接成功")
while True:
msg=input("<<<")
c.send(msg.encode())
while True:
data=c.recv(1024)
print(data.decode())
msg = input("<<<")
c.send(msg.encode())

模拟聊天,现在当服务器运行后,客户端才能连接,并且只有客户端给服务端发话了,等服务端收到了才能给客户端回,客户端收到服务端的回话才能再给服务端发话。

为什么都要等接收到对方信息才能再回复对方呢?

因为​recv()和send()都有阻塞​,++接收和发送是相互切换的,当发送消息后,就到了接收阻塞,当接收到消息后就到了发送阻塞++。

listen是什么?

listen是接听,里面的参数代表服务端一次可以连接多少个客户端。

既然recv和send是阻塞的,那么我们是否存在非阻塞?

非阻塞套接字

服务端:

import socket
import time
server=socket.socket()
print(server)
server.setblocking(False)
server.bind(("",3333))
server.listen(5)
print(server)
print("开始监听")
con_list=[]
while True:
try:
con,adr=server.accept()
con.setblocking(False)
except BlockingIOError as e:
pass
else:
print("客户端{},建立连接".format(adr))
con_list.append(con)
for con in con_list:
try:
data=con.recv(1024)
except BlockingIOError as e:
pass
else:
print(data)
try:
msg=input(">>>")
con.send(msg.encode())
except BlockingIOError as e:
print(e)

客户端

import socket
import time
c = socket.socket()
c.setblocking(False)
try:
c.connect(("127.0.0.1",3333))
except BlockingIOError as e:
pass
else:
print("连接成功")
while True:
msg=input("<<<")
try:
c.send(msg.encode())
except BlockingIOError as e:
pass
else:
try:
data=c.recv(1024)
except BlockingIOError as e:
print(e)
else:
print(data)

setblocking(False)​ 服务端server设置为非阻塞,conn设置为非阻塞,客户端 c设置为非阻塞。

设置非阻塞后,accept、recv、send、c.connect()都会报BlockingIOError, 所以要try包围。

现在的模式:服务端只有在接收到客户端的信息后才能回复信息,客户端可以只有先发了信息后才能收到信息。

非阻塞套接字的缺点:

关键一: 任何Python操作都是需要花费CPU资源的 !

关键二: 如果资源还没有到达,那么

accept、recv以及

send(在connect没有完成时)

操作都是无效的CPU花费 !

关键三: 对应BlockingIOError的异常处理

也是无效的CPU花费 !

IO多路复用

我们把socket交给操作系统来监控收发信息。

epoll 是惰性的

事件回调

惰性事件回调是由用户进程自己调用的。

操作系统只起到通知的作用。

epoll,目前是linux上效率最高的IO多路复用

IO多路复用选择器:

import socket
import selectors
import time

#sel=selectors.EpollSelector();#选择epoll linux的
sel=selectors.DefaultSelector();#默认选择器 根据操作系统变换
server=socket.socket()
server.bind(('',8888))
server.listen(5)
print("开始监听")

def readable(conn):
data=conn.recv(1024)
if data:
print(data)
else:
sel.unregister(conn)
conn.close()

def acc(server):
conn,addr = server.accept()
print('客户端{},连接成功'.format(addr))
sel.register(conn,selectors.EVENT_READ,readable)
#注册事件
sel.register(server,selectors.EVENT_READ,acc)

while True:
events = sel.select()#返回变化的套接字
for key,mask in events:
print(key)# 第一次打印haha SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 4444)>, fd=4, events=1, data=<function acc at 0x7fc271f77c80>)
#有数据交互:haha SelectorKey(fileobj=<socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 4444), raddr=('127.0.0.1', 54658)>, fd=5, events=1, data=<function readable at 0x7fc27347b2f0>)

time.sleep(5)
callback = key.data
callback(key.fileobj)

  • 第一步:import selectors

  • 第二步:sel=selectors.EpollSelector();#选择epoll 是linux的。
    sel=selectors.DefaultSelector();#默认选择器 根据操作系统变换

  • 第三步:注册事件 sel.register(server,selectors.EVENT_READ,acc)。
    第一个参数是套接字,第二个参数是可读,第三个参数是一个回调函数。

  • 第四步:events = sel.select() 遍历events



举报

相关推荐

0 条评论