如何在套接字编程(c++)中读取所有数据?

How to read all the data in socket programming (c++)?

我正在学习 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] = '[=10=]';
                cout << "From the node: " << buf << endl;
            }

        }
    }

据我所知,您无法确定对方发送的缓冲区大小。我认为你的方法是最好的处理方法,但你总是可以使用一些技巧。 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);

首先,网络不可靠且不可预测,因此我们无法知道我们将接收多少字节,这是设计服务器的出发点。

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

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

How can I read all the data at once without parsing through the loop again to read the remaining data ?

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

另请注意,发送方没有义务及时发送您期望的所有字节,网络也没有义务及时交付所有字节。因此,发件人很可能会向您发送 sizeof(buf)-1 字节,然后在发送最后一个字节之前等待 15 分钟,如果您在等待最后一个字节的 recv() 调用中被阻塞,那么您的服务器在那段时间里将不会对所有其他客户端做出响应。

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

只是为了完成我的评论 - 此代码片段可能有助于理解

未测试代码

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] = '[=10=]';
            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 
        }

    }
}

我已经使用 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] = '[=10=]';
                    cout << "From the node: " << buf << endl;
                    ioctl(sd,FIONREAD,&arg);
                    if(arg > 0){
                        goto receive_more;
                    }
                }

        }
    }