本文主要参考 https://docs.python.org/3/howto/sockets.html 。
本文只讨论 STREAME(比如 TCP) INET(比如 IPv4) socket。
在多种跨进程通信方式中,sockets 是最受欢迎的。对于任意给定的平台,有可能存在其他更快的跨进程通信方式,但对于跨平台交流,sockets 应该是唯一的一种。
创建 Socket
客户端 Socket
通俗的讲,当你点击一个链接,你的浏览器会做以下事情:
# create an INET, STREAMing socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器,如果 URL 中没有指明端口,那么端口为默认的 80
s.connect(("www.python.org", 80))
建立连接后,可以用 socket s
来发送请求。然后 s
会读取回复,然后被销毁。在一次请求-接收过程(或者一系列连续的小的过程)中,客户端 sockets 通常只会被使用一次。
服务端 Socket
对于 web 服务器来说:
# create an INET, STREAMing socket
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind the socket to a public host, and a well-known port
serversocket.bind((socket.gethostname(), 80))
# become a server socket
serversocket.listen(5)
socket.gethostname()
的地址可以被外部看到。
listen
告知 socket 库,监听队列中最多能有 5 个连接请求,队列满了之后的请求会被拒绝。
主循环
while True:
# accept connections from outside
(clientsocket, address) = serversocket.accept()
# now do something with the clientsocket
# in this case, we'll pretend this is a threaded server
ct = client_thread(clientsocket)
ct.run()
没有连接时 accept 会一直阻塞。
主循环通常有三种工作方式:
- 分派一个线程去处理
clientsocket
- 创建一个进程去处理
clientsocket
- 重构以使用非阻塞 sockets 并使用
select
在我们的服务器 socket 和clientsocket
多路传输(multiplex)
上面的代码就是服务端 socket 所做的。它不发送、接收任何数据。它只是生产 clientsocket
。每个 clientsocket
被创建出,用来响应 connect()
来的 “client” sockets(比如浏览器)。
服务端 socket 在创建 clientsocket
后,又重新返回去监听更多的连接。那两个客户端 sockets 在自由地交谈 -- 使用动态分配的并在谈话结束后会被回收的端口。
使用 Socket
作为设计者,你必须决定客户端 sockets 之间的交流规则。
send
和 recv
操作网络 buffers,它们不一定会处理所有你传递给它们的 bytes,因为它们集中于处理网络 buffers。当网络 buffers 被 send
或 recv
时,它们会返回它们处理的 bytes 数目。调用它们以确保所有数据已被处理是你的责任。
当 recv
返回 b""
或者 send
返回 0 意味着另一边已经关闭了(或正在关闭)连接。如果是 recv ,那么你将不会从这个连接再收到任何数据,但你可能可以成功的发送数据,在下文会谈到。如果是 send,那你不能再向这个 socket 发送任何数据。
类似 HTTP 协议在一次交谈中只使用一个 socket。客户端 socket 发送请求,读取回复,然后客户端 socket 被遗弃。所以客户端可以通过接受到 0 bytes 的回复来发现交谈结束了。
如果你打算为了将来的传输复用你的 socket,你需要知道 socket 中没有传输结束(EOT)这个标识。
总结一下:如果 send
或 recv
0 bytes,那么这个连接已经被关闭了。如果一个连接没有被关闭,你可能永远在等 recv
,因为 socket 不会告诉你现在并没有更多消息了。
所以信息
- 必须是固定长度的
- 或者被划定了界限
- 或者指出信息有多长
- 或者以关闭连接来结束
完全由你来选择使用何种方法。
信息长度指 send
和 recv
的信息的长度。比如 send
发送 bytes,那么是 str 转换为 bytes 后的信息的长度而不是 str 的表示的信息的长度。
最简单的方法是固定长度的消息:
class MySocket:
"""demonstration class only
- coded for clarity, not efficiency
"""
def __init__(self, sock=None):
if sock is None:
self.sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
else:
self.sock = sock
def connect(self, host, port):
self.sock.connect((host, port))
def mysend(self, msg):
totalsent = 0
while totalsent < MSGLEN:
sent = self.sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError("socket connection broken")
totalsent = totalsent + sent
def myreceive(self):
chunks = []
bytes_recd = 0
while bytes_recd < MSGLEN:
chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
if chunk == b'':
raise RuntimeError("socket connection broken")
chunks.append(chunk)
bytes_recd = bytes_recd + len(chunk)
return b''.join(chunks)
长度的选择是要发送的信息的最大长度,如果信息长度不足,那么按照约定补充信息直到长度符合,约定的字符也是由你决定。
上面的代码是确保发送、接收的代码不小于定义的长度。
在发送时,由于发送的长度不固定,所以每次要从之前发送的信息之后开始发送。
接收时,要准确地指定需要接收的消息长度。如果指定长度小于实际长度,那么信息就不完整;反之,会一直等待信息发送。又由于最多接收 2048 bytes,所以要 min(MSGLEN - bytes_recd, 2048)
。
Python 的 len()
可以计算含有