【问题标题】:How to read all the data in socket programming (c++)?如何读取套接字编程(c++)中的所有数据?
【发布时间】:2020-05-12 12:40:46
【问题描述】:

我正在学习 C++ 中的套接字编程。我已经通过缓冲区值初始化为 10。我使用 select() 函数来监视套接字。当客户端发送的数据大于我的缓冲区可以容纳的数据时,它将读取缓冲区大小的数据,并再次遍历我的所有客户端并读取剩余的数据。如何一次读取所有数据而无需再次通过循环解析读取剩余数据?

感谢您的帮助。

        for(i = 0;i < max_clients; i++){
        sd = clients_list[i];
        if(FD_ISSET(sd,&temp)){

            char buf[10];
            int n = recv(sd, buf, sizeof(buf),0);
            if(n == -1){
                perror("recv");
            }else if(n == 0){

                cout << "Client is GONE " << endl;
                close(sd);
                clients_list[i] = 0;
            }
            else{

                buf[n] = '\0';
                cout << "From the node: " << buf << endl;
            }

        }
    }

【问题讨论】:

  • 您好,无法理解您的问题。循环所有客户端并读取所有缓冲区直到传输所有数据是正常过程。您可以扩大缓冲区以获取每个块的更多数据,但仍会循环。也许如果这不能回答您的问题 - 请更新您的问题以更准确。
  • 嗨。如果我要读取超过 10 个字节的数据,它将读取这 10 个字节,将其打印出来并返回并通过 for 循环解析,即通过所有 FD_SET 并再次检查某些 I/O 并打印剩余数据。有没有一种方法可以一次读取所有数据而无需一次又一次地通过 for 循环并检查该套接字的某些 I/O。我希望这可以解决这个问题。 @TomFreudenberg
  • @thevortex_17 recv() 不能保证一次性接收所有请求的字节,所以无论如何你都需要一个循环。发送方可以在发送缓冲区内容之前发送缓冲区大小,然后接收方将知道预期的字节数,然后它可以继续读取直到实际到达的字节数,无论它需要多少 recv() 调用。跨度>
  • @thevortex_17 也许你可以只改变循环。而是循环所有客户端并只读取一个块,您可能会保留并循环直到当前客户端的数据不再可用。这是你喜欢的吗?

标签: c++ network-programming


【解决方案1】:

只是为了完成我的评论——这段代码 sn-p 可能有助于理解

未经测试的代码

for (i = 0;i < max_clients; i++) {
    sd = clients_list[i];
    if (FD_ISSET(sd,&temp)) {

        char buf[10];
        int n;
        int flag;

        do {
          n = recv(sd, buf, sizeof(buf),0);
          if (n > 0) {
            buf[n] = '\0';
            cout << "From the node: " << buf << endl;
            ioctl(sd, FIONREAD, &flag);
          } else {
            flag = 0;
          }
        } while (flag > 0);

        if (n < 0) {
          perror("recv");
          // in case of an error you may actively close the socket
          // and end transmission by server
          close(sd);
          clients_list[i] = 0;
        } else {
          cout << "Client has currently no further transmission " << endl;
          // don't close socket maybe later new transmission
          // active close socket by server process only if
          // e.g. a timeout has reached 
        }

    }
}

【讨论】:

  • 太好了,也许您也可以将其选中作为您在此处发出“已解决”信号的首选答案。干杯。
【解决方案2】:

据我所知,您无法确定对方发送的缓冲区大小。我认为你的方式是最好的处理方式,但你总是可以使用一些技巧。 1. 确定将作为常数发送的缓冲区大小,因此您每次都可以获得所需的数量。 2.我认为最好的一种方式:使用自制协议来确定发送的消息的长度。例如,您可以读取前 4 个字节以确定消息的大小,然后按给定大小从缓冲区中读取数据。

您可以使用下一个转换:

char* c = new char[4];
recv(sd, c, sizeof(c), 0);
int len = (*(int*)c);
delete[] c;
char* but = new char[len];
recv(sd, but, len, 0);

【讨论】:

  • 我认为 char* c = new char[4]; 可以替换为 char c[4]; 而不会丢失任何功能。
  • 你说得对,不过不忘释放内存也没关系。
  • 它仍然需要两次堆访问,而不是将字节放在堆栈上;另外,即使你永远不会忘记释放内存,下一个来修改代码的人也可能不会那么精明。
【解决方案3】:

首先,网络是不可靠且不可预测的,所以我们无法知道我们会收到多少字节,这是设计服务器的起点。

其次,我认为如果你想要一个更优雅的服务器,你可能想尝试epoll或IOCP,它们仍然有循环,但I/O复用性能要好得多。

第三,如果你想接受所有的数据,你可以尝试构造一个缓冲区。在实际网络中,我们总是使用 kafka 或其他一些应用程序来缓冲数据。

【讨论】:

    【解决方案4】:

    如何在不通过循环解析的情况下一次读取所有数据 再次读取剩余数据?

    一般来说,您不能,因为所有数据可能还不可用。如果你愿意,你可以告诉recv() 阻止直到sizeof(buf) 字节可用,方法是将MSG_WAITALL 而不是0 传递给它的最终(“标志”)参数......但请注意,即使那样它也是在某些情况下,recv() 可能返回少于sizeof(buf) 字节(例如,如果捕获到信号,或者在接收到sizeof(buf) 字节之前关闭连接,等等)。因此,即使您使用MSG_WAITALL,您仍然希望实现短读处理逻辑以获得 100% 正确的行为。

    另请注意,发送方没有义务及时发送您期望的所有字节,网络也没有义务及时发送所有字节。因此,发件人很有可能会向您发送sizeof(buf)-1 字节,然后等待 15 分钟再发送最后一个字节,如果您在等待最后一个字节的recv() 呼叫中被阻止,那么您的服务器将无法响应在那段漫长的时间里,它的所有其他客户。

    因此,当实现这样的多路复用/单线程服务器时,通常最好将套接字设置为非阻塞模式,并为每个客户端的套接字保留一个单独的接收数据缓冲区。这样你就可以遍历你的套接字列表,recv() 尽可能多的数据来自每个套接字(没有阻塞),并将该数据附加到该套接字的关联缓冲区,然后检查缓冲区以查看其中是否有足够的数据尚未处理下一个块,如果这样做,则处理该块,然后从缓冲区中删除数据,然后继续。这样,客户端 B 永远不必等待客户端 A 完成向服务器发送消息。

    【讨论】:

      【解决方案5】:

      我已经使用 ioctl() 函数和 FIONREAD 来检查是否有更多数据可以读取它并且它一直在工作。

      代码如下:

      for(i = 0;i < max_clients; i++){
              sd = clients_list[i];
      
              if(FD_ISSET(sd,&temp)){
                  receive_more:
                      char buf[10];
                      int arg = 0;
                      int n = recv(sd, buf, sizeof(buf),0);
      
                      if(n == -1){
                          perror("recv");
                      }else if(n == 0){
      
                          cout << "Client is GONE " << endl;
                          close(sd);
                          clients_list[i] = 0;
                      }
                      else{
      
                          buf[n] = '\0';
                          cout << "From the node: " << buf << endl;
                          ioctl(sd,FIONREAD,&arg);
                          if(arg > 0){
                              goto receive_more;
                          }
                      }
      
              }
          }
      

      【讨论】:

      • 嗨@thevortex_17 不建议使用 GOTO。查看我更新的答案。使用 ioctl() 调用更新了 DO WHILE 循环。
      猜你喜欢
      • 2018-09-13
      • 2013-07-10
      • 2012-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-15
      相关资源
      最近更新 更多