韩顺平java教程多用户即时通讯系统私聊模块可能出现的invalid type code: AC报错详细分析

报错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 + '\'' +
                '}';
    }
}

韩顺平java教程多用户即时通讯系统私聊模块可能出现的invalid type code: AC报错详细分析_第1张图片
韩顺平java教程多用户即时通讯系统私聊模块可能出现的invalid type code: AC报错详细分析_第2张图片
成功复现了第一次发送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都被创建出来了,结果就是第一次服务器端就读不出来了。
韩顺平java教程多用户即时通讯系统私聊模块可能出现的invalid type code: AC报错详细分析_第3张图片
同理,回到通讯系统服务端代码那里,如果oos的创建是在
ois上面,那么readObject()就起不到一个阻塞oos创建的效果,其结果就是,A发送消息给B,第一次就会报错,因为此时同一个socket的两个ObjectOutputStream已创建。

跟据上面的两个机制,来解释一下2、3现象的原因。
A第一次给B发信息,服务端A的socket线程先读消息后创建socketOutputStream对象,之后调用服务端B的socketOutputStream输出信息,如果B端回复的话,私聊分支语句会再创建一个A的socketOutputStream对象,从而在A接收的时候报错。

你可能感兴趣的:(GXM的Java学习日志,1024程序员节,java)