美文网首页
Android 即时通信(二)WebSocket传输消息

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

作者: 古早味蛋糕 | 来源:发表于2023-04-25 16:32 被阅读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

    相关文章

      网友评论

          本文标题:Android 即时通信(二)WebSocket传输消息

          本文链接:https://www.haomeiwen.com/subject/zbfmjdtx.html