Beej 提到的方法,以及 AlastairG 提到的方法是这样的:
对于每个并发连接,您维护一个已读取但尚未处理的数据的缓冲区。(这是 Beej 建议将缓冲区大小设置为最大数据包长度的两倍)。显然,缓冲区一开始是空的:
unsigned char recv_buffer[BUF_SIZE];
size_t recv_len = 0;
每当您的套接字可读时,读入缓冲区中的剩余空间,然后立即尝试处理您拥有的内容:
result = recv(sock, recv_buffer + recv_len, BUF_SIZE - recv_len, 0);
if (result > 0) {
recv_len += result;
process_buffer(recv_buffer, &recv_len);
}
将尝试process_buffer()
将缓冲区中的数据作为数据包进行处理。如果缓冲区还没有包含完整的数据包,它只会返回 - 否则,它会处理数据并将其从缓冲区中删除。因此,对于您的示例协议,它看起来像:
void process_buffer(unsigned char *buffer, size_t *len)
{
while (*len >= 3) {
/* We have at least 3 bytes, so we have the payload length */
unsigned payload_len = buffer[2];
if (*len < 3 + payload_len) {
/* Too short - haven't recieved whole payload yet */
break;
}
/* OK - execute command */
do_command(buffer[0], buffer[1], payload_len, &buffer[3]);
/* Now shuffle the remaining data in the buffer back to the start */
*len -= 3 + payload_len;
if (*len > 0)
memmove(buffer, buffer + 3 + payload_len, *len);
}
}
(该do_command()
函数将检查有效的标头和命令字节)。
这种技术最终是必要的,因为任何 recv()
都可以返回一个短的长度 - 使用您提出的方法,如果您的有效负载长度为 500,但下一个recv()
仅返回 400 个字节会发生什么?无论如何,您必须保存这 400 个字节,直到下次套接字变得可读为止。
当您处理多个并发客户端时,您只需拥有一个recv_buffer
和recv_len
每个客户端,并将它们填充到每个客户端的结构中(其中可能还包含其他内容 - 例如客户端的套接字,可能是它们的源地址,当前状态等)。