今天给大家带来一篇老文章,介绍WebSocket,大家可以了解了解。
前言
最近老板又来新需求了,要做一个物联网相关的app,其中有个需求是客户端需要收发服务器不定期发出的消息。
内心OS:
开淦!
WebSocket介绍
先简单介绍下WebSocket。我们都知道Http是处于应用层的一个通信协议,但是只支持单向主动通信,做不到服务器主动向客户端推送消息。而且Http是无状态的,即每次通信都没有关联性,导致跟服务器关系不紧密。
为了解决和服务器长时间通信的痛点呢,HTML5规范引出了WebSocket协议(知道这名字咋来的吧,人家HTML5规范引出的,随爸姓),是一种建立在TCP协议基础上的全双工通信的协议。他跟Http同属于应用层协议,下层还是需要通过TCP建立连接。
但是,WebSocket在TCP连接建立后,还要通过Http进行一次握手,也就是通过Http发送一条GET请求消息给服务器,告诉服务器我要建立WebSocket连接了,你准备好哦,具体做法就是在头部信息中添加相关参数。然后服务器响应我知道了,并且将连接协议改成WebSocket,开始建立长连接。
这里贴上请求头和响应头信息,从网上找了一张图:
3851594110877_.pic.jpg
简单说明下参数:
OkHttp实现
添加OkHttp依赖
- implementation("com.squareup.okhttp3:okhttp:4.7.2")
实现代码
首先是初始化OkHttpClient和WebSocket实例:
- /**
- * 初始化WebSocket
- */
- public void init() {
- mWbSocketUrl = "ws://echo.websocket.org";
- mClient = new OkHttpClient.Builder()
- .pingInterval(10, TimeUnit.SECONDS)
- .build();
- Request request = new Request.Builder()
- .url(mWbSocketUrl)
- .build();
- mWebSocket = mClient.newWebSocket(request, new WsListener());
- }
这里主要是配置了OkHttp的一些参数,以及WebSocket的连接地址。其中newWebSocket方法就是进行WebSocket的初始化和连接。
这里要注意的点是pingInterval方法的配置,这个方法主要是用来设置WebSocket连接的保活。相信做过长连接的同学都知道,一个长连接一般要隔几秒发送一条消息告诉服务器我在线,而服务器也会回复一个消息表示收到了,这样就确认了连接正常,客户端和服务器端都在线。
如果服务器没有按时收到这个消息那么服务器可能就会主动关闭这个连接,节约资源。客户端没有正常收到这个返回的消息,也会做一些类似重连的操作,所以这个保活消息非常重要。
我们称这个消息叫作心跳包,一般用PING,PONG表示,像乒乓球一样,一来一回。所以这里的pingInterval就是设置心跳包发送的间隔时间,设置了这个方法之后,OkHttp就会自动帮我们发送心跳包事件,也就是ping包。当间隔时间到了,没有收到pong包的话,监听事件中的onFailure方法就会被调用,此时我们就可以进行重连。
但是由于实际业务需求不一样,以及okhttp中心跳包事件给予我们权限较少,所以我们也可以自己完成心跳包事件,即在WebSocket连接成功之后,开始定时发送ping包,在下一次发送ping包之前检查上一个pong包是否收到,如果没收到,就视为异常,开始重连。感兴趣的同学可以看看文末的相关源码。
建立连接后,我们就可以正常发送和读取消息了,也就是在上文WsListener监听事件中表现:
- //监听事件,用于收消息,监听连接的状态
- class WsListener extends WebSocketListener {
- @Override
- public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
- super.onClosed(webSocket, code, reason);
- }
- @Override
- public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
- super.onClosing(webSocket, code, reason);
- }
- @Override
- public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
- super.onFailure(webSocket, t, response);
- }
- @Override
- public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
- super.onMessage(webSocket, text);
- Log.e(TAG, "客户端收到消息:" + text);
- onWSDataChanged(DATE_NORMAL, text);
- //测试发消息
- webSocket.send("我是客户端,你好啊");
- }
- @Override
- public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
- super.onMessage(webSocket, bytes);
- }
- @Override
- public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
- super.onOpen(webSocket, response);
- Log.e(TAG,"连接成功!");
- }
- }
- //发送String消息
- public void send(final String message) {
- if (mWebSocket != null) {
- mWebSocket.send(message);
- }
- }
- /**
- * 发送byte消息
- * @param message
- */
- public void send(final ByteString message) {
- if (mWebSocket != null) {
- mWebSocket.send(message);
- }
- }
- //主动断开连接
- public void disconnect(int code, String reason) {
- if (mWebSocket != null)
- mWebSocket.close(code, reason);
- }
这里要注意,回调的方法都是在子线程回调的,如果需要更新UI,需要切换到主线程。
基本操作就这么多,还是很简单的吧,初始化Websocket——连接——连接成功——收发消息。
其中WebSocket类是一个操作接口,主要提供了以下几个方法
如果有同学想测试下WebSocket的功能但是又没有实际的服务器,怎么办呢?其实OkHttp官方有一个MockWebSocket服务,可以用来模拟服务端,下面我们一起试一下:
模拟服务器
首先集成MockWebSocket服务库:
- implementation 'com.squareup.okhttp3:mockwebserver:4.7.2'
然后就可以新建MockWebServer,并加入MockResponse作为接收消息的响应。
- MockWebServer mMockWebServer = new MockWebServer();
- MockResponse response = new MockResponse()
- .withWebSocketUpgrade(new WebSocketListener() {
- @Override
- public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
- super.onOpen(webSocket, response);
- //有客户端连接时回调
- Log.e(TAG, "服务器收到客户端连接成功回调:");
- mWebSocket = webSocket;
- mWebSocket.send("我是服务器,你好呀");
- }
- @Override
- public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
- super.onMessage(webSocket, text);
- Log.e(TAG, "服务器收到消息:" + text);
- }
- @Override
- public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
- super.onClosed(webSocket, code, reason);
- Log.e(TAG, "onClosed:");
- }
- });
- mMockWebServer.enqueue(response);
这里服务器端在收到客户端连接成功消息后,给客户端发送了一条消息。要注意的是这段代码要在子线程执行,因为主线程不能进行网络操作。
然后就可以去初始化Websocket客户端了:
- //获取连接url,初始化websocket客户端
- String websocketUrl = "ws://" + mMockWebServer.getHostName() + ":" + mMockWebServer.getPort() + "/";
- WSManager.getInstance().init(websocketUrl);
ok,运行项目
//运行结果 E/jimu: mWbSocketUrl=ws://localhost:38355/ E/jimu: 服务器收到客户端连接成功回调: E/jimu: 连接成功! E/jimu: 客户端收到消息:我是服务器,你好呀 E/jimu: 服务器收到消息:我是客户端,你好啊
参考
https://github.com/square/okhttp
本文转载自微信公众号「码上积木,可以通过以下二维码关注。转载本文请联系码上积木公众号。
采访嘉宾 | 于佳(宗心) 编辑 | Tina 闲鱼在 2017 年引入 Flutter,当时的 Flut...
技术分享 王绍翾(大沙) 阿里达摩院机器智能实验室 资深技术专家肖允锋(鹤冲) 阿...
Python 从一种小的开源语言开始,到现在,它已经成为开发者很受欢迎的编程语言之...
混合云数据分析平台公司Teradata日前宣布增强其Teradata Vantage平台,使协作和...
本文转载自微信公众号「接地气学堂」,作者接地气的陈老师。转载本文请联系接地...
一、数据结构的存储方式 数据结构的存储方式只有两种:数组(顺序存储)和链表(链...
9月28日,由中国国际大数据产业博览会组委会主办的永不落幕的数博会2020系列活动...
窗口枚举 此例程枚举了计算机上所有可见的窗口。每个可见窗口都会将其标题与窗口...
操作场景 您可以删除不需要的私有镜像。 删除私有镜像后,将无法找回,请谨慎操...
前端测试用例怎么写?为什么写测试用例?测试用例为了特定的目的证明软件存在某问...