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交给操作系统来监控收发信息。

image

epoll 是惰性的
事件回调

image

惰性事件回调是由用户进程自己调用的。
操作系统只起到通知的作用。

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

猜你喜欢

转载自blog.csdn.net/weixin_36586120/article/details/79859528