springboot は tio-websocket ソリューションを統合してシンプルなチャットを実現します

最初に書きます:

一般的に使用される http プロトコルはステートレスであり、クライアントにアクティブに応答できません。当初、動的ステータス追跡を実現する唯一の方法はポーリングまたはその他の非効率な方法を使用することでした。そのため、サーバーがクライアントにデータをアクティブにプッシュできるようにするために WebSocket プロトコルが導入されました。WebSocket API では、ブラウザとサーバーはハンドシェイクを完了するだけでよく、双方向データ送信のために両者の間に永続的な接続を直接作成できます。簡単に言うと、2 つ以上のクライアントは相互に通信できません。1 対 1 のチャットと同様の機能を実現するには、基本的にクライアント A が情報をソケット サーバーに送信し、ソケット サーバーがその情報をクライアント B に積極的にプッシュします。または、複数のクライアント間での情報転送を実現するための複数のクライアント。

不満点: t-io は優れたソケット フレームワークですが、ドキュメントが非常に少ないです。作者が書いたドキュメントも初心者には優しくありません (お金がかかることを除いて)。他のドキュメントはお金がかかるか、書かれている内容が不十分です。この技術的なドキュメントは、環境は本当にゴミです。

1. パッケージをインポートします (TIO の 2 つの依存関係をインポートします。その他の必要な依存関係については詳しく説明しません)

        <dependency>
            <groupId>org.t-io</groupId>
            <artifactId>tio-websocket-spring-boot-starter</artifactId>
            <version>3.6.0.v20200315-RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.t-io</groupId>
            <artifactId>tio-core-spring-boot-starter</artifactId>
            <version>3.6.0.v20200315-RELEASE</version>
        </dependency>

2.yml設定

server:
  port: 8652

tio:
  websocket:
    server:
      port: 8078
      heartbeat-timeout: 12000
    cluster:
      enabled: false
  customPort: 4768 //自定义socket服务端监听端口,其实也可以用上面server.port做监听端口

3. 設定パラメータ

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.tio.utils.time.Time;

/**
 * @Author 955
 * @Date 2023-07-26 17:25
 * @Description
 */
@Component
public class CaseServerConfig {
    
    

    /**
     * 协议名字(可以随便取,主要用于开发人员辨识)
     */
    public static final String PROTOCOL_NAME = "xxxxxxx";
    public static final String CHARSET = "utf-8";
    /**
     * 监听的ip
     */
    public static final String SERVER_IP = null;//null表示监听所有,并不指定ip
    /**
     * 监听端口
     */
    public static int PORT;
    /**
     * 心跳超时时间,单位:毫秒
     */
    public static final int HEARTBEAT_TIMEOUT = 1000 * 60;

    /**
     * 服务器地址
     */
    public static final String SERVER = "127.0.0.1";

    /**
     * ip数据监控统计,时间段
     *
     * @author tanyaowu
     */
    public static interface IpStatDuration {
    
    
        public static final Long DURATION_1 = Time.MINUTE_1 * 5;
        public static final Long[] IPSTAT_DURATIONS = new Long[]{
    
    DURATION_1};
    }
    
    /**
     * 用于群聊的group id(自定义)
     */
    public static final String GROUP_ID = "showcase-websocket";

    @Value("${tio.customPort}")
    public void setPort(int port) {
    
    
        PORT = port;
    }

}

4. いくつかの監視クラスを実装する

1.ServerAioListener リスニング

import org.tio.core.ChannelContext;
import org.tio.core.intf.Packet;
import org.tio.server.intf.ServerAioListener;

/**
 * @Author 955
 * @Date 2023-07-26 17:24
 * @Description
 */
public class ServerAioListenerImpl implements ServerAioListener {
    
    

    @Override
    public void onAfterConnected(ChannelContext channelContext, boolean b, boolean b1) throws Exception {
    
    

    }

    @Override
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int i) throws Exception {
    
    

    }

    @Override
    public void onAfterReceivedBytes(ChannelContext channelContext, int i) throws Exception {
    
    

    }

    @Override
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean b) throws Exception {
    
    

    }

    @Override
    public void onAfterHandled(ChannelContext channelContext, Packet packet, long l) throws Exception {
    
    

    }

    @Override
    public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String s, boolean b) throws Exception {
    
    

    }

    @Override
    public boolean onHeartbeatTimeout(ChannelContext channelContext, Long aLong, int i) {
    
    
        return false;
    }

}

2.IpStatListener モニタリング (これはオプションです)


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.intf.Packet;
import org.tio.core.stat.IpStat;
import org.tio.core.stat.IpStatListener;

/**
 * @Author 955
 * @Date 2023-07-27 12:03
 * @Description
 */
public class ShowcaseIpStatListener implements IpStatListener {
    
    

    @SuppressWarnings("unused")
    private static Logger log = LoggerFactory.getLogger(ShowcaseIpStatListener.class);
    public static final ShowcaseIpStatListener me = new ShowcaseIpStatListener();

    /**
     *
     */
    private ShowcaseIpStatListener() {
    
    
    }

    @Override
    public void onExpired(TioConfig tioConfig, IpStat ipStat) {
    
    
        //在这里把统计数据入库中或日志
//        if (log.isInfoEnabled()) {
    
    
//            log.info("可以把统计数据入库\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect, IpStat ipStat) throws Exception {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onAfterConnected\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onDecodeError(ChannelContext channelContext, IpStat ipStat) {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onDecodeError\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess, IpStat ipStat) throws Exception {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onAfterSent\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize, IpStat ipStat) throws Exception {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onAfterDecoded\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes, IpStat ipStat) throws Exception {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onAfterReceivedBytes\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }

    @Override
    public void onAfterHandled(ChannelContext channelContext, Packet packet, IpStat ipStat, long cost) throws Exception {
    
    
//        if (log.isInfoEnabled()) {
    
    
//            log.info("onAfterHandled\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }

}

3.WsServerAioListener リスニング


import com.wlj.config.CaseServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.intf.Packet;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.common.WsSessionContext;
import org.tio.websocket.server.WsServerAioListener;

/**
 * @Author 955
 * @Date 2023-07-27 12:01
 * @Description
 */
public class ShowcaseServerAioListener extends WsServerAioListener {
    
    

    private static Logger log = LoggerFactory.getLogger(ShowcaseServerAioListener.class);
    public static final ShowcaseServerAioListener me = new ShowcaseServerAioListener();

    private ShowcaseServerAioListener() {
    
    
    }

    @Override
    public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
    
    
        super.onAfterConnected(channelContext, isConnected, isReconnect);
        if (log.isInfoEnabled()) {
    
    
            log.info("onAfterConnected\r\n{}", channelContext);
        }
    }

    @Override
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception {
    
    
        super.onAfterSent(channelContext, packet, isSentSuccess);
        if (log.isInfoEnabled()) {
    
    
            log.info("onAfterSent\r\n{}\r\n{}", packet.logstr(), channelContext);
        }
    }

    @Override
    public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception {
    
    
        super.onBeforeClose(channelContext, throwable, remark, isRemove);
        if (log.isInfoEnabled()) {
    
    
            log.info("onBeforeClose\r\n{}", channelContext);
        }
        WsSessionContext wsSessionContext = (WsSessionContext) channelContext.getAttribute();
        if (wsSessionContext != null && wsSessionContext.isHandshaked()) {
    
    
            int count = Tio.getAllChannelContexts(channelContext.tioConfig).getObj().size();
            String msg = channelContext.getClientNode().toString() + " 离开了,现在共有【" + count + "】人在线";
            //用tio-websocket,服务器发送到客户端的Packet都是WsResponse
            WsResponse wsResponse = WsResponse.fromText(msg, CaseServerConfig.CHARSET);
            //群发
            Tio.sendToGroup(channelContext.tioConfig, CaseServerConfig.GROUP_ID, wsResponse);
        }
    }

    @Override
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception {
    
    
        super.onAfterDecoded(channelContext, packet, packetSize);
        if (log.isInfoEnabled()) {
    
    
            log.info("onAfterDecoded\r\n{}\r\n{}", packet.logstr(), channelContext);
        }
    }

    @Override
    public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception {
    
    
        super.onAfterReceivedBytes(channelContext, receivedBytes);
        if (log.isInfoEnabled()) {
    
    
            log.info("onAfterReceivedBytes\r\n{}", channelContext);
        }
    }

    @Override
    public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception {
    
    
        super.onAfterHandled(channelContext, packet, cost);
        if (log.isInfoEnabled()) {
    
    
            log.info("onAfterHandled\r\n{}\r\n{}", packet.logstr(), channelContext);
        }
    }


}

4.IWsMsgHandler インターセプト (内部のロジックは特定のビジネスに基づいていますが、これは実装する必要があります。実装しないと起動時にエラーが報告されます)

package com.wlj.im;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.TioConfig;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.server.handler.IWsMsgHandler;

/**
 * @Author 955
 * @Date 2023-07-31 18:26
 * @Description
 */
@Slf4j
@Component
public class TioWsMsgHandler implements IWsMsgHandler {
    
    
    /**
     * TIO-WEBSOCKET 配置信息
     */
    public static TioConfig serverTioConfig;

    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
    
    
        serverTioConfig = channelContext.tioConfig;
        return httpResponse;
    }

    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
    
    
        // 拿到用户id
        String id = httpRequest.getParam("id");
        // 绑定用户
        Tio.bindUser(channelContext, id);

        // 绑定业务类型(根据业务类型判定处理相关业务)
        String bsId = httpRequest.getParam("bsId");
        if (StringUtils.isNotBlank(bsId)) {
    
    
            Tio.bindBsId(channelContext, bsId);
        }
        // 给用户发送消息
        WsResponse wsResponse = WsResponse.fromText("您已成功连接 WebSocket 服务器", "UTF-8");
        Tio.sendToUser(channelContext.tioConfig, id, wsResponse);
    }

    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
    
    
        return null;
    }

    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
    
    
        // 关闭连接
        Tio.remove(channelContext, "WebSocket Close");
        return null;
    }

    @Override
    public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
    
    
        WsResponse wsResponse = WsResponse.fromText("服务器已收到消息:" + s, "UTF-8");
        Tio.sendToUser(channelContext.tioConfig, userid, wsResponse);
        return null;
    }
}

5. 一部のメッセージ本文 (ビジネス ニーズに応じた)

import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.tio.core.intf.Packet;

import java.util.List;

/**
 * @Author 955
 * @Date 2023-07-26 17:26
 * @Description  消息体
 */
@Setter
@Getter
public class MindPackage extends Packet {
    
    

    private static final long serialVersionUID = -172060606924066412L;
    public static final String CHARSET = "utf-8";
    private List<JSONObject> body;

}



import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.tio.core.intf.Packet;

import java.io.Serializable;

/**
 * @Author 955
 * @Date 2023-07-26 17:27
 * @Description  响应消息体
 */
@Getter
@Setter
public class ResponsePackage extends Packet {
    
    

    private static final long serialVersionUID = -172060606924066412L;
    public static final String CHARSET = "utf-8";
    //响应具体内容
    private JSONObject body;
    //电话号码
    private String phoneNum;
    // 下发指令类型
    private Integer type;
}

6. 一部の VO (実際の業務による)

import lombok.Data;

import java.io.Serializable;

/**
 * @Author 955
 * @Date 2023-07-26 17:28
 * @Description 客户端接收指令类型
 */
@Data
public class ClientDirectivesVo implements Serializable {
    
    

    // 结束上报指令
    public static final int END_REPORT_RESPONSE = 0;
    // 心跳检查指令
    public static final int HEART_BEET_REQUEST = 1;
    // GPS开始上报指令
    public static final int GPS_START_REPORT_RESPONSE = 2;
    // 客户端数据下发
    public static final int DATA_DISTRIBUTION = 3;


    // 0:结束上报指令,1:心跳检测指令,2:GPS开始上报指令,3:客户端数据下发
    private Integer type;

}


import lombok.Data;

import java.io.Serializable;

/**
 * @Author 955
 * @Date 2023-07-26 17:29
 * @Description 业务实体vo,根据自己业务来
 */
@Data
public class PositioningDataReportVo implements Serializable {
    
    

    private String userId;

    private String name;

    private String phone;

    private String type;

}

import lombok.Data;

import java.io.Serializable;

/**
 * @Author 955
 * @Date 2023-07-26 17:30
 * @Description 回执方法vo
 */
@Data
public class ReceiptDataVo implements Serializable {
    
    

    //所属用户id
    private String userId;

    //所属用户电话号码
    private String phone;

    //xxx具体业务字段
    private String yl;

}

import lombok.Data;

import java.io.Serializable;

/**
 * @Author 955
 * @Date 2023-07-26 17:31
 * @Description 响应vo
 */
@Data
public class ResponseVo implements Serializable {
    
    

    //响应类型
    private Integer type;

    //响应值
    private Integer value;

}

7. 具体的な業務方法


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wlj.tcp.MindPackage;
import com.wlj.tcp.ResponsePackage;
import com.wlj.vo.ClientDirectivesVo;
import com.wlj.vo.PositioningDataReportVo;
import com.wlj.vo.ReceiptDataVo;
import com.wlj.vo.ResponseVo;
import jodd.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.server.intf.ServerAioHandler;
import org.tio.utils.hutool.CollUtil;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author 955
 * @Date 2023-07-26 17:27
 * @Description 具体业务方法
 */
@Slf4j
public class ServerAioHandlerImpl implements ServerAioHandler {
    
    


    private static AtomicInteger counter = new AtomicInteger(0);

    private Map<String, ChannelContext> channelMaps = new ConcurrentHashMap<>();

    private Queue<ResponsePackage> respQueue = new LinkedBlockingQueue<>();

    private Queue<ResponsePackage> heartQueue = new LinkedBlockingQueue<>();

    public boolean offer2SendQueue(ResponsePackage respPacket) {
    
    
        return respQueue.offer(respPacket);
    }

    public Queue<ResponsePackage> getRespQueue() {
    
    
        return respQueue;
    }

    public boolean offer2HeartQueue(ResponsePackage respPacket) {
    
    
        return heartQueue.offer(respPacket);
    }

    public Map<String, ChannelContext> getChannelMaps() {
    
    
        return channelMaps;
    }

    /**
     * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
     * 总的消息结构:消息体
     * 消息体结构: 对象的json串的16进制字符串
     */
    @Override
    public MindPackage decode(ByteBuffer buffer, int i, int i1, int i2, ChannelContext channelContext) throws AioDecodeException {
    
    
        MindPackage imPacket = new MindPackage();
        try {
    
    
            List<JSONObject> msgList = new ArrayList<>();
            //Charset charset = Charset.forName("UTF-8");
            //这里使用UTF-8收中文时会报错
            Charset charset = Charset.forName("GBK");
            CharsetDecoder decoder = charset.newDecoder();
            CharBuffer charBuffer = decoder.decode(buffer);
            String str = charBuffer.toString();
            if (str.indexOf("{") != 0) {
    
    
                str = str.substring(str.indexOf("{"));
            }
            if (str.indexOf("}{") > -1) {
    
    
                String[] split = str.split("}");
                List<String> list = Arrays.asList(split);
                list.forEach(item -> {
    
    
                    item += "}";
                    msgList.add(JSON.parseObject(item));
                });
            } else {
    
    
                msgList.add(JSON.parseObject(str));
            }
            log.info("收到" + msgList.size() + "条消息");
            imPacket.setBody(msgList);
            return imPacket;
        } catch (Exception e) {
    
    
            return imPacket;
        }
    }

    /**
     * 编码:把业务消息包编码为可以发送的ByteBuffer
     */
    @Override
    public ByteBuffer encode(Packet packet, TioConfig groupContext, ChannelContext channelContext) {
    
    
        ResponsePackage helloPacket = (ResponsePackage) packet;
        JSONObject body = helloPacket.getBody();
        //写入消息体
        try {
    
    
            return ByteBuffer.wrap(body.toJSONString().getBytes("GB2312"));
        } catch (UnsupportedEncodingException e) {
    
    

        }
        return null;
    }

 /**
     * 处理消息(最核心的方法)
     */
    @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
    
    
        MindPackage helloPacket = (MindPackage) packet;
        List<JSONObject> msgList = helloPacket.getBody();
        if (CollectionUtil.isNotEmpty(msgList)) {
    
    
            msgList.forEach(body -> {
    
    
                if (body != null) {
    
    
                    log.info("收到设备上报信息 " + body);
                    // 获取指令
                    Integer type = body.getInteger("type");
                    if (type != null) {
    
    
                        channelContext.set("type", type);
                        String phoneNum = body.getString("phoneNum");
                        String content = body.getString("content");
                        Tio.bindToken(channelContext, phoneNum);
                        ResponsePackage respPacket = new ResponsePackage();
                        switch (type) {
    
    
                            // 接收下线指令
                            case ClientDirectivesVo.END_REPORT_RESPONSE:
                                //保存连接
                                channelMaps.put(phoneNum, channelContext);
                                //TODO 更改客户端状态为下线状态
                                log.info("收到{}客户端下线通知", phoneNum);
                                Tio.unbindUser(channelContext.tioConfig, phoneNum);
                                respPacket.setPhoneNum("您已下线");
                                // 回执方法
                                receiptHandler(respPacket, phoneNum, ClientDirectivesVo.END_REPORT_RESPONSE);
                                break;
                            case ClientDirectivesVo.HEART_BEET_REQUEST:  //接收心跳检查指令
                                //保存连接
                                channelMaps.put(phoneNum, channelContext);
                                Tio.bindUser(channelContext, phoneNum);
                                log.info("收到{}客户端心跳检查指令", phoneNum);
                                // 回执方法
                                receiptHandler(respPacket, phoneNum, ClientDirectivesVo.HEART_BEET_REQUEST);
                                break;
                            case ClientDirectivesVo.GPS_START_REPORT_RESPONSE: //开始上报GPS指令
                                //保存连接
                                channelMaps.put(phoneNum, channelContext);

//                                PositioningDataReportVo vo = JSONObject.toJavaObject(body, PositioningDataReportVo.class);

                                log.info("收到{}客户端上报GPS指令,上报数据:{}", phoneNum, "vo");
                                // 回执方法
                                receiptHandler(respPacket, phoneNum, ClientDirectivesVo.GPS_START_REPORT_RESPONSE);
                                break;
                            case ClientDirectivesVo.DATA_DISTRIBUTION: //开始下发数据指令
                                //保存连接
                                channelMaps.put(phoneNum, channelContext);
                                log.info("收到{}客户端下发数据指令", phoneNum);
                                SetWithLock<ChannelContext> obj = Tio.getByUserid(channelContext.tioConfig, phoneNum);
                                if (ObjectUtil.isEmpty(obj)) {
    
    
                                    // 回执方法
                                    respPacket.setBody(JSONObject.parseObject("{\"type\":\"该用户不在线\"}"));
                                    receiptHandler(respPacket, phoneNum, ClientDirectivesVo.GPS_START_REPORT_RESPONSE);
                                } else {
    
    
                                    // 回执方法
                                    DataDistributionReportVo data = new DataDistributionReportVo();
                                    data.setPhone(phoneNum);
                                    data.setServiceInfo(content);
                                    // 回复时的设备标志,必填
                                    respPacket.setPhoneNum(phoneNum);
                                    respPacket.setBody((JSONObject) JSON.toJSON(data));
                                    respPacket.setType(ClientDirectivesVo.DATA_DISTRIBUTION);
                                    Tio.sendToUser(channelContext.tioConfig, phoneNum, respPacket);
                                }

                                break;
                        }
                    }
                }
            });
        }
        return;
    }

    /**
     * 回执信息方法
     *
     * @Author: laohuang
     * @Date: 2022/11/24 13:53
     */
    public void receiptHandler(ResponsePackage respPacket, String phoneNum, Integer clientDirectives) {
    
    
        // 回执信息
        //ResponseVo callVo = new ResponseVo();
        //callVo.setType(clientDirectives);
        // 响应结果  1:成功 0:失败
        //callVo.setValue(1);
        // 回复时的设备标志,必填
        respPacket.setPhoneNum(phoneNum);
        //respPacket.setBody((JSONObject) JSON.toJSON(callVo));
        respPacket.setType(clientDirectives);
        offer2SendQueue(respPacket);

    }

    private Object locker = new Object();

    public ServerAioHandlerImpl() {
    
    
        try {
    
    
            new Thread(() -> {
    
    
                while (true) {
    
    
                    try {
    
    
                        ResponsePackage respPacket = respQueue.poll();
                        if (respPacket != null) {
    
    
                            synchronized (locker) {
    
    
                                String phoneNum = respPacket.getPhoneNum();
                                ChannelContext channelContext = channelMaps.get(phoneNum);
                                if (channelContext != null) {
    
    
                                    Boolean send = Tio.send(channelContext, respPacket);
                                    String s = JSON.toJSONString(respPacket);
                                    System.err.println("发送数据" + s);
                                    System.err.println("数据长度" + s.getBytes().length);
                                    log.info("下发设备指令 设备ip" + channelContext + " 设备[" + respPacket.getPhoneNum() + "]" + (send ? "成功" : "失败") + "消息:" + JSON.toJSONString(respPacket.getBody()));
                                }
                            }
                        }
                    } catch (Exception e) {
    
    
                        log.error(e.getMessage());
                    } finally {
    
    
                        log.debug("发送队列大小:" + respQueue.size());
                        ThreadUtil.sleep(10);
                    }
                }
            }).start();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 确保只有一个呼叫器响应后修改呼叫记录
     *
     * @param recordId  记录id
     * @param resCallSn 响应的呼叫器sn
     */
    public synchronized void updateCallRecordAndStopResponse(Long recordId, String resCallSn, String sn) {
    
    


    }
}

8.Tioの設定

public abstract class TioServerConfig {
    
    
    /**
     * 协议名字(可以随便取,主要用于开发人员辨识)
     */
    public static final String PROTOCOL_NAME = "IM";

    public static final String CHARSET = "utf-8";


    /**
     * 监听端口
     */
    public static final int SERVER_PORT = 4768;

    /**
     * 心跳超时时间,单位:毫秒
     */
    public static final int HEARTBEAT_TIMEOUT = 1000 * 60;

    /**
     * ip数据监控统计,时间段
     *
     * @author tanyaowu
     */
    public interface IpStatDuration {
    
    
        Long DURATION_1 = Time.MINUTE_1 * 5;
        Long[] IPSTAT_DURATIONS = new Long[]{
    
    DURATION_1};
    }

}



import org.tio.server.ServerTioConfig;
import org.tio.websocket.server.WsServerStarter;

/**
 * TIO 配置文件
 *
 * @author 
 * @since  
 */
public class TioWebsocketStarter {
    
    

    private WsServerStarter wsServerStarter;
    private ServerTioConfig serverGroupContext;


    /**
     * @author tanyaowu
     */
    public TioWebsocketStarter(int port, TioWsMsgHandler wsMsgHandler) throws Exception {
    
    
        wsServerStarter = new WsServerStarter(port, wsMsgHandler);

        serverGroupContext = wsServerStarter.getServerTioConfig();
        serverGroupContext.setName(TioServerConfig.PROTOCOL_NAME);
        serverGroupContext.setServerAioListener(ServerAioListener.me);

        //设置ip监控
        serverGroupContext.setIpStatListener(IpStatListener.me);
        //设置ip统计时间段
        serverGroupContext.ipStats.addDurations(TioServerConfig.IpStatDuration.IPSTAT_DURATIONS);

        //设置心跳超时时间
        serverGroupContext.setHeartbeatTimeout(TioServerConfig.HEARTBEAT_TIMEOUT);
        //如果你希望通过wss来访问,就加上下面的代码吧,不过首先你得有SSL证书(证书必须和域名相匹配,否则可能访问不了ssl)
//		String keyStoreFile = "classpath:config/ssl/keystore.jks";
//		String trustStoreFile = "classpath:config/ssl/keystore.jks";
//		String keyStorePwd = "214323428310224";
//		serverGroupContext.useSsl(keyStoreFile, trustStoreFile, keyStorePwd);

    }

    public WsServerStarter getWsServerStarter() {
    
    
        return wsServerStarter;
    }


}

9. 内部スタートアップクラス


@Component
public class StartTioRunner implements CommandLineRunner {
    
    

    @Resource
    private TioWsMsgHandler tioWsMsgHandler;

    private TioWebsocketStarter appStarter;

    @Override
    public void run(String... args) throws Exception {
    
    
        appStarter = new TioWebsocketStarter(TioServerConfig.SERVER_PORT, tioWsMsgHandler);
        appStarter.getWsServerStarter().start();
    }

    public TioWebsocketStarter getAppStarter() {
    
    
        return appStarter;
    }
}


9. NetAssist テスト ツールを使用して効果をテストします (ダウンロードに 0 ポイント)
https://download.csdn.net/download/m0_49605579/88106789?spm=1001.2014.3001.5503
ここに画像の説明を挿入します

注: ここでのリモート ホスト ポートは、yml で構成された tioPort であり、プロジェクトの開始時にコンソールに出力されるリスニング ポートです。接続されると、データをサーバーに送信できます。ツールは複数のシミュレートされたクライアントを開くことができます。

ここに画像の説明を挿入します
最後に以下
のように記述します。 メイン ビジネス ハンドラーのロジックは次のとおりです。
ステップ 1:
ユーザー A は、type: HEART_BEET_REQUEST に対応する {"type": 1, "phoneNum": "User A"} を送信し、Tio.bindUser(channelContext, userId);ユーザーをバインドします。
ユーザー B は上記と同じ操作を実行します {"type":1,"phoneNum":"User A"}
ここに画像の説明を挿入します

ステップ 2:
ユーザー A は、タイプ: DATA_DISTRIBUTION に対応する {"type": 3, "content": "Send message to user B", "phoneNum": "User B"} を送信し、サーバー経由で指示を発行します。サーバーは最初に決定します。オンラインかどうか。オンラインの場合、ユーザー A が送信したメッセージは、携帯電話番号がユーザー B であるユーザー B にプッシュされます。このとき、ユーザー B はメッセージをリアルタイムで受信します。
ここに画像の説明を挿入します

レンダリング (メッセージの送信、オンラインへの移行、オフラインへの移行をそれぞれ制御するタイプ):
ここに画像の説明を挿入します
ここに画像の説明を挿入します
補足: Handler 以外の場所から ServerTioConfig を取得したい場合は、このメソッドを使用できます (上記の実装方法に従って開始された場合に限ります) CommandLineRunner メソッド)

@Resource
private ApplicationContext applicationContext;
StartTioRunner startTioRunner = applicationContext.getBean(StartTioRunner.class);
ServerTioConfig serverTioConfig = startTioRunner.getAppStarter().getWsServerStarter().getServerTioConfig();

おすすめ

転載: blog.csdn.net/m0_49605579/article/details/131956239