Python高级技术之:`socket`模块的`recv()`和`sendall()`方法:流量控制与缓冲区管理。

各位观众老爷,晚上好!我是今晚的主讲人,咱们今儿个不整虚的,直奔主题:Python socket模块的recv()sendall()方法,重点聊聊流量控制和缓冲区管理。这俩哥们儿看似简单,但用起来门道可不少,稍不留神,你的程序就可能掉坑里。

一、socket编程的那些事儿:打个招呼先

在深入recv()sendall()之前,咱们先简单回顾一下socket编程的基础。简单来说,socket就是应用程序之间进行网络通信的接口。你可以把它想象成一个电话插孔,两边的程序通过这个插孔连接起来,然后就可以互相“打电话”聊天了。

Python的socket模块提供了创建和使用socket的工具。使用流程大致如下:

  1. 创建Socket: 选择使用TCP(SOCK_STREAM)还是UDP(SOCK_DGRAM)。TCP是可靠的、面向连接的,UDP是不可靠的、无连接的。
  2. 绑定地址: 将Socket绑定到一个IP地址和端口号上,这样其他程序才能找到你。
  3. 监听连接(TCP): 如果是服务器,需要监听来自客户端的连接请求。
  4. 接受连接(TCP): 接受客户端的连接请求,建立连接。
  5. 发送/接收数据: 使用send()/recv()或者sendall()/recv()来发送和接收数据。
  6. 关闭连接: 通信完成后,关闭Socket连接。

二、recv():小口慢咽,接收数据

recv(bufsize)方法用于从socket接收数据。 bufsize参数指定了可以接收的最大数据量(字节数)。

  • 工作原理: recv()尝试从socket缓冲区读取最多bufsize个字节的数据。
  • 返回值: 返回接收到的字节串,如果连接已关闭,则返回空字节串b''
  • 阻塞行为: 默认情况下,recv()是阻塞的。这意味着如果socket缓冲区中没有数据,recv()会一直等待,直到有数据到达或者连接关闭。
  • 数据完整性: 重点来了! recv() 不保证 接收到bufsize个字节的数据。它可能只接收到一部分数据,即使发送方发送了更多的数据。这是因为网络传输的复杂性和缓冲区大小的限制。

举个栗子:

import socket

# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定地址
s.bind(('127.0.0.1', 8888))

# 监听连接
s.listen(1)

# 接受连接
conn, addr = s.accept()
print(f"Connected by {addr}")

# 接收数据
data = conn.recv(1024) # 尝试接收1024字节的数据
print(f"Received: {data}")

# 关闭连接
conn.close()
s.close()

在这个例子中,conn.recv(1024)尝试接收1024字节的数据,但实际上可能接收到的数据少于1024字节。所以,在使用recv()时,你需要做好处理接收到的数据可能不完整的准备。

三、sendall():一口闷,发送全部数据

sendall(bytes)方法用于从socket发送所有数据。

  • 工作原理: sendall() 会尝试发送 bytes 中的所有数据。
  • 返回值: 如果发送成功,sendall()返回None
  • 阻塞行为:recv()一样,sendall()也是阻塞的。
  • 数据完整性: 重点来了! sendall() 保证 发送所有数据,或者抛出一个异常。它会持续发送数据,直到所有数据都发送完毕,或者发生错误。

举个栗子:

import socket

# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接到服务器
s.connect(('127.0.0.1', 8888))

# 要发送的数据
message = b"Hello, world! This is a long message."

# 发送数据
s.sendall(message)
print("Message sent")

# 关闭连接
s.close()

在这个例子中,s.sendall(message) 会尝试发送 message 中的所有数据。如果发送过程中发生错误,比如连接断开,sendall() 会抛出一个 socket.error 异常。

四、recv()sendall():爱恨情仇,流量控制与缓冲区管理

现在,我们来聊聊recv()sendall()与流量控制和缓冲区管理的关系。

  • 流量控制:

    • recv() recv()bufsize 参数直接影响了接收方的接收速率。如果 bufsize 设置得太小,接收方需要多次调用 recv() 才能接收到完整的数据,这会增加开销。如果 bufsize 设置得太大,可能会浪费内存。更重要的是,如果发送方发送数据的速率超过了接收方处理数据的速率,接收方的缓冲区可能会溢出,导致数据丢失。

    • sendall() sendall() 虽然保证发送所有数据,但它并不能解决流量控制的问题。如果接收方处理数据的速率跟不上发送方发送数据的速率,发送方仍然会被阻塞,直到接收方有足够的缓冲区空间来接收数据。

  • 缓冲区管理:

    • recv() recv() 从 socket 缓冲区读取数据。socket 缓冲区是操作系统内核为每个 socket 分配的一块内存区域,用于临时存储接收到的数据。recv() 的作用就是将数据从 socket 缓冲区复制到应用程序的内存中。

    • sendall() sendall() 将数据写入 socket 缓冲区。操作系统内核会将数据从 socket 缓冲区发送到网络上。

五、实战演练:解决数据不完整的问题

前面说了,recv()不保证接收到完整的数据。那么,如何解决这个问题呢?通常有两种方法:

  1. 固定长度的消息: 发送方发送固定长度的消息,接收方每次调用recv()接收固定长度的数据。
  2. 消息边界: 在消息中添加消息边界,接收方通过消息边界来判断消息是否完整。

咱们先来看固定长度的消息:

import socket

def receive_fixed_length_message(sock, msglen):
    message = b''
    while len(message) < msglen:
        chunk = sock.recv(msglen - len(message))
        if chunk == b'':
            return None # 连接已关闭
        message += chunk
    return message

# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8888))
s.listen(1)
conn, addr = s.accept()
print(f"Connected by {addr}")

# 假设消息长度为10字节
message = receive_fixed_length_message(conn, 10)
print(f"Received: {message}")

conn.close()
s.close()

在这个例子中,receive_fixed_length_message() 函数会循环调用 recv(),直到接收到指定长度的消息为止。

再来看消息边界的例子:

import socket

def receive_message_with_delimiter(sock, delimiter=b'n'):
    message = b''
    while True:
        chunk = sock.recv(4096) # 每次接收 4096 字节
        if not chunk:
            return None # 连接已关闭
        message += chunk
        if delimiter in message:
            return message.split(delimiter)[0] # 返回第一个完整的消息

# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8888))
s.listen(1)
conn, addr = s.accept()
print(f"Connected by {addr}")

# 接收消息,以换行符作为分隔符
message = receive_message_with_delimiter(conn)
print(f"Received: {message}")

conn.close()
s.close()

在这个例子中,receive_message_with_delimiter() 函数会循环调用 recv(),直到接收到包含分隔符的消息为止。

六、缓冲区大小的调整:精打细算,物尽其用

socket 缓冲区的大小会影响网络通信的性能。可以使用 getsockopt()setsockopt() 函数来获取和设置 socket 缓冲区的大小。

  • SO_RCVBUF: 接收缓冲区大小。
  • SO_SNDBUF: 发送缓冲区大小。

举个栗子:

import socket

# 创建一个TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取接收缓冲区大小
recv_buf_size = s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
print(f"Default receive buffer size: {recv_buf_size}")

# 设置接收缓冲区大小
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8192) # 设置为 8KB
recv_buf_size = s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
print(f"New receive buffer size: {recv_buf_size}")

# 获取发送缓冲区大小
send_buf_size = s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
print(f"Default send buffer size: {send_buf_size}")

# 设置发送缓冲区大小
s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) # 设置为 8KB
send_buf_size = s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
print(f"New send buffer size: {send_buf_size}")

s.close()

注意: 操作系统对 socket 缓冲区的大小有限制。通常情况下,你可以增大缓冲区的大小,但不能超过操作系统的限制。

七、总结:recv()sendall()的正确使用姿势

方法 功能 是否保证数据完整性 阻塞行为 流量控制影响

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注