使用套接字进行大文件传输

7
当我使用套接字编程传输大型文件时,接收到的文件是不完整的,即播放时声音不正常。以下是代码:
服务器端代码:
File myFile = new File("abc.mp3");
{
    Socket sock = servsock.accept();
    int packetsize=1024;
    double nosofpackets=Math.ceil(((int) myFile.length())/packetsize);
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));
    for(double i=0;i<nosofpackets+1;i++) {
        byte[] mybytearray = new byte[packetsize];
        bis.read(mybytearray, 0, mybytearray.length);
        System.out.println("Packet:"+(i+1));
        OutputStream os = sock.getOutputStream();
        os.write(mybytearray, 0,mybytearray.length);
        os.flush();
    }
}

客户端:

int packetsize=1024;
FileOutputStream fos = new FileOutputStream("zz.mp3");
BufferedOutputStream bos = new BufferedOutputStream(fos);
double nosofpackets=Math.ceil(((int) (new File("abc.mp3")).length())/packetsize);
for(double i=0;i<nosofpackets+1;i++)
{
    InputStream is = sock.getInputStream();
    byte[] mybytearray = new byte[packetsize];
    int bytesRead = is.read(mybytearray, 0,mybytearray.length );
    System.out.println("Packet:"+(i+1));
    bos.write(mybytearray, 0,mybytearray.length);
}
sock.close();
bos.close();

在客户端,我仅为简单起见使用了new File("abc.mp3")).length(我可以从服务器端发送文件长度)。如果客户端和服务器在同一台机器上,则此代码完美运行,但如果它们在不同的机器上,则文件会变形。

如果您有一个缓冲的输出流,为什么要手动进行缓冲?为什么不一次读取所有内容并全部写出呢? - corsiKa
缓冲区的大小不是有限制吗?所以我将文件分成块并发送它们。 - anonymous123
1
@anonymous 当然有限制,但你不必写那么多来获取分块传输。它会自动发生。 - user207421
4个回答

19

在Java中复制流的规范方法:

int count;
byte[] buffer = new byte[8192];
while ((count = in.read(buffer)) > 0)
{
  out.write(buffer, 0, count);
}

适用于任何大于零的缓冲区大小。切勿将缓冲区大小与输入大小联系起来。


9
我认为问题在于您忽略了各种read调用返回的值,并假设它们完全填充了缓冲区。这是有问题的:
  • 从文件中读取时,最后一次读取可能不会填满缓冲区。

  • 从套接字中读取时,任何读取都可能在填满缓冲区之前返回。

结果是,您的写入将在流中(在服务器端)和目标文件中(在客户端端)放入垃圾。
此外,根据文件大小将文件分成块是毫无意义的。只需读取直到到达文件结尾即可。

6

0

不要使用数据包。
尝试使用ByteArrayOutputStream而不是使用静态字节数组。
继续从输入流中读取,直到达到EOF。将每个读取的内容写入ByteArrayOutputStream。


InputStream is = sock.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int byteToBeRead = -1;
while((byteToBeRead = is.read())!=-1){
baos.write(byteToBeRead);
}
byte[] mybytearray = baos.toByteArray();
bos.write(mybytearray, 0,mybytearray.length);

希望这能有所帮助。

1
为什么?任意文件不一定适合全部存储在内存中。最好的做法是假设它们不会,并相应地进行处理。你的解决方案没有任何值得推荐的地方:冗余初始化;增加的延迟;无限制的内存成本;而且它比我发布的另一种选择更多代码,而后者没有这些问题。 - user207421
@EJP,你是如何解决缓冲区大小的问题的? - Sujay
@Sujay:这并不是很重要。8192是一个方便的数字,既不太大也不太小。我发布的循环将适用于任何缓冲区大小> 0。 - user207421
@Sujay:我在客户端尝试了你的代码,它会出现连接重置异常,而且在客户端创建的文件大小只有1kb。我已经想到了一个解决方案,但是会有数据丢失。我的做法是将服务器上的默认数据包大小的for循环替换为while循环(bis.available!=0),并在客户端上使用相同的循环,除了while(true)。如果文件大小为5mb,则只有4.95mb被复制。 - anonymous123
@EJP while循环内的读取操作并不一定会复制客户端传输的所有字节。每当打印出不同的值时,我都会打印计数变量n。 - anonymous123
@anonymous123:当然可以。它会精确地复制每个字节,直到遇到结束符(EOS)。你的观察并不能证明什么。每次读取的字节数可以任意变化。具体而言,你不能假设缓冲区已填满。传输的总字节数是多少?在这些循环中使用available()是不正确的。如果你对我的答案有评论,请在正确的位置上进行评论。 - user207421

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