Java TCP文件传输仅在第一次尝试时完成
尽管我花了几个小时研究这个问题,但进展甚微。根据我的教授所说,代码应该是按书面形式运行的 我有一个保持打开的服务器和一个请求文件的客户端。一旦客户端收到文件,客户端将关闭 当我打开服务器时,我能够传输一个完整的.jpg图像文件。然后,客户端关闭,而服务器保持打开状态。我启动另一个客户端并尝试传输相同的映像,但只有一部分字节被传输/写入磁盘。文件传输仅在服务器传输的第一个文件中完全成功 此外,奇怪的是,一个简单的.txt文本文件从未成功传输。我认为原因在于服务器端,因为它与每次重新启动的客户端相反,始终保持打开状态 服务器代码:Java TCP文件传输仅在第一次尝试时完成,java,file,sockets,tcp,transfer,Java,File,Sockets,Tcp,Transfer,尽管我花了几个小时研究这个问题,但进展甚微。根据我的教授所说,代码应该是按书面形式运行的 我有一个保持打开的服务器和一个请求文件的客户端。一旦客户端收到文件,客户端将关闭 当我打开服务器时,我能够传输一个完整的.jpg图像文件。然后,客户端关闭,而服务器保持打开状态。我启动另一个客户端并尝试传输相同的映像,但只有一部分字节被传输/写入磁盘。文件传输仅在服务器传输的第一个文件中完全成功 此外,奇怪的是,一个简单的.txt文本文件从未成功传输。我认为原因在于服务器端,因为它与每次重新启动的客户端相反
import java.io.*;
import java.net.*;
import java.util.Arrays;
class ft_server {
public static void main(String args[]) throws Exception {
/*
* Asks user for port number and listens on that port
*/
BufferedReader portFromUser = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter the port you'd like to use: ");
int portNumber = Integer.valueOf(portFromUser.readLine());
if (portNumber < 1 || portNumber > 65535) {
System.out.println("Please choose a port number between 1 and 65535.");
return;
}
portFromUser.close();
ServerSocket listenSocket = new ServerSocket(portNumber);
/*
* Finished with user input
*/
/*
* Continuously listens for clients:
*/
while (true) {
Socket clientSocket = listenSocket.accept();
BufferedReader inFromClient = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
DataOutputStream outToClient = new DataOutputStream(clientSocket.getOutputStream());
String clientIP = clientSocket.getRemoteSocketAddress().toString();
System.out.println("The client " + clientIP + " connected!");
String clientMessage = inFromClient.readLine();
System.out.println("The client requested file: " + clientMessage);
// Get file. If doesn't exist, let's client know.
// Otherwise informs client of file size.
File myFile = new File(clientMessage);
if (!myFile.exists()) {
outToClient.writeBytes("File does not exist!\n");
return;
} else {
outToClient.writeBytes(String.valueOf((int)myFile.length()) + "\n");
}
// Create array for storage of file bytes:
byte[] byteArray = new byte[(int)myFile.length()];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));
// Read file into array:
bis.read(byteArray, 0, byteArray.length);
// Send the file:
outToClient.write(byteArray, 0, byteArray.length);
outToClient.close();
clientSocket.close();
}
}
}
缓冲读卡器是否干扰输出流?有没有更好的传输文件的方法?值得在服务器代码中检查file
read()
调用返回的值,因此:
int bytesRead = bis.read(byteArray, 0, byteArray.length);
System.out.println("File bytes read: " + bytesRead + " from file size: " + myFile.length());
read()
方法没有义务填充byteArray-只返回一些内容并告诉您它读取了多少字节。从中可以看出:
从该输入流读取多达len字节的数据到
字节。如果len不为零,则该方法将阻塞,直到输入为零
可用的;否则,不读取字节,返回0
你需要保持循环阅读。我会这样做(事实上,和你的客户一样!):
或者类似的东西。我也关闭了该文件:它将在GC/finalize时关闭,但这可能需要一段时间,同时您将保持该文件打开
编辑
在本例中,图像读取的具体问题在客户端代码中。读取代码顶部附近的文件大小:
// Creates InputStream from server to get file size and other messages:
BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
然后再次访问客户端:
InputStream is = clientSocket.getInputStream(); // calling clientSocket.getInputStream() twice???
正如你的评论所暗示的,这很糟糕!感谢@EJP突出显示这一点
这会导致缓冲区过度摄取的问题:BufferedReader在其肚子中消耗的字节数多于从中提取的字节数,因此,当您第二次访问clientSocket inputstream时,读取指针已移动。您再也不会看到BufferedReader消耗了什么
一般来说,一旦将缓冲代码插入到某个对象上,就必须小心地只从该缓冲区读取。在这种情况下,这很困难,因为您无法从读取器读取图像(原始二进制)数据,因为它将忙于将二进制值解释为字符,并将其读取为UTF-8或其他内容
即使没有缓冲区,在同一个流上混合读取器(面向文本)和二进制数据(数据流)也是一个小错误。HTTP和电子邮件可以做到这一点,所以你是一个很好的伙伴,但它们通过非常严格的指定而得以逃脱。问题是,无论您是在读Unix“LF”还是Windows“CR/LF”行的结尾,您都很容易被每一端的本地/默认字符编码问题所困扰
在这种情况下,请尝试根本不使用BufferedReader,并一直尝试使用DataInput/Output流。尝试使用writeUTF
和readUTF()
传输字符串数据。理想情况下,按如下方式创建它们:
DataInputStream inFromServer = new DataInputStream (new BufferedInputStream(clientSocket.getInputStream()));
因此,您仍然可以获得缓冲的好处
编辑2
看到新的客户端代码:
byteSize = (int) Integer.valueOf(response);
byte[] byteArray = new byte[byteSize];
FileOutputStream fos = new FileOutputStream(message);
int readBytes = inFromServer.read(byteArray);
// Continuously writes the file to the disk until complete:
int total = 0;
for (int i=0; i<byteArray.length; i++) {
fos.write(byteArray[i]);
total++;
}
fos.close();
一个变体是这样的-相同的东西,但一次字节。可能更清楚一点。这会很慢-所有这些读写操作都会影响操作系统,但是如果在套接字/文件流周围放置一个BufferedInputStream/BufferedOutputStream,就会解决这个问题。我添加了以下内容:
DataInputStream inFromServer =
new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
...
byteSize = (int) Integer.valueOf(response);
OutputStream fos = new BufferedOutputStream(FileOutputStream(message));
int total = 0;
int ch;
// Continuously writes the file to the disk until complete:
while (total < byteSize && (ch = inFromServer.read()) != -1) {
fos.write(ch);
total ++;
}
fos.close();
对!!20世纪90年代,那些善良的人在Javasoft中添加了一个方法,它可以满足您的需求基本上包装了上面的代码。这是最简单的解决方案,也可以说是最正确的方法:“尽可能使用现有库”。OTOH,这是最没有教育意义的,你花在阅读/写作上的时间不会从你的预期寿命中扣除
事实上,readFully
方法有严重的局限性。试着将它指向1GB文件,看看会发生什么(在您确定了顶部的阵列大小之后):您将a)耗尽内存,b)希望在接收一个巨大的blob时,您至少可以将其假脱机到磁盘。如果您尝试使用2.5G文件,您会注意到其中一些int应该变成long以处理大于等于2^31的数字
如果是我,我会做4K缓冲区。(顺便说一句,我是在一台没有安装Java编译器的笔记本电脑上写这篇文章的,所以我没有实际运行上面的内容!如果有任何困难,一定要响应。)不要在同一个套接字上混合使用缓冲流和非缓冲流以及读卡器。找到另一种发送文件名的方法,例如使用
DataOutputStream.writeUTF()
,然后使用DataInputStream.readUTF()
读取。我会尝试一下。但是,混合它们是否有内在的问题?缓冲读取器,呃,缓冲区。如果读取文件名时图像数据已经存在,它会读取一些图像数据。混合流类型有点糟糕,是的。客户端使用DataOutput.writeBytes
发送文件名,后者发送每个字符的底部字节;然后,服务器使用系统默认的字符编码,通过字节->字符转换器(InputStreamReader
)使用BufferedReader.readLine()
读取它。只要您坚持使用普通ASCII,您可能会很好,但如果您使用UTF-8作为默认编码,并且文件名中包含非ASCII字符,则所有文件都会变得非常混乱。@EJP哦,天哪,我看不出您所说的缓冲区问题!一定很晚了。不是每次在服务器循环中都会重新创建所有的BufferedReader吗?客户端每次都会重新启动….?由于某种原因,bis.read
从未返回-1,因此程序只是在那里暂停。“我只是取消了这个循环,因为它不是必需的。”尼克
byteSize = (int) Integer.valueOf(response);
byte[] byteArray = new byte[byteSize];
FileOutputStream fos = new FileOutputStream(message);
int readBytes = inFromServer.read(byteArray);
// Continuously writes the file to the disk until complete:
int total = 0;
for (int i=0; i<byteArray.length; i++) {
fos.write(byteArray[i]);
total++;
}
fos.close();
byteSize = (int) Integer.valueOf(response);
byte[] byteArray = new byte[4096];
FileOutputStream fos = new FileOutputStream(message);
int total = 0;
// Continuously writes the file to the disk until complete:
while (total < byteSize && (readBytes = inFromServer.read(byteArray)) != -1) {
fos.write(byteArray, 0, readBytes);
total += readBytes;
}
fos.close();
DataInputStream inFromServer =
new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
...
byteSize = (int) Integer.valueOf(response);
OutputStream fos = new BufferedOutputStream(FileOutputStream(message));
int total = 0;
int ch;
// Continuously writes the file to the disk until complete:
while (total < byteSize && (ch = inFromServer.read()) != -1) {
fos.write(ch);
total ++;
}
fos.close();
int readBytes = inFromServer.readFully(byteArray);