【问题标题】:C: sockets: can't read the whole server responseC:套接字:无法读取整个服务器响应
【发布时间】:2015-02-17 08:36:01
【问题描述】:

我正在用 C 语言编写一个 IRC 聊天客户端。除了我无法阅读服务器发送的全部答案外,一切都运行良好。这是代码:

char buffer[2048];
write_on_screen(current_page(), "LOG COMMAND", command);
write(sockfd, command, strlen(command)); //write to socket
bzero(buffer, sizeof(buffer));
read(sockfd, buffer, sizeof(buffer));
write_on_screen(current_page(), "RESPONSE", buffer);
return buffer;

大部分时间缓冲区将只包含响应的一部分(小于 2048 字节),而其他时候它什么也不包含。在这两种情况下,如果我在第一个之后再做一个read(),它会返回我剩下的答案或另一个小部分(然后我必须再做一个read())。如果我在write()read() 之间添加sleep(1),我会得到完整的答案,但我确信这不是一个好习惯。 有什么办法可以避免这种情况吗?

提前谢谢你

【问题讨论】:

  • read()来自套接字的返回不一定像告诉读取的那样多。这是根据定义。仔细阅读文档:man 3 readman recv
  • 一个套接字是一个流,但是数据是以数据包的形式发送的。这意味着它可以分段到达。如果你想创建一个聊天服务器,你需要异步处理读写。您可以使用select(或epoll,如果您的目标环境仅限于 Linux)和非阻塞 I/O 在一个线程中执行此操作,或者您可以为每个传入连接设置一个线程,一个用于发送和使用阻塞IO。

标签: c sockets client


【解决方案1】:

你犯了常见的错误。如果不将read()recv() 的结果存储到变量中,就不可能编写正确的网络代码。你必须:

  1. 检查它是否为 -1,如果是,请查看 errno 以查看是否是致命的,它几乎总是除了 EAGAIN/EWOULDBLOCK,如果致命则关闭套接字并放弃该进程。
  2. 检查为零,表示对端断开连接。您必须再次关闭套接字并放弃该进程。
  3. 使用它作为实际接收的字节数。这些函数没有义务也不保证会填充缓冲区。他们在阻塞模式下的合约是阻塞直到出现错误、流结束或至少传输一个字节。如果您期望超过一个字节,则通常必须循环直到得到它。

【讨论】:

    【解决方案2】:

    根据RFC-1459,IRC 中的单行文本最多可包含 512 个字符,并以 CRLF (\r\n) 对终止。然而:

    • 您不能保证每次准确收到 512 个字节。 例如,您可能会收到来自频道中其他人的一条相对较短的消息:Hi!
    • 与上述相关:一组 512 字节可能表示不止一条消息。例如,缓冲区可能包含一整行,以及 下一个的一部分线路:PRIVMSG <msgtarget> <message>\r\nPRIVMS

    鉴于您的 buffer[] 中随时可能有零个或多个 complete 行加上零个或一个 incomplete 行,您可以尝试这样做类似于:

    char buffer[2048];
    
    while(keep_going)
    {
    
      char **lines;
      int i, num_lines;
    
      // Receive data from the internet.
      receiveData(buffer);
    
      // Create an array of all COMPLETE lines in the buffer (split on \r\n).
      lines = getCompleteLines(buffer, &num_lines);
      removeCompleteLinesFromBuffer(buffer);
    
      // Handle each COMPLETE line in the array.
      for (i = 0; i < num_lines; ++i) { handle_line(lines[i]); }
      freeLines(lines);
    
    }
    

    这将允许您一次性处理零个或多个 complete 行,并且保留任何 incomplete 行(即最后一个 \r\n 对之后的任何内容)直到下一个调用receiveData()

    【讨论】:

      【解决方案3】:

      您需要循环 read() 直到检测到 CRLF。

      一种可能的方法是:

      #include <stdio.h>
      #include <unistd.h>
      #include <errno.h>
      
      ssize_t read_until_crlf(int sd, char * p, size_t s, int break_on_interupt)
      {
        ssize_t bytes_read = 0;
        ssize_t result = 0;
        int read_cr = 0;
        int read_crlf = 0;
      
        while (bytes_read < s)
        {
          result = read(sd, p + bytes_read, 1);
          if (-1 == result)
          {
            if ((EAGAIN == errno) || (EWOULDBLOCK == errno))
            {
              continue;
            }
            else if (EINTR == errno)
            {
              if (break_on_interupt)
              {
                break;
              }
      
              continue;
            }
            else
            {
              perror("read() failed");
              break;
            }
          }
          else if (0 == result)
          {
            break; /* peer disconnected */
          }
      
          if ('\r' == p[bytes_read])
          {
            read_cr = 1;
          }
          else if (('\n' == p[bytes_read]) && read_cr)
          {
            read_crlf = 1;
            break; /* CRLF detected */
          }
          else
          {
            read_cr = 0;
          }
      
          ++bytes_read;
        }
      
        if (!read_crlf)
        {
          result = -1; /* Buffer full without having read a CRLF. */
          errno = ENOSPC; /* ... or whatever might suite. */
        }
      
        return (0 >= result) ?result :bytes_read;
      }
      

      这样称呼它:

      #include <stdio.h>
      
      ssize_t read_until_crlf(int sd, char * p, size_t s, int break_on_interupt);
      
      int main(void)
      {
        int sd = -1;
      
        /* init sd here */
      
        {
          char line[2048] = "";
          ssize_t result = read_until_crlf(sd, line, sizeof line, 0);
          if (-1 == result)
          {
            perror("read_until_newline() failed");
          }
      
          printf("read '%s'\n", line);
        }
      
        return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2018-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-26
        • 2012-03-30
        • 2014-02-06
        • 2014-08-22
        • 2018-06-25
        相关资源
        最近更新 更多