何为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