TCP 是一种点对点协议 - 在服务器与accept
客户端连接之后connect
,数据可以向任一方向发送(除非一方关闭用于发送或接收的套接字,即使您有单向应用协议)。每一方都简单地使用write
/send
和read
/ recv
。
至少对于 TCP,“客户端/服务器”简单地描述了主动客户端发起与被动侦听服务器的连接。之后,连接上的通信能力是相同的。客户端通常更简单,但是创建多个同时连接的客户端可能与处理多个客户端的服务器一样复杂。客户端也可能是服务器,甚至“服务器”进程也可以连接回它的一个客户端(恰好也是服务器)。
有大量此类连接的示例代码片段 - 我通常搜索 GNU libc 套接字示例代码,以便让我快速编写基本服务器和客户端(它说明select()
了多个客户端的基于处理;多线程是另一个有效的替代方案) . 阅读http://www.gnu.org/software/libc/manual/html_node/Connections.html以获取背景信息和示例代码。
编辑:讨论您添加到问题中的代码...
int buff_size = 10;
char* buff = malloc(buff_size);
buff_size = recv(client_desc,buff,buff_size,MSG_PEEK);
buff = realloc(buff,buff_size);
recv(client_desc,buff,buff_size,MSG_WAITALL);
第一个recv()
调用的作用是报告客户端断开连接 [返回值 0] 或发生了其他错误/异常 [-1],或者等待(如有必要)直到它可以窥视(复制到buff
而不从流中删除)来自客户端的至少 1 个字节和最多 10 个字节 [返回实际读取的字节数]。
因此,假设您的客户知道他们有一个 N 字节的“逻辑”消息并将其写入连接的末尾。不久之后,他们可能会编写另一个长度为 O 字节的“逻辑”消息。然后你的应用程序被安排在 CPU 上,你的recv()
逻辑开始运行。peek 可以检索从 1 到 max(10, N + O) 字节的任何地方......所有这些都是完全有效的非错误行为。该数据只是发送的两条消息中的第一个/几个字节。N 和 O 的值不能从“窥视”的字节数(即从buff_size = recv(...)
)可靠地推断出来。
为了使这更具体,假设第一条消息是“ABCDEFGHIJKLMNOPQRSTUVWXYZ”。您的 peek 可以加载到缓冲区“A”(在这种情况下 buff_size 将设置为 1)、“AB”(2),直到“ABCDEFGHIJ”(10),但是您的程序无法知道还有多少字节在那条消息中。如果您的消息是“ABCD”和“EFGH”,那么您在查看时可能仍然会从“A”到“ABCDEFGH”得到任何信息。
只有几种实用的方法可以让接收方知道需要多少数据:
- 编写
recv()
代码的程序员知道send()
代码总是发送一些特定数量的字节
- 接收到的数据嵌入了消息中数据量的指示符
- 它可能有一个“标题”,其中数据的第一位表示总消息长度
- 知道标头分层多长时间的问题会调用这些相同的选择,但通常决定消息标头可以是固定长度(上面的第一个选择)并且可以使用其他约定之一灵活调整消息主体的大小是很实际的
- 它可以使用始终表示消息结尾的“分隔符”字符,流行的选择包括换行和/或回车以及 NUL 字符(用于二进制流)
这些设计决策是为服务器和客户端之间的通信创建应用程序级协议的一部分。该协议位于 TCP 协议之上。
当你有一个协议时,通常还有recv()
代码的实现选择。最简单的方法通常是使用 MSG_WAIT_FOR_ALL 接收固定长度的标头,然后使用第二个 MSG_WAIT_FOR_ALL 来检索标头承诺的额外数据字节。正如我之前的评论中提到的,对于非常大的消息,可能存在缓冲问题。
如果您在要发送的消息的前面嵌入一个长度,最简单的方法可能是将其写为一个固定宽度的数字字段,如下所示:
const char* p = asprintf("%06d%s", message_length, message_data);
然后检索器可以说:
char header[6 + 1];
header[6] = '\0'; // make sure it's NUL terminated as per C ASCIIZ string conventions
if (recv(client_desc,header,sizeof header,MSG_WAITALL) == sizeof header)
{
int message_size = atoi(header);
char* buff = malloc(message_size);
if (recv(client_desc, buff, message_size, MSG_WAITALL) == message_size)
{
// use the message in buff...
}
else
fprintf(stderr, "couldn't retrieve all the message body\n");
}
else
fprintf(stderr, "couldn't retrieve all the message header\n");
使用这种方法,消息本身可能看起来像“000005hello”或“000011hello world”。计数可以选择性地包括标头中的字节。许多协议使用数字的 2 的补码编码,例如消息长度 - 您可以使用 hton 和 ntoh 来标准化大端和小端机器的异构集合中的字节顺序,就像您可能已经为 TCP 所做的那样结构中的端口号sockaddr_in
,然后write(descriptor, &my_32bits, sizeof(my_32bits))
.