确保TCP客户端和服务器之间没有数据包丢失

3
我正在编写一个Java TCP客户端,它将数据块发送到C服务器。在我的开发PC上,客户端和服务器运行得非常好。然而,当我将代码部署到硬件板上时,出现了数据包丢失的情况。我只有日志记录,我知道服务器没有接收到所有数据包。但是因为我没有硬件来测试,所以首先我想确保客户端代码确实发送了所有必需的数据。
以下是我的代码(Java中的客户端部分)。如何确保数据发送完整?是否有一些带有时间限制的重发命令等等?
        Socket mySocket = new Socket("10.0.0.2",2800);
        OutputStream os = mySocket.getOutputStream();         

        System.out.println(" Sending 8 byte Header Msg with length of following data to Server");
        os.write(hdr, 0, 8);  
        os.flush();

        System.out.println(" Sending Data ");
        start = 0;
        for(int index=0; index < ((rbuffer.length/chucksize)+1); index++){             
            if(start + chucksize > rbuffer.length) {
                System.arraycopy(rbuffer, start, val, 0, rbuffer.length - start);
            } else {
                System.arraycopy(rbuffer, start, val, 0, chucksize);
            }
            start += chucksize ;

            os.write(val,0,chucksize);  
            os.flush();
        }

以下是接收此数据的C代码片段:

while ((bytes_received = recv(connected, rMsg, sizeof(rMsg),0)) > 0){

 if (bytes_received  > 0)      // zero indicates end of transmission */
    {
    /* get length of message (2 bytes) */
    tmpVal = 0;
    tmpVal |= rMsg[idx++];
    tmpVal = tmpVal << 8;
    tmpVal |= rMsg[idx++];
    msg_len = tmpVal;

    len = msg_len;
    //printf("msg_len = %d\n", len);
    printf("length of following message from header message : %d\n", len);
    char echoBuffer[RCVBUFSIZE] ;        
    memset(echoBuffer, 0, RCVBUFSIZE);
    int recvMsgsize = 0;

    plain=(char *)malloc(len+1);
    if (!plain)
    {
     fprintf(stderr, "Memory error!");
    }
    for( i = RCVBUFSIZE; i < (len+RCVBUFSIZE); i=i+RCVBUFSIZE){
     if(i>=len){
         recvMsgSize = recv(connected, echoBuffer, (len - (i-RCVBUFSIZE)), 0);
         memcpy(&plain[k], echoBuffer, recvMsgSize);
         k = k+recvMsgSize;
     }
     else{
         recvMsgSize = recv(connected, echoBuffer, RCVBUFSIZE, 0);
         memcpy(&plain[k], echoBuffer, recvMsgSize);
         k = k+recvMsgSize;
      }
    }
 }//closing if
}//closing while

你怎么知道数据包丢失了?TCP是一种可靠的传输协议。这意味着如果写入或读取失败,就会出现错误。这可能是应用程序中的错误吗? - Miserable Variable
这就是我无法理解的地方。TCP 是一种可靠的协议。我将发布我的 C 端服务器接收数据块的代码片段。这也可能是硬件问题。我在 Java 方面还是个新手,只想确保是否有任何重新发送数据包的方法。当然,内核必须为协议的可靠性而执行此操作...您怎么看? - user489152
数据包可能会在输入层被丢弃,但TCP会重新发送它们等等,按照它们被发送的顺序将它们传递给接收者。我认为你的服务器或客户端存在一些错误。 - Miserable Variable
你如何在C代码中将数据读入到len变量中? - nos
@nos:我首先发送一个8字节的头部,其中2个字节表示接下来的数据长度。请参见顶部更新的代码。 - user489152
@user489152 嗯,那段代码是错的,rMsg有多大?如果它只有2个字节,你没有处理recv仅返回1个字节的情况。这种情况可能会发生,而且总有一天会发生。如果它比2个字节大,你将扔掉它可能接收到的所有其他字节。 - nos
2个回答

5

首先,在TCP/IP中不存在数据包丢失这样的事情。该协议旨在可靠地按正确的顺序发送一系列字节。因此,问题必须出在您的应用程序或另一方。

我并不想分析整个arraycopy()疯狂(C语言?),但为什么不通过BufferedOutputStream一次性发送整个rbuffer呢?

OutputStream os = new BufferedOutputStream(mySocket.getOutputStream());

然后:

os.write(rbuffer);

相信我,BufferedOutputStream正在做同样的事情(将字节收集到块中并一次性发送它们)。或者我可能漏掉了什么?

@user489152:所以在 for 循环中数据会发生变化? - home
不,数据没有改变。我设置了一个缓冲区,因为我担心可能会接收到大块数据,这可能会阻塞网络并且超出TCP窗口大小,仅此而已。 - user489152
2
@user489152,需要记住的一件事是Java是大端字节序,而C具有处理器的字节序,因此如果您正在发送从Java基元构建的字节流,则可能需要在C端反转字节顺序。 - Gnat
@Gnat:谢谢。但是在我的开发系统上,我的代码可以正常工作。我收到了我发送的确切数据。然而,在硬件上运行时似乎存在一些数据包丢失。 - user489152
我能否使用类似于C语言的while循环,通过读取流并写入直到它为负数来进行检查?请帮忙。 - user489152
显示剩余2条评论

0

我按照以下方式更改了C端程序,现在它可以正常工作:

                    printf("length of following message from header message : %d\n", len);

                    plain=(char *)malloc(len+1);
                    if (!plain)
                    {
                        fprintf(stderr, "Memory error!");

                    }
                    memset(plain, 0, len+1);
                    int remain = len;
                    k= 0;
                    while (remain){
                        int toGet = remain > RCVBUFSIZE ? RCVBUFSIZE : remain;
                        remain -= toGet;
                        int recvd = 0;
                        while(recvd < toGet) {
                            if((recvMsgSize = recv(connected, echoBuffer, toGet-recvd, 0)) < 0){
                                printf("error receiving data\n");
                            }
                            memcpy(&plain[k], echoBuffer, recvMsgSize);
                            k += recvMsgSize;
                            printf("Total data accumulated after recv input %d\n", k);
                            recvd += recvMsgSize;
                        }
                    }

1
为什么要从“remain”中减去“toGet”?如果由于某些原因您的“recv”读取的字节数少于“toGet”,那么您将忽略流的末尾。为了安全起见,您应该减去“recvMsgSize”。 - Joachim Sauer
我的recv函数获取到了toGet-recvd字节,其中recvd += recvMsgSize。 - user489152
你对此有何看法?这是一个好的方式吗?请查看我与此代码相关的问题:http://stackoverflow.com/questions/7555242/corrupted-tcp-packets-during-flooding-attack-c-server-java-client - user489152

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接