从套接字缓冲数据?

时间:2010-08-26 13:20:20

标签: c buffer sockets

我正在尝试创建一个能够解析客户端请求并发回响应的简单HTTP服务器。

现在我遇到了问题。我必须在请求中一次读取并处理一行,我不知道是否应该:

  • 一次读取一个字节,或
  • 一次读取 N 字节的块,将它们放入缓冲区,然后逐个处理字节,然后再读取一大块字节。

什么是最佳选择,以及为什么

此外,还有一些替代解决方案吗?就像一个从套接字一次读取一行的函数?

6 个答案:

答案 0 :(得分:5)

一次一个字节会破坏性能。考虑一个体积适中的循环缓冲区。

读取缓冲区中任意大小的块。大多数时候你会得到简短的阅读。检查读取字节中http命令的结尾。处理完成命令和下一个字节成为缓冲区的头部。如果缓冲区已满,请将其复制到备份缓冲区,使用第二个循环缓冲区,报告错误或其他任何内容。

答案 1 :(得分:4)

对你的问题的简短回答是,我会一次读取一个字节。不幸的是,这两种情况都有利弊。

对于缓冲区的使用,从网络IO的角度来看,实现可以更有效。反对使用缓冲区,我认为代码本质上比单字节版本更复杂。因此它的效率与复杂性相互影响。好消息是,如果测试表明它是值得的,你可以先实现简单的解决方案,分析结果并“升级”到缓冲方法。

另外,请注意,作为思想实验,我为一个循环编写了一些伪代码,该循环执行基于缓冲区的http数据包读取,如下所示。实现缓冲读取的复杂性似乎并不坏。但请注意,我没有过多考虑错误处理,或者测试它是否可行。但是,它应该避免过度“双重处理”数据,这很重要,因为这会降低效率增益,这是这种方法的目的。

#define CHUNK_SIZE 1024

nextHttpBytesRead = 0;
nextHttp = NULL;
while (1)
{
  size_t httpBytesRead = nextHttpBytesRead;
  size_t thisHttpSize;
  char *http = nextHttp;
  char *temp;
  char *httpTerminator;

  do
  {
    temp = realloc(httpBytesRead + CHUNK_SIZE);
    if (NULL == temp)
      ...
    http = temp;

    httpBytesRead += read(httpSocket, http + httpBytesRead, CHUNK_SIZE);
    httpTerminator = strstr(http, "\r\n\r\n");
  }while (NULL == httpTerminator)

  thisHttpSize = ((int)httpTerminator - (int)http + 4; // Include terminator
  nextHttpBytesRead = httpBytesRead - thisHttpSize;

  // Adding CHUNK_SIZE here means that the first realloc won't have to do any work
  nextHttp = malloc(nextHttpBytesRead + CHUNK_SIZE);
  memcpy(nextHttp,  http + thisHttpSize, nextHttpSize);

  http[thisHttpSize] = '\0';
  processHttp(http);
}

答案 2 :(得分:1)

TCP数据流一次只能在一个IP数据包中进入,根据IP层配置,最多可达1,500个字节。在Linux中,这将等待一个SKB等待应用程序层读取队列。如果您一次读取一个字节,则会在应用程序和内核之间进行上下文切换,只需将一个字节从一个结构复制到另一个结构。最佳解决方案是使用非阻塞IO一次读取一个SKB的内容,从而最大限度地减少交换机。

如果您处于最佳带宽之后,您可以读取更长的字节大小,以便以更昂贵的延迟进一步减少上下文切换,因为更多时间将花费在应用程序复制内存之外。但这仅适用于极端情况,并且应在需要时实施此类代码更改。

如果您检查大量现有HTTP技术,您可以找到其他方法,例如使用多个线程和阻塞套接字,将更多工作推回到内核中,以减少切换到应用程序和返回的开销。

我已经在这里实现了一个非常类似于 torak 的伪代码的HTTP服务器库,http://code.google.com/p/openpgm/source/browse/trunk/openpgm/pgm/http.c这个实现的最大速度提升来自于使所有内容都异步,所以没有任何阻塞。 / p>

答案 3 :(得分:1)

例如,

Indy采用缓冲方法。当代码要求Indy读取一行时,它首先检查其当前缓冲区以查看是否存在换行符。如果不是,则以块的形式读取网络并将其附加到缓冲区,直到出现换行符。一旦这样做,只需将直到换行符的数据从缓冲区中删除并返回到应用程序,将剩余的数据留在缓冲区中以进行下一次读取操作。这使得在简单的应用程序级API(ReadLine,ReadStream等)之间实现了良好的平衡,同时提供了有效的网络I / O(读取套接字中当前可用的所有内容,缓冲它,并确认发送者是不要等待太长时间 - 这种方式需要更少的网络级读取。)

答案 4 :(得分:0)

首先读取一个字节(虽然注意到HTTP中的行以cr / lf结尾),因为它很简单。如果这还不够,那就做更复杂的事情。

答案 5 :(得分:0)

一次读取一个字节数组缓冲区。由于用户和内核模式之间的多个上下文切换(实际上取决于libc),读取单个字符将会变慢。

如果你读缓冲区,你需要做好准备,缓冲区可能没有完全填充(观察长度返回),缓冲区没有足够的字节到行结束或缓冲区包含多行。

网络应用程序中的常见模式是如何将行或固定大小的块请求映射到缓冲区的变量蒸汽(并且通常实现错误,例如可以使用0字节长度的答案)。较高的语言会使您免于这种复杂性。