今天在写套接字相关的网络传输代码时碰到了这个异常,所以研究了一下,下面将该问题具象化为以下代码来说明该异常的起因和避免方法:
public class Server01 {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket serverSocket = new ServerSocket(8888);
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("d:\\pic.gif"));
Socket socket = serverSocket.accept();
byte[]buf=new byte[1024];
int readLen=0;
while((readLen=bufferedInputStream.read(buf))!=-1){
Transmission transmission = new Transmission();
transmission.setReadLen(readLen);
byte[]buf2=transmission.getBuf();
for (int i = 0; i <readLen ; i++) {
buf2[i]=buf[i];
}
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//每次传输过程都有一个新的ObjectInputStream类对象
objectOutputStream.writeObject(transmission);
}
Thread.sleep(1000*1200);//避免服务端套接字过快关闭,而客户端正在读取套接字中的数据,而导致的EOF异常
serverSocket.close();
socket.close();
}
}
public class Client01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());//只有这一个ObjectInputStream对象
while(true) {
Transmission transmission = (Transmission) objectInputStream.readObject();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("D:\\pic3.gif",true));
bufferedOutputStream.write(transmission.getBuf(),0,transmission.getReadLen());
bufferedOutputStream.close();
}
}
}
public class Transmission implements Serializable {
private byte[]buf=new byte[1024];
private int readLen;
public int getReadLen() {
return readLen;
}
public void setReadLen(int readLen) {
this.readLen = readLen;
}
public byte[] getBuf() {
return buf;
}
}
上面的代码在服务端运行起来后,再启动客户端,然后客户端进程就会抛出异常,抛出异常的位置如下:
Transmission transmission = (Transmission) objectInputStream.readObject();
其实这是因为
ObjectOutputStream实例在第一次进行序列化时会额外添加一个head,后面继续使用该对象实例进行序列化传输的时候省去了这一个步骤,而ObjectInputStream 实例在第一次反序列化时会去掉head,然后在后续使用该对象实例进行反序列化时就直接读取,不再有去掉head的操作。上述代码的服务端每次循环都会重新new一个ObjectOutputStream对象来进行序列化,导致每次循环进行序列化后都会额外添加一个head,而客户端每次反序列化都用的是同一个ObjectInputStream 实例对象,导致只有在第一次循环进行反序列化对象时会去掉一个head,而后续的读取不会进行该操作。这样就导致ObjectOutputStream进行序列化加入的head没有被ObjectOutputStream反序列化完全去掉,就会出问题。
所以这里有两个解决方案
第一,让客户端和服务端保持匹配,客户端中的ObjectInputStream对象实例在每次循环时重新new一个
public class Client01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
while(true) {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Transmission transmission = (Transmission) objectInputStream.readObject();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("D:\\pic3.gif",true));
bufferedOutputStream.write(transmission.getBuf(),0,transmission.getReadLen());
bufferedOutputStream.close();
}
}
}
第二,让服务端与客户端保持一致,服务端也是自始至终都只使用同一个ObjectOutputStream实例对象
public class Server01 {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket serverSocket = new ServerSocket(8888);
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("d:\\pic.gif"));
Socket socket = serverSocket.accept();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
byte[]buf=new byte[1024];
int readLen=0;
while((readLen=bufferedInputStream.read(buf))!=-1){
Transmission transmission = new Transmission();
transmission.setReadLen(readLen);
byte[]buf2=transmission.getBuf();
for (int i = 0; i <readLen ; i++) {
buf2[i]=buf[i];
}
objectOutputStream.writeObject(transmission);
}
Thread.sleep(1000*1200);//避免服务端套接字过快关闭,而客户端正在读取套接字中的数据,而导致的EOF异常
serverSocket.close();
socket.close();
}
}