报错invalid type code: AC问题场景如下:
设有两个登录用户A和B
1.A发消息给自己
2.A发消息给B,第一次没报错。A再次发消息给B,报错invalid type code: AC。
3.A发消息给B,第一次没报错。B回消息给A,报错invalid type code: AC。
服务端socketThread错误代码如下,重点看
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
的位置。
public class ServerLinksClientThread extends Thread{
private Socket socket;
private String userId;
public ServerLinksClientThread(Socket socket,String userId){
this.socket = socket;
this.userId = userId;
}
public Socket getSocket() {
return socket;
}
@Override
public String toString() {
return "ServerLinksClientThread{" +
"socket=" + socket +
", userId='" + userId + '\'' +
'}';
}
@Override
public void run() {
System.out.println("服务器正在准备与" + userId + "客户端通信..");
// ObjectInputStream ois = null;
// try {
// ois = new ObjectInputStream(socket.getInputStream());
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
boolean loop = true;
while(loop){
try {
// ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// System.out.println(socket);
/**
* oos不能定义在这,后面私聊还会用到oos
* 在同一个作用域上,同一个socket就不能new ObjectOutputStream(socket.getOutputStream())赋给不同的对象引用。
* 否则会有invalid type code: AC异常
* */
Message message = (Message)ois.readObject();
// ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
switch(message.getMessageType()){
case MessageType.GET_ONLINE_FRIEND:
// ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
String content = ManageServerLinksClientThread.getOnlineFriends();
Message message1 = new Message();
message1.setContent(content);
message1.setMessageType(MessageType.RETURN_ONLINE_FRIEND);
message1.setReceiver(message.getSendId());
oos.writeObject(message1);
break;
case MessageType.COMMON_MESSAGE:
System.out.println("收件人是" + message.getReceiver());
ServerLinksClientThread serverLinksClientThread =
ManageServerLinksClientThread.getServerLinksClientThread(message.getReceiver());
// System.out.println(serverLinksClientThread);
// System.out.println(serverLinksClientThread.getSocket());
ObjectOutputStream oos2 = new ObjectOutputStream(
serverLinksClientThread.getSocket().getOutputStream());
// ObjectOutputStream oos2 = new ObjectOutputStream(
// ManageServerLinksClientThread.getServerLinksClientThread(
// message.getReceiver()).getSocket().getOutputStream());
oos2.writeObject(message);
// System.out.println("消息已发给对象");
break;
case MessageType.TO_ALL_MESSAGE:
break;
case MessageType.CLIENT_EXIT:
System.out.println("服务器与" + userId + "连接已断开……");
socket.close();
loop = false;
ManageServerLinksClientThread.removeServerLinksClientThread(userId);
break;
default:
System.out.println("目前暂不处理此类");
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
首先要知道invalid type code:AC是怎么出现的?
在网上查到大量的案例,总结一下就是:
每次创建一个ObjectOutputStream对象时,调用构造器的时候,会固定写入头编码AC。
当同一个socket的OutputStream被再次包装成ObjectOutputStream时(即再创建一个实例),就会在socket的数据通道多写入一个AC,但是另外一边的ObjectInputStream是会把第二个AC认为是你传入的对象的内容,从而有这个报错。
所以第一个问题场景会报错的原因已经知道了。在switch结构外面创建了一个oos,在
case MessageType.COMMON_MESSAGE:
分支里又创建了一个oos2,这两个包装的都是A对象的SocketOutputStream。
最让我百思不解的是为什么会出现如下两种情况:
2.A发消息给B,第一次没报错。A再次发消息给B,报错invalid type code: AC。
3.A发消息给B,第一次没报错。B回消息给A,报错invalid type code: AC。
自我直觉认为就算要报错应该第一遍就要报错了,由于对多线程还不够敏感,没考虑到多线程的运行,刚开始的问题排查方向一直是判断switch外面和里面的socket输出流到底是不是同一个,很遗憾,就是不同的socket。
后面才想到多线程的问题,在A进入switch的私聊信息分支的时候,B的线程一直是在running的,只不过还没人给B发消息,那么问题来了,如果服务器端B的线程因为没有收到信息而一直阻塞在
Message message = (Message)ois.readObject();
的话,因为上面new了一个包装了B的socketOutputStream的ObjectOutputStream对象,加上A线程私聊分支语句中又new了一个包装了B的socketOutputStream的ObjectOutputStream对象,这么看第一遍就应该有invalid type code:AC才对。
但我经过测试后发现,B线程其实不是阻塞在readObject那句上,而是阻塞在创建ois对象那里,这样一来创建oos的那条语句要等ois创建完成才能执行,socket的数据保存形式就会像AC 类对象数据 AC,而不是 AC AC 类对象数据。
下面上测试代码
public class TestSever2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket accept = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
A a = (A)ois.readObject();
System.out.println(a);
ObjectOutputStream oos = new ObjectOutputStream(accept.getOutputStream());
oos.writeObject(a);
System.out.println(ois.readObject());
// System.out.println(a);
// System.out.println(ois.readObject());
}
}
public class JDSR {
TestClient2 testClient2;
public JDSR(TestClient2 testClient2){
this.testClient2 = testClient2;
}
// public static void main(String[] args) throws IOException {
// ObjectOutputStream oos = new ObjectOutputStream(new TestClient2().socket.getOutputStream());
// oos.writeObject(new A("真相大白"));
//
// }
public void test() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(testClient2.socket.getOutputStream());
System.out.println("真相大白还没发送");
oos.writeObject(new A("真相大白"));
System.out.println("真相大白已发送。。");
// testClient2.socket.shutdownOutput();
}
}
public class TestClient2 extends Thread{
Socket socket;
public TestClient2() throws IOException {
socket = new Socket(InetAddress.getLocalHost(), 9999);
}
@Override
public void run() {
// while(true){
try {
System.out.println("堵在开始。。。");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
System.out.println("开始执行oos");
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
System.out.println("结束执行oos");
// ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ois.readObject();
// ObjectOutputStream oos2 = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(new A("又一次"));
socket.shutdownOutput();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
// }
}
public static void main(String[] args) throws IOException {
TestClient2 testClient2 = new TestClient2();
testClient2.start();
JDSR jdsr = new JDSR(testClient2);
jdsr.test();
// testClient2.socket.shutdownOutput();
// jdsr.test();
// Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
// OutputStream outputStream = socket.getOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(outputStream);
// oos.writeObject(new A("郭"));
// ObjectOutputStream oos2 = new ObjectOutputStream(outputStream);
// oos2.writeObject(new A("学"));
// outputStream.write("hi".getBytes());
// Socket socket2 = new Socket(InetAddress.getLocalHost(), 9999);
// OutputStream outputStream2 = socket2.getOutputStream();
// ObjectOutputStream oos2 = new ObjectOutputStream(outputStream2);
// oos2.writeObject(new A("学"));
// outputStream2.write("hello".getBytes());
// socket.shutdownOutput();
// socket2.shutdownOutput();
// outputStream.close();
// InputStream inputStream = socket.getInputStream();
// outputStream2.close();
// socket.close();
}
}
public class A implements Serializable {
String name;
public A(String name){
this.name = name;
}
@Override
public String toString() {
return "A{" +
"name='" + name + '\'' +
'}';
}
}
成功复现了第一次发送A对象不会报错,但是再次发送A对象就会报错的现象。
所以正确的流程如下:
1.TestClient2线程启动以后,一直没有收到Object,阻塞在oos创建对象那里(即此条语句未执行)。
2.JDSR(借刀杀人的首拼2333)执行test方法,创建了一个TestClient2的socketOutputStream的ObjectOutputStream对象,给TestServer2传了一个A对象,服务器再回传给客户端。
3.此时客户端开始创建ois对象并读取内容,这个时候才又创建了一个TestClient2的socketOutputStream的ObjectOutputStream对象,并且输出对象new A(“又一次”)时在服务器端读取时,成功报错。
如果把ois的创建放在oos后,那么客户端的socket两个ObjectOutputStream都被创建出来了,结果就是第一次服务器端就读不出来了。
同理,回到通讯系统服务端代码那里,如果oos的创建是在
ois上面,那么readObject()就起不到一个阻塞oos创建的效果,其结果就是,A发送消息给B,第一次就会报错,因为此时同一个socket的两个ObjectOutputStream已创建。
跟据上面的两个机制,来解释一下2、3现象的原因。
A第一次给B发信息,服务端A的socket线程先读消息后创建socketOutputStream对象,之后调用服务端B的socketOutputStream输出信息,如果B端回复的话,私聊分支语句会再创建一个A的socketOutputStream对象,从而在A接收的时候报错。