前言
- 先将使用过程需要说明的要点记录下:
- socket.socket() 创建了一个 socket 对象,并且支持 context manager type,使用 with 语句,这样就不用再手动调用 s.close() 来关闭 socket
- 1024 是缓冲区数据大小限制最大值参数 bufsize
- 标准库中的selectors 模块提供了高层且高效的 I/O 多路复用,基于原始的 select 模块构建
- asyncio 使用单线程来处理多任务,使用事件循环来管理任务。通过使用 select(),可以创建自己的事件循环,更简单且同步化。当使用多线程时,要处理并发的情况,不得不面临使用 CPython 或者 PyPy 中的「全局解析器锁 GIL」,这有效地限制了可以并行完成的工作量。
- 从 Python 3.3 开始,与 socket 或地址语义相关的错误会引发 OSError 或其子类之一的异常引用
- 需要有不同的网络缓冲区,使用 recv() 方法不断的从缓冲区中读取数据,直到你的应用确定读取到了足够的数据
- 一个通用的方案,很多协议都会用到它,包括 HTTP。在每条消息前面追加一个头信息,头信息中包括消息的长度和其它需要的字段。这样做的话我们只需要追踪头信息,当读到头信息时,就可以查到消息的长度并且读出所有字节然后使用它。
单客户端/单服务器模式
import socket
host="127.0.0.1"
port=65432
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
sock.connect((host,port))
sock.sendall(b"hello world!")
data = sock.recv(1024)
print("Recieved ",repr(data))
import socket
host="127.0.0.1"
port=65432
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sock:
sock.bind((host,port))
sock.listen()
conn,addr = sock.accept()
with conn:
print("Connected by ",addr)
while True:
data = conn.recv(1024)
print(data)
if not data:
break
conn.sendall(data)
多客户端/多服务器模式
import sys
import socket
import selectors
import types
sel = selectors.DefaultSelector()
messages = [b"Message 1 from client.", b"Message 2 from client."]
def start_connections(host, port, num_conns):
server_addr = (host, port)
for i in range(0, num_conns):
connid = i + 1
print("starting connection", connid, "to", server_addr)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
sock.connect_ex(server_addr)
events = selectors.EVENT_READ | selectors.EVENT_WRITE
data = types.SimpleNamespace(
connid=connid,
msg_total=sum(len(m) for m in messages),
recv_total=0,
messages=list(messages),
outb=b"",
)
sel.register(sock, events, data=data)
def service_connection(key, mask):
sock = key.fileobj
data = key.data
if mask & selectors.EVENT_READ:
recv_data = sock.recv(1024)
if recv_data:
print("received", repr(recv_data), "from connection", data.connid)
data.recv_total += len(recv_data)
if not recv_data or data.recv_total == data.msg_total:
print("closing connection", data.connid)
sel.unregister(sock)
sock.close()
if mask & selectors.EVENT_WRITE:
if not data.outb and data.messages:
data.outb = data.messages.pop(0)
if data.outb:
print("sending", repr(data.outb), "to connection", data.connid)
sent = sock.send(data.outb)
data.outb = data.outb[sent:]
if len(sys.argv) != 4:
print("usage:", sys.argv[0], "<host> <port> <num_connections>")
sys.exit(1)
host, port, num_conns = sys.argv[1:4]
start_connections(host, int(port), int(num_conns))
try:
while True:
events = sel.select(timeout=1)
if events:
for key, mask in events:
service_connection(key, mask)
if not sel.get_map():
break
except KeyboardInterrupt:
print("caught keyboard interrupt, exiting")
finally:
sel.close()
import sys
import socket
import selectors
import types
sel = selectors.DefaultSelector()
def accept_wrapper(sock):
conn, addr = sock.accept()
print("accepted connection from", addr)
conn.setblocking(False)
data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"")
events = selectors.EVENT_READ | selectors.EVENT_WRITE
sel.register(conn, events, data=data)
def service_connection(key, mask):
sock = key.fileobj
data = key.data
if mask & selectors.EVENT_READ:
'''
如果 socket 就绪而且可以被读取,mask & selectors.EVENT_READ 就为真,sock.recv() 会被调用。
所有读取到的数据都会被追加到 data.outb 里面
'''
recv_data = sock.recv(1024)
if recv_data:
data.outb += recv_data
else:
'''
如果没有收到任何数据,表示客户端关闭了它的 socket 连接,这时服务端也应该关闭自己的连接。
先调用 sel.unregister() 来撤销 select() 的监控
'''
print("closing connection to", data.addr)
sel.unregister(sock)
sock.close()
if mask & selectors.EVENT_WRITE:
if data.outb:
print("echoing", repr(data.outb), "to", data.addr)
sent = sock.send(data.outb)
data.outb = data.outb[sent:]
if len(sys.argv) != 3:
print("usage:", sys.argv[0], "<host> <port>")
sys.exit(1)
host, port = sys.argv[1], int(sys.argv[2])
lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.bind((host, port))
lsock.listen()
print("listening on", (host, port))
lsock.setblocking(False)
sel.register(lsock, selectors.EVENT_READ, data=None)
try:
while True:
events = sel.select(timeout=None)
"""
sel.select(timeout=None)返回一个(key, events) 元组;
key 包含了 socket 对象「fileobj」和数据对象;
mask 包含了就绪的事件
"""
for key, mask in events:
if key.data is None:
accept_wrapper(key.fileobj)
else:
service_connection(key, mask)
except KeyboardInterrupt:
print("caught keyboard interrupt, exiting")
finally:
sel.close()
参考链接
Python网络编程(第3版)