Android 即时通信(二)WebSocket传输消息

一、利用WebSocket传输消息
文本与图片的即时通信都可以由SocketIO实现,看似它要一统即时通信了,可是深究起来会发现SocketIO存在很多局限,包括但不限于下列几点:
(1)SocketIO不能直接传输字节数据,只能重新编码成字符串后(比如BASE64编码)再传输,造成了额外的系统开销。
(2)SocketIO不能保证前后发送的数据被收到时仍然是同样顺序,如果业务要求实现分段数据的有序性,开发者就得自己采取某种机制确保这种有序性。
(3)SocketIO服务器只有一个main程序,不可避免地会产生性能瓶颈。倘若有许多通信请求奔涌过来,一个main程序很难应对。

为了解决上述几点问题,业界提出了一种互联网时代的Socket协议,名叫WebSocket。它支持在TCP连接上进行全双工通信,这个协议在2011年被定为互联网的标准之一,并纳入HTML5的规范体系。

相对于传统的HTTP与Socket协议来说,WebSocket具备以下几点优势:
(1)实时性更强,无须轮询即可实时获得对方设备的消息推送。
(2)利用率更高,连接创建之后,基于相同的控制协议,每次交互的数据包头部较小,节省了数据处理的开销。
(3)功能更强大,WebSocket定义了二进制帧,使得传输二进制的字节数组不在话下。
(4)扩展更方便,WebSocket接口被托管在普通的Web服务之上,跟着Web服务方便扩容,有效规避了性能瓶颈。
WebSocket不仅拥有如此丰富的特性,而且用起来也特别简单。
先在服务端的WebSocket编程,除了引入它的依赖包javaee-api-8.0.1.jar,服务器添加相关代码如下:

package com.websocket.server;

import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint("/testWebSocket")
public class WebSocketServer {
    // 存放每个客户端对应的WebSocket对象
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
private Session mSession; // 当前的连接会话

// 连接成功后调用
@OnOpen
public void onOpen(Session session) {
    System.out.println("WebSocket连接成功");
    this.mSession = session;
    webSocketSet.add(this);
}

// 连接关闭后调用
@OnClose
public void onClose() {
    System.out.println("WebSocket连接关闭");
    webSocketSet.remove(this);
}

// 连接异常时调用
@OnError
public void onError(Throwable error) {
    System.out.println("WebSocket连接异常");
    error.printStackTrace();
}

// 收到客户端消息时调用
@OnMessage
public void onMessage(String msg) throws Exception {
    System.out.println("接收到客户端消息:" + msg);
    for(WebSocketServer item : webSocketSet){
        item.mSession.getBasicRemote().sendText("我听到消息啦“"+msg+"”");
   }
  }
}

启动服务器的Web工程,便能通过形如ws://localhost:8080/HttpServer/testWebSocket这样的地址访问WebSocket。
App端的WebSocket编程,由于WebSocket协议尚未纳入JDK,因此要引入它所依赖的jar包tyrus-standalone-client-1.17.jar。

代码方面则需自定义客户端的连接任务,注意给任务类添加注解@ClientEndpoint,表示该类属于WebSocket的客户端任务。任务内部需要重写onOpen(连接成功后调用)、processMessage(收到服务端消息时调用)、processError(收到服务端错误时调用)三个方法,还得定义一个向服务端发消息的发送方法,消息内容支持文本与二进制两种格式。

下面是处理客户端消息交互工作的示例代码:

import android.app.Activity;
import android.util.Log;

import javax.websocket.*;

@ClientEndpoint
public class AppClientEndpoint {
private final static String TAG = "AppClientEndpoint";
private Activity mAct; // 声明一个活动实例
private OnRespListener mListener; // 消息应答监听器
private Session mSession; // 连接会话

public AppClientEndpoint(Activity act, OnRespListener listener) {
    mAct = act;
    mListener = listener;
}

// 向服务器发送请求报文
public void sendRequest(String req) {
    Log.d(TAG, "发送请求报文:"+req);
    try {
        if (mSession != null) {
            RemoteEndpoint.Basic remote = mSession.getBasicRemote();
            remote.sendText(req); // 发送文本数据
            // remote.sendBinary(buffer); // 发送二进制数据
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 连接成功后调用
@OnOpen
public void onOpen(final Session session) {
    mSession = session;
    Log.d(TAG, "成功创建连接");
}

// 收到服务端消息时调用
@OnMessage
public void processMessage(Session session, String message) {
    Log.d(TAG, "WebSocket服务端返回:" + message);
    if (mListener != null) {
        mAct.runOnUiThread(() -> mListener.receiveResponse(message));
    }
}

// 收到服务端错误时调用
@OnError
public void processError(Throwable t) {
    t.printStackTrace();
}

// 定义一个WebSocket应答的监听器接口
public interface OnRespListener {
    void receiveResponse(String resp);
 }
}

App的活动代码,依次执行下述步骤就能向WebSocket服务器发送消息:获取WebSocket容器→连接WebSocket服务器→调用WebSocket任务的发送方法。其中前两步涉及的初始化代码如下:

private AppClientEndpoint mAppTask; // 声明一个WebSocket客户端任务对象
// 初始化WebSocket的客户端任务
private void initWebSocket() {
    // 创建文本传输任务,并指定消息应答监听器
    mAppTask = new AppClientEndpoint(this, resp -> {
        String desc = String.format("%s 收到服务端返回:%s",
                DateUtil.getNowTime(), resp);
        tv_response.setText(desc);
    });
    // 获取WebSocket容器
    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
    try {
        URI uri = new URI(SERVER_URL); // 创建一个URI对象
        // 连接WebSocket服务器,并关联文本传输任务获得连接会话
        Session session = container.connectToServer(mAppTask, uri);
        // 设置文本消息的最大缓存大小
        session.setMaxTextMessageBufferSize(1024 * 1024 * 10);
        // 设置二进制消息的最大缓存大小
        //session.setMaxBinaryMessageBufferSize(1024 * 1024 * 10);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

因为WebSocket接口仍为网络操作,所以必须在子线程中初始化WebSocket,启动初始化线程的代码如下所示:

// 启动线程初始化WebSocket客户端
new Thread(() -> initWebSocket()).start(); 

同理,发送WebSocket消息也要在子线程中操作,启动消息发送线程的代码如下:

 // 启动线程发送文本消息
 new Thread(() -> mAppTask.sendRequest(content)).start();

最后确保后端的Web服务正在运行,再运行并测试该App,在编辑框输入待发送的文本,此时交互界面如图【成功发送WebSocket消息】所示:


成功发送WebSocket消息.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容