首先,我以前从未使用过C(主要是Java,这就是你会发现我写一些天真的C代码的原因)。我在C写一个简单的命令解释器。我有这样的东西:
//Initialization code
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("Select dead");
exit(EXIT_FAILURE);
}
....
....
//Loop through connections to see who has the data ready
//If the data is ready
if ((nbytes = recv(i, buf, sizeof(buf), 0)) > 0) {
//Do something with the message in the buffer
}
现在,如果我正在查看类似于长段命令的内容,很明显256字节缓冲区将无法获得整个命令。目前,我正在使用2056字节缓冲区来获取整个命令。但是,如果我想使用256字节缓冲区,我该怎么做呢?我是否跟踪哪个客户端给了我什么数据并将其附加到某个缓冲区?我的意思是,使用像二维数组这样的东西吗?
答案 0 :(得分:3)
是的,通常的方法是为每个客户端提供一个“我收到但未处理的数据”的缓冲区,大小足以容纳最大的协议消息。
您读入该缓冲区(始终跟踪缓冲区中当前有多少数据),并在每次读取后检查您是否有完整的消息(或消息,因为您可能得到两个一旦!)。如果这样做,您将处理该消息,将其从缓冲区中删除,并将所有剩余数据移至缓冲区的开头。
大致类似于:
for (i = 0; i < nclients; i++)
{
if (!FD_ISSET(client[i].fd, &read_fds))
continue;
nbytes = recv(client[i].fd, client[i].buf + client[i].bytes, sizeof(client[i].buf) - client[i].bytes, 0);
if (nbytes > 0)
{
client[i].bytes += nbytes;
while (check_for_message(client[i]))
{
size_t message_len;
message_len = process_message(client[i]);
client[i].bytes -= message_len;
memmove(client[i].buf, client[i].buf + message_len, client[i].bytes);
}
}
else
/* Handle client close or error */
}
顺便说一下,如果errno == EINTR
返回-1,你应检查select()
,然后再循环 - 这不是致命的错误。
答案 1 :(得分:2)
我会为每个客户保留一个结构。每个结构都包含一个指向读取命令的缓冲区的指针。也许你在没有使用时释放缓冲区,或者你可以保留它们。该结构也可以包含客户端的fd。然后你只需要一个你循环的客户端数组(或列表)。
除了256字节可能不够之外,你想要这样做的另一个原因是recv并不总是填充缓冲区。部分数据可能仍在通过网络传输。
但是,如果为每个客户端保留缓冲区,则可能会遇到“slowloris”攻击,即单个客户端不断发送少量数据并占用所有内存。
答案 2 :(得分:1)
当您通过网络获取大量数据时,这可能会非常严重。在分配大数组或多次读取与数据移动之间存在持续的交易。您应该考虑获取现成的缓冲区链表,然后在读取链表的每个节点中的缓冲区时遍历链表。这样它可以优雅地缩放,您可以快速删除已处理的内容。我认为这是最好的方法,也是增强asio实现缓冲读取的方式。
答案 3 :(得分:1)
如果您正在处理多个客户端,则为每个连接分配fork / exec的常用方法。你的服务器会侦听传入的连接,当一个连接时,它会fork并执行一个子版本,然后处理问题的“命令解释器”部分。
这样您就可以让操作系统管理客户端进程 - 也就是说,您不必在程序中使用数据结构来管理它们。在终止时,您仍需要清理服务器中的子进程。
至于管理缓冲区...在发布响应之前,您期望获得多少数据?您可能需要准备动态调整缓冲区的大小。