【问题标题】:C++ socket only receives first send?C++ 套接字只接收第一次发送?
【发布时间】:2012-11-23 11:13:20
【问题描述】:

我已经看了几个小时了。我已经尝试了我能想到的一切,坦率地说这没有意义。我积极地使用套接字发送和接收没有问题,但是一旦我将数据更改为不同的消息,相同的样式,它就会停止接收。我正在使用 TCP。我有一个管理器进程发送最多 N 个带有表数据的路由器消息。我后来发送了一个相同样式的数据包,它接收它,然后停止接收....代码回到循环的顶部,但没有得到更多数据。

哦,我使用的网络代码是 beejs TCP 服务器客户端代码的复制和粘贴。 http://beej.us/guide/bgnet/output/html/multipage/clientserver.html

管理器线程,这部分有效

for(vector< vector<int> >::iterator it = table.begin(); it!=table.end(); ++it ){
            vector< int > d = *it;

            for(vector<int>::iterator itA = d.begin(); itA!=d.end(); ++itA ){
                cout << "Sending... "<< *itA << endl;
                s <<*itA<<" ";
            }
            if (send(new_fd, s.str().c_str(), 13, 0) == -1)
                perror("Serv:send");
            sleep(2);
            logs << "Sent to router " << i <<":\n" << s.str();
            writeLog(logs.str().c_str());
            s.str("");
            logs.str("");


        }
        s<<"done";
        if (send(new_fd, s.str().c_str(), 13, 0) == -1)
            perror("Serv:send");
        writeLog(s.str().c_str());

管理 2,只有第一条消息通过

 for(vector <vector <int > >::iterator it = toSendPackets.begin(); it != toSendPackets.end(); ++it){
    sleep(3);
    vector<int> tsp = *it;
    int a,b,c = 0;
    for(vector<int>::iterator itr = tsp.begin(); itr != tsp.end(); ++itr){
        if(c==0){
            a = *itr;
        }
        if(c==1){
            b = *itr;
        }

        c++;
    }
    ss.str("");
    ss << a << " " << b;
    for(int i = 0; i < numN; i++){
        int curSoc = socketList[i];
        stringstream sl;

        sl<<"sent:"<< ss.str().c_str();
        cout << "sending..  " << ss.str() << " to " << i << endl;
        if (send(curSoc, "HOP", strlen("HOP")+1, 0) == -1)
            perror("Serv:send");
        sleep(2);
        if (send(curSoc, ss.str().c_str(), strlen(ss.str().c_str())+1, 0) == -1)
            perror("Serv:send");
        writeLog(sl.str().c_str());
        sleep(1);

    }
}

路由器代码。

上面的管理器代码和管理器代码 2 都发送到这部分代码。 它得到第一次发送,在这种情况下是“HOP”然后什么都没有?我删除了 HOP 数据包解析,所以它应该只说明读取了某些内容。

if(tid == 0){// TCP
    stringstream s;
    bool proc = true;
    while(!doneFlag){
        proc = true;
        cout << "TCP RECEIVING... " << endl;
        int numbytes = 0;
        while(numbytes==0){
            if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
                perror("recvROUTERThread0");
                exit(1);
            }
        }
        buf[numbytes] = '\0';
        numbytes = 0;
        if(strcmp("Quit",buf)==0){
            writeLog("Quit read",outName);
            doneFlag = true;
            close(net.sockfd);
            floodUDP("Quit");
            pthread_exit(NULL);

        }
        else if(strcmp("HOP",buf)==0){
            cout << "HOP READ" << endl;
            numbytes = 0;
            while(numbytes==0){
                if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
                    perror("recvROUTERThread0");
                    exit(1);
                }
            }
            s << id << "R: Receiving a routing command! " << buf;
            cout << s.str().c_str() << endl;
            writeLog(s.str().c_str(),outName);
            HOPpacket hpo = genHopOrig(s.str().c_str());
            if(hpo.s == atoi(id)){
                printHOP(hpo);
                //              cout << "PACKET " << pr << endl;
                stringstream sl;
                char* hop = generateHopPacket(hpo);
                sl << "Generating HOP packet and sending.. " << hop;
                writeLog(sl.str().c_str(),outName);
                sendHOP(hop);
            }
        }
        else{
            cout << "Table row data from manager" << endl;
            s.str("");
            s << id << "R: MANAGER MESSAGE: " << buf << endl;
            cout << s.str() << endl;
            writeLog(s.str().c_str(),outName);
            int intID = atoi(id);
            vector <int> tr = processTR(buf,intID,basePN);
            table.push_back(tr);
        }

    }
   }

我的输出。在这种情况下,有 10 个路由器正在运行。请注意,我没有更改我的打印件以说明它正在发送 HOP 然后 0 5 ..

sending..  0 5 to 0

HOP READ
WRITTING Manager log:12-11-23::4:6:26:
sent:0 5

sending..  0 5 to 1
HOP READ
WRITTING Manager log:12-11-23::4:6:29:
sent:0 5

sending..  0 5 to 2
HOP READ
WRITTING Manager log:12-11-23::4:6:32:
sent:0 5

sending..  0 5 to 3
HOP READ
WRITTING Manager log:12-11-23::4:6:35:
sent:0 5

sending..  0 5 to 4
HOP READ
WRITTING Manager log:12-11-23::4:6:38:
sent:0 5

sending..  0 5 to 5
HOP READ
WRITTING Manager log:12-11-23::4:6:41:
sent:0 5

sending..  0 5 to 6
HOP READ
WRITTING Manager log:12-11-23::4:6:44:
sent:0 5

sending..  0 5 to 7
HOP READ
WRITTING Manager log:12-11-23::4:6:47:
sent:0 5

sending..  0 5 to 8
HOP READ
WRITTING Manager log:12-11-23::4:6:50:
sent:0 5

sending..  0 5 to 9
HOP READ
WRITTING Manager log:12-11-23::4:6:53:
sent:0 5

sending..  3 9 to 0
WRITTING Manager log:12-11-23::4:6:59:
sent:3 9

sending..  3 9 to 1
WRITTING Manager log:12-11-23::4:7:2:
sent:3 9

sending..  3 9 to 2
WRITTING Manager log:12-11-23::4:7:5:
sent:3 9

sending..  3 9 to 3
WRITTING Manager log:12-11-23::4:7:8:
sent:3 9

sending..  3 9 to 4
WRITTING Manager log:12-11-23::4:7:11:
sent:3 9

sending..  3 9 to 5
WRITTING Manager log:12-11-23::4:7:14:
sent:3 9

sending..  3 9 to 6
WRITTING Manager log:12-11-23::4:7:17:
sent:3 9

sending..  3 9 to 7
WRITTING Manager log:12-11-23::4:7:20:
sent:3 9

sending..  3 9 to 8
WRITTING Manager log:12-11-23::4:7:23:
sent:3 9

sending..  3 9 to 9
WRITTING Manager log:12-11-23::4:7:26:
sent:3 9

【问题讨论】:

  • 您对 send() 的使用不可靠;您需要检查返回码,如果该值不是您要发送的字节数,则可能重试。
  • 您在其他代码片段中对 recv() 和 buf 的使用也是...有问题的。而且你的代码不可能发出你引用的输出。
  • 这是控制台的直接复制粘贴,但我可能没有粘贴所有的写入日志调用。

标签: c++ sockets


【解决方案1】:

当你recv数据时有一个问题,TCP是基于流的套接字而不是基于消息的套接字,所以如果你使用:

send( sock, buf1, len1, 0 );  // Send HOP, since it is small, you OS merge this
send( sock, buf2, len2, 0 );  // with next send!

然后尝试使用recv 接收数据,不能保证您在两次单独调用recv 时接收到数据,因此您可能会在一次调用recv 时接收到两个发送的缓冲区:

recv( sock, buf, len, 0 );  // This may receive both buffers in one call

因此,您对recv 的下一次呼叫将因第一次呼叫中已收到的数据而被阻止!此外,当您发送大缓冲区时,它们可能是另一个问题,然后recv 接收的数据可能少于使用send 传递的单个消息。

您必须定义一个协议,在接收到的流中定义消息结束,然后根据该协议接收您的数据。例如,您可以先发送消息的长度或定义一些指示结束的内容(例如\0\r\n)。

抱歉,我对错误的描述不完整。在您的评论中,您说您增加了HOP 消息大小!但这当然不是一个好习惯,而且增加的大小是如此之小,以至于永远不会强制操作系统立即发送它(实际上没有确定的大小可以强制操作系统这样做)。如果您希望操作系统立即发送您的数据,您应该使用TCP_NO_DELAY 选项禁用 Nagle 算法,但在此之前请查看How do I use TCP_NODELAY?这样做也不是一个好习惯,除此之外,这样做会导致您的数据包在您调用 send 时立即发送,但它不会强制接收方的操作系统单独接收消息! 那么什么是这样做的正确方法?

我详细解释问题:

// I don't know exact value of MAXDATASIZE but I will assume it is 128
char buf[ MAXDATASIZE ];

int numbytes = recv( sock, buf, MAXDATASIZE, 0 );
if( numbyte == -1 ) {
    // Handle error
}

// I assume HOP_MSG is a defined constant that contain value of HOP message
if( strcmp(buf, HOP_MSG) == 0 ) { // <-- (1)
    while( (numbytes = recv(sock, buf, MAXDATASIZE, 0)) != -1 ) { // <-- (2)
        if( numbytes == 0 ) break;
    }
    if( numbytes == -1 ) {
        // Handle error
    }
}

但是等等!在标有(1) 我假设recv 完全读取HOP_MSG,但为什么?消息边界,所以它可能只读取 2 个字节!或者是1KB(肯定比HOP_MSG多,那我该怎么办??

有效的答案如下:

int receive_till_zero( SOCKET sock, char* tmpbuf, int& numbytes ) {
    int i = 0;
    do {
        // Check if we have a complete message
        for( ; i < numbytes; i++ ) {
            if( buf[i] == '\0' ) {
                // \0 indicate end of message! so we are done
                return i + 1; // return length of message
            }
        }
        int n = recv( sock, buf + numbytes, MAXDATASIZE - numbytes, 0 );
        if( n == -1 ) {
            return -1; // operation failed!
        }
        numbytes += n;
    } while( true );
}
void remove_message_from_buffer( char* buf, int& numbytes, int msglen ) {
    // remove complete message from the buffer.
    memmove( buf, buf + msglen, numbytes - msglen );
    numbytes -= msglen;
}

void main() {
    SOCKET s;
    char buf[ MAXDATASIZE ];
    int numbytes = 0, msglen;
    // Initialize socket and connect to server, you already do that

    while( true ) {
        msglen = receive_till_zero( s, buf, numbytes );
        if( msglen == -1 ) {/* Handle error */}

        if( !strcmp(buf, HOP_MSG) ) {
            remove_message_from_buffer( buf, numbytes, msglen );
            msglen = receive_till_zero( s, buf, numbytes );
            if( msglen == -1 ) {/* Handle error */}

            std::cout << "Message received from server: " << buf << std::endl;
            remove_message_from_buffer( buf, numbytes, msglen );
        }
    }
}

通过调试这段代码你肯定会明白它的用途,receive_till_zero假设之前调用recv的缓冲区中已经有一些待处理的数据,所以它会首先检查缓冲区中是否有完整的消息或不是,而且它永远不会假设仅通过一次调用 recv 即可完成接收数据,因此它将在循环中调用 recv 直到它在缓冲区中看到 \0。在我们完成缓冲区中的数据后,我们调用remove_message_from_buffer 来吃那个数据并且只吃那个数据,而不是仅仅从缓冲区的开头开始接收,因为它们可能已经在缓冲区中有一些数据。

正如您所见,代码有点复杂,为了获得更好的编程模型和更好的 C++ 代码,您可以使用 boost::asio,它具有非常好的设计并且可以完美地与 C++ 和 iostream 配合使用

【讨论】:

  • 我玩的比较多。 A 使消息更大,添加了 \0,问题不在于它同时收到两条消息。它循环回来并等待接收,但仍然没有任何东西通过 >.
  • 我最终只是一次发送所有消息,然后破解其余的工作。虽然你的更新很有帮助,但下次我做网络编程时它会帮助我。
猜你喜欢
  • 2021-07-08
  • 1970-01-01
  • 2013-04-01
  • 2011-06-14
  • 2023-04-05
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
  • 2016-07-28
相关资源
最近更新 更多