Use WebSocket to realize the instant messaging chat function on Android

        This article mainly introduces the process of using WebSocket to realize the instant messaging chat function on the Android side. Finally, we use WebSocket to realize the instant messaging chat function between the two clients and the chat room function in the live broadcast. Of course, the entire WebSocket is still relatively complicated. , Especially the stability of long links needs to be strengthened (sigh that the long links of WeChat are really stable), so I hope everyone will discuss it together.

        I won’t go into details about the difference between Socket and WebSocket as well as the detailed introduction here. There are quite a lot of introductions on the Internet.

        1. Use Java-WebSocket open source framework        

        This framework is also the one I selected after comparing it on Github. It is more convenient to use. The number of Stars is considerable and it is still being updated and maintained. First of all, use the Java-WebSocket framework locally to implement the WebSocket client, address: Java-WebSocket address , add a dependency in Android Studio according to the introduction of the project homepage:  

    compile 'org.java-websocket:Java-WebSocket:1.3.8'

       Java-WebSocket is a WebSocket client and server implementation written in pure Java. On the client side, we need to write a class that inherits the client WebSocketClient in Java-WebSocket to implement four abstract methods and a construction method, as follows:

public class MyWebSocketClient extends WebSocketClient {

    public MyWebSocketClient(URI serverUri) {
        super(serverUri);
    }

    //长链接开启
    @Override
    public void onOpen(ServerHandshake handshakedata) {
    }

    //消息通道收到消息
    @Override
    public void onMessage(String message) {
    }

    //长链接关闭
    @Override
    public void onClose(int code, String reason, boolean remote) {
    }

    //链接发生错误
    @Override
    public void onError(Exception ex) {
    }
}

        A serverUri needs to be passed in the construction method. It needs to be explained that the WebSocket link is of the ws protocol, so it should be like this: 

ws:// [ip地址] : [端口号] 

        Click super to see the source code and you can see the following structure. It can be seen that the WebSocket protocol version used by Java-WebSocket is RFC 6455 (the author also explained it on the project homepage). Of course, other structure modification protocol versions are also provided.

//源码中的构造方法
public WebSocketClient( URI serverUri ) {
	this( serverUri, new Draft_6455());
}

        Since the WebSocketClient object cannot be reused, I write MyWebSocketClient as a singleton mode:

    private Context mContext;
    //选择懒汉模式
    private static MyWebSocketClient mInstance;

    //1. 私有构造方法
    private MyWebSocketClient(Context context) {
        //开启WebSocket客户端
        super(URI.create("webSocket链接"));
        this.mContext = context;
    }

    //2.公开方法,返回单例对象
    public static MyWebSocketClient getInstance(Context context) {
        //懒汉: 考虑线程安全问题, 两种方式: 1. 给方法加同步锁 synchronized, 效率低; 2. 给创建对象的代码块加同步锁
        if (mInstance == null) {
            synchronized (MyWebSocketClient.class) {
                if (mInstance == null) {
                    mInstance = new MyWebSocketClient(context);
                }
            }
        }
        return mInstance;
    }

        In this way, we can initialize MyWebSocketClient from the outside and open the link.

        Two, open the WebSocket link

        Pay special attention when opening the WebSocket link! ! ! WebSocket has five states, namely NOT_YET_CONNECTED (not connected), CONNECTING (connecting), OPEN (open state), CLOSING (closed), CLOSED (closed). Since the WebSocketClient object cannot be reused, when the WebSocket is in the four states of CONNECTING, OPEN, CLOSING, and CLOSED, it indicates that it has been initialized, so an exception will be reported when the link is reinitialized at this time: WebSocketClient objects are not reuseable; (I didn’t figure it out at the beginning. I used the boolean values ​​returned by the four methods of isConnecting(), isOpen(), isClosing(), and isClosed() to judge the state. I couldn’t judge the state of NOT_YET_CONNECTED, and then various confusions)

//源码中五种状态的枚举
enum READYSTATE {
	NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED
}
//源码中初始化链接的方法,如果状态不对会报异常
public void connect() {
	if( writeThread != null )
		throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
	writeThread = new Thread(this);
	writeThread.setName( "WebSocketConnectReadThread-" + writeThread.getId() );
	writeThread.start();
}

        You can also see above that when you execute connect(), the bottom layer will create a thread and name it, so we don't need to create the thread ourselves.

        Well, now you only need to judge the status of MyWebSocketClient at the appropriate place and use the connect() method to initialize and open the link:

//初始化开启WebSocket链接
WebSocket.READYSTATE readyState = MyWebSocketClient.getInstance(this).getReadyState();
Log.i("WebSocket", "getReadyState() = " + readyState);

//当WebSocket的状态是NOT_YET_CONNECTED时使用connect()方法进行初始化开启链接:
if (readyState.equals(WebSocket.READYSTATE.NOT_YET_CONNECTED)) {
    Log.i("WebSocket", "---初始化WebSocket客户端---");
    MyWebSocketClient.getInstance(this).connect();
}

        When the link is opened, the onOpen (ServerHandshake handshakedata) and onMessage (String message) methods in the WebSocketClient will be called back. In one-to-one chat, the server also needs to generate a unique client device ID for each device, which can be returned to the client through onMessage (String message), and then the client needs to combine the client device ID and the user UserID Binding.

        All subsequent messages pushed to the client through the message channel will be sent to the client through the onMessage(String message) method. Therefore, the operation after receiving the message needs to be completed in the onMessage(String message) method. For example, when receiving a chat message, first save the message to the local message database, and then use EventBus to send the message to the chat page for display.

        Three, WebSocket reconnect

        WebSocket reconnection is a very important part of establishing a stable long WebSocket link, because the long link channel of WebSocket may be disconnected at any time due to factors such as mobile phone network changes and WiFi switching, which affects the stability of chat and other functions. I have used two methods to reconnect WebSocket: one is to initiate a reconnection when the long WebSocket link is disconnected without using the heartbeat packet. If the reconnection is not successful, the reconnection is initiated again; the other is to reconnect at regular intervals. Time (for example, 30 seconds), the client sends a heartbeat packet to the server to determine whether the long link channel is connected, and if the heartbeat packet is not sent successfully, it initiates a reconnection. The advantage of the first method is that it does not need to send heartbeat packets and does not consume client resources, but the stability is not as good as the second method of sending heartbeat packets; the second method of sending heartbeat packets has better stability than the first method, but it has been Sending a heartbeat packet (including when the APP is running in the background) will affect the consumption of client resources and affect some performance.

        1. Initiate a reconnection when the long WebSocket link is disconnected

        First of all, when should we initiate a reconnection? When various factors cause the long WebSocket link to be disconnected, the onClose() method in the WebSocketClient will be called back, so we can initiate a reconnection here; and when the client device ID is bound to the user UserID, if it fails, Need to initiate a reconnection and then bind again. When multiple reconnections are needed, I designed a simple time interval mechanism: the first time it is disconnected, the reconnection is initiated with a delay of 500 milliseconds, and if the reconnection fails, the reconnection is initiated with a delay of 1000 milliseconds for the second time. If it fails, the third time is delayed by 2000 milliseconds to initiate a reconnection, and so on. Each time interval is doubled until the reconnection is 10 times. If the reconnection is not successful, the reconnection is declared as a failure (the maximum time interval can reach 17 minutes, and the user is weak. You can also reconnect when the network environment returns to the normal network environment). After a certain reconnection is successful, the reconnection time interval is reset to 500 milliseconds, and the number of reconnection times is reset to 0, and the same time interval mechanism is executed next time to reconnect.

//长链接关闭
//在各种因素导致WebSocket的长链接断开时会回调onClose()方法,所以可以在此发起重新连接;(客户端设备ID和用户UserID绑定失败时也需要发起重新连接然后再次进行绑定)
@Override
public void onClose(int code, String reason, boolean remote) {
    Log.i("WebSocket", "...MyyWebSocketClient...onClose...");
    mHandler.removeMessages(MSG_EMPTY);
    mHandler.sendEmptyMessageDelayed(MSG_EMPTY, GlobalConstants.RECONNECT_DELAYED_TIME);
    //将时间间隔翻倍
    GlobalConstants.RECONNECT_DELAYED_TIME = GlobalConstants.RECONNECT_DELAYED_TIME * 2;
}
//长链接开启
//重连成功后则将重连时间间隔重置为500毫秒,将重连次数重置为0,以待下次执行同样的时间间隔机制进行重新连接
@Override
public void onOpen(ServerHandshake handshakedata) {
	Log.i("WebSocket", "...MyyWebSocketClient...onOpen...");
	GlobalConstants.webSocketConnectNumber = 0;
	GlobalConstants.RECONNECT_DELAYED_TIME = 500;
	mHandler.removeMessages(MSG_EMPTY);
}

        When doing instant messaging chat and live chat room functions in our own projects, the version of the Java-WebSocket framework is still 1.3.5; when I write this article, the version of the Java-WebSocket framework has been updated to 1.3.8, at 1.3. Two new reconnect methods reconnect() and reconnectBlocking() have been added in version 8. In version 1.3.5, there is no direct method to reconnect. The method I took is: first completely close the original link, then re-create a MyWebSocketClient object (because the WebSocketClient object cannot be reused), and then execute connect() Method to reconnect. Of course, in 1.3.8 and later versions, it is recommended to use the reconnect() or reconnectBlocking() method to reconnect.

//在Handler消息队列中执行重新连接,也便于重连时间间隔控制    
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {

        Log.i("WebSocket", "webSocketConnectNumber = " + GlobalConstants.webSocketConnectNumber);
        //未超过设置次数时执行重连操作    
        if (GlobalConstants.webSocketConnectNumber <= 10) {
            if (mInstance != null) {
                WebSocket.READYSTATE readyState = mInstance.getReadyState();
                if (readyState.equals(WebSocket.READYSTATE.NOT_YET_CONNECTED)) {
                    mInstance.connect();

                } else if (readyState.equals(WebSocket.READYSTATE.CLOSED) || readyState.equals(WebSocket.READYSTATE.CLOSING)) {
                    //先将原来的链接关闭,再重新创建一个MyWebSocketClient对象,然后执行connect()方法重新连接。  
                    //(此为1.3.5版本重连的方法,建议使用1.3.8版本提供的重连方法)    
                    mInstance.closeBlocking();
                    mInstance = new MyWebSocketClient(mContext);
                    mInstance.connect();
                    //将连接次数自增    
                    GlobalConstants.webSocketConnectNumber++;
                }
            }
        } else {
            //超过设置次数则清空重连消息队列,并将mInstance置为null(待外部初始化连接)    
            mHandler.removeMessages(MSG_EMPTY);
            mInstance = null;
        }
    }
}; 
//在Handler消息队列中执行重新连接,也便于重连时间间隔控制    
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {

        Log.i("WebSocket", "webSocketConnectNumber = " + GlobalConstants.webSocketConnectNumber);
        //未超过设置次数时执行重连操作    
        if (GlobalConstants.webSocketConnectNumber <= 10) {
            if (mInstance != null) {
                WebSocket.READYSTATE readyState = mInstance.getReadyState();
                if (readyState.equals(WebSocket.READYSTATE.NOT_YET_CONNECTED)) {
                    mInstance.connect();

                } else if (readyState.equals(WebSocket.READYSTATE.CLOSED) || readyState.equals(WebSocket.READYSTATE.CLOSING)) {
                    //使用1.3.8版本提供的reconnect()方法重连  
                    mInstance.reconnect();
                    //将连接次数自增    
                    GlobalConstants.webSocketConnectNumber++;
                }
            }
        } else {
            //超过设置次数则清空重连消息队列,并将mInstance置为null(待外部初始化连接)    
            mHandler.removeMessages(MSG_EMPTY);
            mInstance = null;
        }
    }
}; 

        2. The client sends a heartbeat packet to the server 

        There are two methods of sending messages encapsulated in the Java-WebSocket framework: send(String text) and sendPing(). When we need to send a message, use the send(String text) method to send the carried message to the server, and sendPing The () method does not carry any messages, but it can determine whether the long link channel is connected to the server, so we can use the sendPing() method to send a heartbeat packet to the server, and initiate a reconnection if it is not successful.

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG_HEART) {
                try {
                    mInstance.sendPing();
                } catch (Exception e) {
                    e.printStackTrace();
                    mInstance.reconnect();
                } finally {
                    mHandler.removeMessages(MSG_HEART);
                    mHandler.sendEmptyMessageDelayed(MSG_HEART, 30 * 1000);
                }
            }
        }
    };

       The heartbeat packet should be sent when the long link is opened, and a heartbeat packet should be sent immediately in the onClose() method when the long link is closed.

    //长链接开启
    @Override
    public void onOpen(ServerHandshake handshakedata) {
        mHandler.removeMessages(MSG_HEART);
        mHandler.sendEmptyMessageDelayed(MSG_HEART, 30 * 1000);
    }

        So far, the functions I have implemented are basically completed. Of course, the entire WebSocket is still relatively complicated, and the stability of my own implementation needs to be strengthened. If there are any shortcomings, please point out.   

Guess you like

Origin blog.csdn.net/beita08/article/details/80162070