在Java中使用Socket与ServerSocket建立客户机和服务器时,若采用ObjectInputStream与ObjectOutputStream建立通信,则需要注意两个流的顺序。否则会发生两方互相等待导致死锁。
下面通过一个例子来证明:
服务器:
package com.gary;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server implements Runnable {
private ServerSocket serverSocket;
private int port;
private boolean goon;
private ObjectInputStream ois;
private ObjectOutputStream oos;
public Server() {
goon = false;
}
public Server(int port) {
this();
this.port = port;
}
public void setPort(int port) {
this.port = port;
}
public void stopServer() {
this.goon = false;
try {
this.serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void startServer() throws Exception {
if (this.port == 0) {
// TODO 抛异常
return;
}
this.serverSocket = new ServerSocket(port);
this.goon = true;
new Thread(this, "SERVER").start();
}
@Override
public void run() {
while(goon) {
try {
Socket client = serverSocket.accept();
ois = new ObjectInputStream(client.getInputStream());
oos = new ObjectOutputStream(client.getOutputStream());
oos.writeObject(new String("去死"));
String str = (String) ois.readObject();
System.out.println("服务器收到客户端[" + client.getInetAddress().getHostAddress() + "]消息:" + str);
} catch (IOException e) {
goon = false;
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
stopServer();
}
}
客户机:
package com.gary;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class Client {
private String serverIp;
private int serverPort;
public Client(String serverIp, int serverPort) {
this.serverIp = serverIp;
this.serverPort = serverPort;
}
private void closeSocket(ObjectInputStream ois, ObjectOutputStream oos, Socket socket) {
try {
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ois = null;
}
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
oos = null;
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
socket = null;
}
}
public void start() throws Exception {
Socket socket = new Socket(serverIp, serverPort);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(new String("好的"));
Object result = ois.readObject();
System.out.println("client收到:" + result);
closeSocket(ois, oos, socket);
}
}
测试:
package com.gary;
public class TestServer {
public static void main(String[] args) {
Server rpcServer = new Server();
rpcServer.setPort(54189);
try {
rpcServer.startServer();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.gary;
public class TestClient {
public static void main(String[] args) {
Client client = new Client("172.21.126.74", 54189);
try {
client.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果
分析原因:
仔细看ObjectInputStream的API
第一段的意思是,创建一个从指定的InputStream读取的ObjectInputStream,序列化的流的头是从这个Strem中读取并验证的。此构造方法会一直阻塞直到相应的ObjectOutputStream已经写入并刷新头。
所以上述代码执行后会都阻塞,如果将创建ObjectInputStream的顺序修改成其他的顺序,便可正常通信。