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

2023-04-25  本文已影响0人  古早味蛋糕

一、利用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
上一篇 下一篇

猜你喜欢

热点阅读