Send file with socket BIO use Socket, ServerSocket, ObjectInputStream and ObjectOutputStream but blocked. The detail code is: Model:
@Data
@ToString
public class FileTransModel implements Serializable {
private String fileName;
private Long fileLength;
private Integer status;
}
Client:
public class FileTransClient {
private static final int BUFFER_SIZE = 1024 * 8;
private static final byte[] BUFFER = new byte[BUFFER_SIZE];
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 1888);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
File file = new File("D:\\trans\\1.mp4");
FileTransModel fileTransModel = new FileTransModel();
fileTransModel.setFileName(file.getName());
fileTransModel.setFileLength(file.length());
oos.writeObject(fileTransModel);
FileInputStream fileInputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
int len;
while ((len = bufferedInputStream.read(BUFFER)) != -1) {
System.out.println(len);
oos.write(BUFFER, 0, len);
oos.flush();
}
System.out.println("file send over");
FileTransModel model = (FileTransModel) ois.readObject();
System.out.println(model);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
Server:
public class FileTransServer {
private static final int BUFFER_SIZE = 1024 * 8;
private static final byte[] BUFFER = new byte[BUFFER_SIZE];
public static void main(String[] args) {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(1888);
System.out.println("Socket Server start on port: 1888");
while(true) {
Socket accept = serverSocket.accept();
System.out.println("new file come!");
new Thread(new Task(accept)).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static class Task implements Runnable {
Socket socket;
public Task(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
FileTransModel fileTransModel = (FileTransModel) ois.readObject();
System.out.println(fileTransModel);
File file = new File("D:\\trans_rec2\\"+fileTransModel.getFileName());
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos);
int len;
len = ois.read(BUFFER);
int size = 0;
while (true) {
size += len;
System.out.println(len + ": " + size);
if (len == -1) {
break;
}
bufferedOutputStream.write(BUFFER, 0, len);
bufferedOutputStream.flush();
len = ois.read(BUFFER);
}
System.out.println("file write over");
fileTransModel.setStatus(0);
oos.writeObject(fileTransModel);
System.out.println("file receive over");
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
and the result is : enter image description here Client had trans over, but Server is like that: enter image description here it cannot go on. how to fix it
I have tried to use DataInputStream and DataOutputStream, but the result is the same.
According to the documentation of read
method in ObjectInputStream
:
This method will block until some input is available.
So basically you are never getting len
to be equal to -1
at server side. That's because of the sequence of events:
ObjectInputStream
is in blocking I/O mode here, so is readObject
in the following snippet of your client side code:
System.out.println("file send over");
FileTransModel model = (FileTransModel) ois.readObject(); // Blocks here.
-1
(assuming no IOException
occurs) inside the loop, then it is not going to send the model back.As a result both sides are blocked waiting from each other.
To solve this you can signify the end of file transmission in a way such that the server knows when to stop reading file bytes. Since the file contents can be arbitrary and its size is not pre-determined at server side, then you can send the file size before you begin sending its contents to the server. But the model already has this information, so your reading loop in the server side can be modified from:
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos);
int len;
len = ois.read(BUFFER);
int size = 0;
while (true) {
size += len;
System.out.println(len + ": " + size);
if (len == -1) {
break;
}
bufferedOutputStream.write(BUFFER, 0, len);
bufferedOutputStream.flush();
len = ois.read(BUFFER);
}
to:
try (final FileOutputStream fos = new FileOutputStream(file);
final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos)) {
final long expected = fileTransModel.getFileLength() == null? 0: fileTransModel.getFileLength();
long size = 0;
while (size < expected) {
final int current = ois.read(BUFFER);
bufferedOutputStream.write(BUFFER, 0, current);
//bufferedOutputStream.flush(); //Flushing after each write would defeat the purpose of buffering, so better don't do it.
size += current;
System.out.println("So far: " + size);
}
} //Always close the file.
Closing the client side ObjectOutputStream
(the oos
reference) right after writing the file contents, in order for example to get a -1
at the server side, is not an option here since closing oos
would close the underlying OutputStream
of the Socket
, which in turn according to the documentation would close the Socket
itself. So then if you were going to send the model back (ie from server to client), then a SocketException
would be raised (the Socket
would be no more usable).