TCP文件传输中的丢失字节

3
我需要能够读取一个文件,将它分成一些任意大小的数据包,比如说512字节,并通过TCP发送这些数据包。问题在于,接收者没有收到我发送的所有字节。如果我发送1000个数据包,那么接收者从InputStream读取数据时会阻塞,因为他已经没有可读取的数据了,大约只接收了990个数据包左右。
以下是发送和接收的代码(仅包含发送和接收部分):
发送者:
int parts = (int)Math.ceil((double)(file.length()/512.0));

out.println(parts+"");
int readFile;
int i = 0;
while ((readFile = fileIn.read(buffer)) != -1) {
   i++;
   fileOut.write(buffer, 0, readFile);
   fileOut.flush();
   System.out.println("-- Sent packet " + i + "/" + parts + ". " + "Bytes sent = " + readFile);
}

接收器:

int parts = Integer.parseInt(in.readLine());
byte[] buffer = new byte[512];
FileOutputStream pw = new FileOutputStream("file.ext");
DataInputStream fileIn = new DataInputStream(socket.getInputStream());
for(int j = 0; j < parts; j++){
   int read = 0;
   if(j == parts - 1){
      read = fileIn.read(buffer);
      pw.write(buffer, 0, read);
   }else{
      fileIn.readFully(buffer);
      pw.write(buffer);
   }
   System.out.println("-- Received packet " + (j+1) + "/" + parts + ". Read " +read+ " bytes.");
}

我试着增加套接字的发送和接收缓冲区大小,但没有成功。我错过了什么吗?
以下是输出示例:
发送方:
-- Sent packet 1/10. Bytes sent = 512
-- Sent packet 2/10. Bytes sent = 512
-- Sent packet 3/10. Bytes sent = 512
-- Sent packet 4/10. Bytes sent = 512
-- Sent packet 5/10. Bytes sent = 512
-- Sent packet 6/10. Bytes sent = 512
-- Sent packet 7/10. Bytes sent = 512
-- Sent packet 8/10. Bytes sent = 512
-- Sent packet 9/10. Bytes sent = 512
-- Sent packet 10/10. Bytes sent = 234

接收器:

-- Received packet 1/10. Read 512 bytes.
-- Received packet 2/10. Read 512 bytes.
-- Received packet 3/10. Read 512 bytes.
-- Received packet 4/10. Read 512 bytes.
-- Received packet 5/10. Read 512 bytes.
-- Received packet 6/10. Read 512 bytes.
-- Received packet 7/10. Read 512 bytes. (And it blocks here, because there is no more data to read)

你可能永远无法百分之百确定你将以“部分”写入内容。你需要一直写,直到完成为止。缓冲区的大小是多少?基本上,你需要有一个内部循环,并继续向fileOut写入,直到你写完整个缓冲区的内容。 - DejanLekic
“缓冲区”具有所需的数据包大小,本例中为512字节。根据我的输出,文件总是成功地被分成“部分”数据包发送。 - rfsbraz
我认为你的接收器坏了。你不应该依赖于parts读取,以确保你能接收到所有内容。只需循环等待直到有东西可以读取... - DejanLekic
我添加了一个示例输出。 - rfsbraz
我尝试了,问题仍然存在...例如,如果我发送一个大小为1213234字节的文件,大多数情况下接收方会出现此消息“--读取314字节。 (1206066/1213234)”,这意味着-(已读取字节数/总字节数)。 - rfsbraz
2个回答

3
TCP是一种流协议。它没有数据包,只有单个的数据流。
你不应该假定一个单独的write()(带或不带flush())将对应一个单独的read()。因此,你的接收循环for(int j = 0; j < parts; j++)是误导人的:更好的方法是计算读取的字节数,并准备好处理read()调用返回的不同数量的数据。
在评论中,你认为readFully()已经解决了这个问题。然而,我的关注点不在于代码,而是基于数据包的流视图。这会导致像你最后一个fileIn.read(buffer)调用中那样的错误。它可能只返回作为你最后一个“数据包”一部分发送的一半数据,而你永远不会知道!

这就是为什么我使用readFully()方法而不是简单的read()方法。我知道我正在通过流发送数据,但我不想在一个发送中发送所有数据。 - rfsbraz
@WorkingSoHard:我认为你的整体方法是有缺陷的。例如,考虑最后一个read()(在if(j == parts - 1)中)。它可能只会给你发送数据的一半,而你永远不会知道你已经截断了流! - NPE
虽然这是一个问题,需要修复,但它并不是导致我麻烦的问题。我已经将接收器更改为使用read()而不是readFully(),但问题仍然存在。我认为一些字节没有到达接收器。它们有被丢弃的可能吗? - rfsbraz
@WorkingSoHard:TCP提供了可靠的传输。您能否发布完整的、可运行的客户端和服务器,以便我们可以进行实验? - NPE

1
将其分成任意大小的数据包。为什么?这是一种流协议。您可以在发送端将其分割成任意大小,但本地TCP将尽力将您的写入组合成更大的TCP段;本地IP将将其分块为MTU大小的IP数据包;中间路由器可能会进一步分片;远程IP然后将碎片重新组装成数据包;远程TCP然后将数据包重新组装成段;远程应用程序也将根据所有这些以及内核中套接字接收缓冲区的大小和应用程序接收缓冲区的大小来接收数据块。

不要试图超越所有这些处理。你做不到。只需在需要写入时编写所需内容即可。

如果您有“丢失的字节”,那只能是因为您忽略了接收器上read()返回的长度值,或者如果您在发送方使用非阻塞I/O,则是发送方write()返回的长度值。


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