Netty-socketio integra redis y el clúster de servidores envía mensajes

 

Antes de comenzar, primero aprenda los conceptos de habitación y espacio de nombres.

Dirección de enlace de dirección oficial 

En pocas palabras, el socket pertenecerá a una determinada sala, si no se especifica, entonces el socket pertenecerá a la sala predeterminada, y cada sala pertenecerá a un determinado espacio de nombres, el espacio de nombres predeterminado es /.

Cuando el cliente se conecta, puede especificar a qué espacio de nombres pertenece su propio socket ( aquí hay una trampa. El espacio de nombres que defina debe ser /xiuweiSapce. Tenga en cuenta que /xiuweiSapce no es xiuweiSapce ).

var socket = io.connect("http://localhost:9099/xiuweiSpace?room=F006");

En cuanto a qué habitación le pertenece, lo establece el propio servidor.

Luego está la api de Netty-socketIo, y no hay mucha descripción aquí. Compartir un blog, enlaces  y el diseño del contenido del artículo son mejores que los míos.

El resto es ponerse manos a la obra, esta es una solución que usa redis y usa la redissión del cliente Java como una solución de inserción de clúster porque netty-soketio y redisson son del mismo autor. (Adora al Gran Dios de nuevo aquí). La función de publicación y suscripción de Redis se usa aquí. Cuando el servidor necesita enviar mensajes activamente, sin importar a qué instancia se envíe el mensaje, se enviará a otras instancias a través de publicar y suscribir, y finalmente se obtendrán todos los mensajes de inserción de socketClient. .

Luego, sin una palabra, agregue el código 

Importar el pom requerido

		<dependency>
			<groupId>com.corundumstudio.socketio</groupId>
			<artifactId>netty-socketio</artifactId>
			<version>1.7.17</version>
		</dependency>

		<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.11.0</version>
		</dependency>

Clases de configuración y archivos de configuración relacionados

#WebSocket
# host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
lsmdjsj.websocket.host=socketio.host=localhost
lsmdjsj.websocket.socket-port=9099
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
lsmdjsj.websocket.maxFramePayloadLength=1048576
# 设置http交互最大内容长度
lsmdjsj.websocket.maxHttpContentLength=1048576
# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
lsmdjsj.websocket.upgradeTimeout=1000000
# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
lsmdjsj.websocket.pingTimeout=6000000
# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
lsmdjsj.websocket.pingInterval=25000


#redis  
lsmdjsj.redisson.address=redis://127.0.0.1:6379
lsmdjsj.redisson.password=
lsmdjsj.redisson.database=5
@Data
@Component
@ConfigurationProperties(prefix = "lsmdjsj.websocket")
public class WebSocketProperties {

    /**
     * socket 地址
     */
    private String host;
    /**
     * socket 端口
     */
    private Integer socketPort;
    /**
     * 最大每帧处理数据的长度
     */
    private String maxFramePayloadLength;
    /**
     * http交互最大内容长度
     */
    private String maxHttpContentLength;


    /**
     * Ping 心跳间隔(毫秒)
     */
    private Integer pingInterval;
    /**
     * Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
     */
    private Integer pingTimeout;
    /**
     * 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
     */
    private Integer upgradeTimeout;
}
@Data
@ConfigurationProperties(prefix = "lsmdjsj.redisson")
public class RedissonProperty {
    private int timeout = 3000;

    private String address;

    private String password;

    private int connectionPoolSize = 5;

    private int connectionMinimumIdleSize=2;

    private int slaveConnectionPoolSize = 250;

    private int masterConnectionPoolSize = 250;

    private String[] sentinelAddresses;

    private String masterName;
    private int database = 1;

}

Inicializar el servidor y redisson de netty-socketio

@Configuration
@EnableConfigurationProperties(RedissonProperty.class)
public class RedissonConfig {

    @Resource
    private RedissonProperty conf;

    @Bean(name="redission",destroyMethod="shutdown")
    public RedissonClient redission() {
        Config config = new Config();
        config.setCodec(new org.redisson.client.codec.StringCodec());
        if(conf.getSentinelAddresses()!=null && conf.getSentinelAddresses().length>0){
            config.useSentinelServers()
                    .setMasterName(conf.getMasterName()).addSentinelAddress(conf.getSentinelAddresses())
                    .setPassword(conf.getPassword()).setDatabase(conf.getDatabase());
        }else{
            SingleServerConfig serverConfig = config.useSingleServer()
                    .setAddress(conf.getAddress())
                    .setTimeout(conf.getTimeout())
                    .setConnectionPoolSize(conf.getConnectionPoolSize())
                    .setConnectionMinimumIdleSize(conf.getConnectionMinimumIdleSize())
                    .setDatabase(conf.getDatabase());
            if(StringUtils.isNotBlank(conf.getPassword())) {
                serverConfig.setPassword(conf.getPassword());
            }
        }
        return Redisson.create(config);
    }
}

Inicializar netty-socketio

@Configuration
public class SocketIoConfig {

    private static Logger logger = LoggerFactory.getLogger(SocketIoConfig.class);

    @Resource
    private RedissonClient redisson;

    @Resource
    private WebSocketProperties webSocketProperties;

    @Resource
    private NettyExceptionListener nettyExceptionListener;

    /**
     * 创建 StoreFactory
     * @return
     */
    private RedissonStoreFactory  createRedissonStoreFactory(){
        logger.info("创建 RedissonStoreFactory 开始");
        RedissonStoreFactory redissonStoreFactory = new RedissonStoreFactory(redisson);
        logger.info("创建 RedissonStoreFactory 结束");
        return redissonStoreFactory;
    }



    @Bean
    public SocketIOServer getSocketIOServer(){
        logger.info("创建 SocketIOServer 开始");
        //Sokcket配置 参考 jdk
        SocketConfig socketConfig = new SocketConfig();

        socketConfig.setTcpNoDelay(true);
        //在默认情况下,当调用close关闭socke的使用,close会立即返回,
        // 但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回.
        socketConfig.setSoLinger(0);
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        // 设置监听端口
        config.setPort(webSocketProperties.getSocketPort());
        // 协议升级超时时间(毫秒),默认10000。HTTP握手升级为ws协议超时时间
        config.setUpgradeTimeout(webSocketProperties.getUpgradeTimeout());
        // Ping消息间隔(毫秒),默认25000。客户端向服务器发送一条心跳消息间隔
        config.setPingInterval(webSocketProperties.getPingInterval());
        // Ping消息超时时间(毫秒),默认60000,这个时间间隔内没有接收到心跳消息就会发送超时事件
        config.setPingTimeout(webSocketProperties.getPingTimeout());
        // 推荐使用redisson
        config.setStoreFactory(createRedissonStoreFactory());
        //异常处理
        config.setExceptionListener(nettyExceptionListener);
        //手动确认
        config.setAckMode(AckMode.MANUAL);
        // 握手协议参数使用JWT的Token认证方案 认证方案
        config.setAuthorizationListener(data -> {
           /* HttpHeaders httpHeaders = data.getHttpHeaders();
            String token = httpHeaders.get("Authorization");*/
           return  true;
        });
        config.setSocketConfig(socketConfig);
        logger.info("创建 SocketIOServer 结束");
        return new SocketIOServer(config);
    }

    /**
     * spring
     * @param socketServer
     * @return
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }

    @Bean
    public PubSubStore pubSubStore(SocketIOServer socketServer) {
        return socketServer.getConfiguration().getStoreFactory().pubSubStore();
    }
}

Inicie el servicio de socket y suscríbase a eventos, y también puede personalizar los eventos relacionados del espacio de nombres correspondiente

@Component
@Order(1)
public class SocketServerRunner implements CommandLineRunner {

    private static Logger logger = LoggerFactory.getLogger(SocketServerRunner.class);

    @Resource
    private SocketIOServer socketIOServer;

    @Resource
    private PubSubStore pubSubStore;

    @Resource
    private RedissonClient redisson;

    @Override
    public void run(String... args) throws Exception {
        logger.info("socketIOServer 启动");
        socketIOServer.start();

        //订阅消息
        pubSubStore.subscribe(PubSubType.DISPATCH,data -> {
            Collection<SocketIOClient> clients = null;
            String room = data.getRoom();
            String namespace = data.getNamespace();
            Packet packet = data.getPacket();
            String jsonData = packet.getData();
            if(!StringUtils.isBlank(namespace)){
                SocketIONamespace socketIONamespace = socketIOServer.getNamespace(namespace);
                if(StringUtils.isBlank(room)){
                    clients = socketIONamespace.getRoomOperations(room).getClients();
                }
            }else{
                clients = socketIOServer.getBroadcastOperations().getClients();
            }
            if(!CollectionUtils.isEmpty(clients)){
                for (SocketIOClient client : clients) {
                    client.sendEvent(Constants.PUSH_MSG,jsonData);
                }
            }
        },DispatchMessage.class);
        addNameSpace(socketIOServer);
    }


    private void addNameSpace(SocketIOServer socketIOServer){
        SocketIONamespace xiuweiSpace = socketIOServer.addNamespace(Constants.XIU_WEI_NAME_SPACE);
        xiuweiSpace.addConnectListener(client -> {
            Map<String,Object> clientMap = new HashMap<>(16);
            String nameSpace = client.getNamespace().getName();
            String room = client.getHandshakeData().getSingleUrlParam("room");
            String sessionId = client.getSessionId().toString();
            logger.info("xiuweiSpace连接成功, room={},nameSpace={}, sessionId={}", room, nameSpace,sessionId);
            if(StringUtils.isNotBlank(room)){
                client.joinRoom(room);
                clientMap.put("rooms",room);
            }
            clientMap.put("createTime", LocalDateTime.now().toString());
            redisson.getBucket(Constants.KEY_ROOM_PREFIX+Constants.XIU_WEI_NAME_SPACE+sessionId).trySet(clientMap);
        });
        xiuweiSpace.addDisconnectListener(client -> {
            logger.info("客户端:" + client.getSessionId() + "断开连接");
            String sessionId = client.getSessionId().toString();
            redisson.getBucket(Constants.KEY_ROOM_PREFIX+Constants.XIU_WEI_NAME_SPACE+sessionId).delete();
        });
        xiuweiSpace.addEventListener(Constants.XIU_WEI_EVINT,String.class,(client, data, ackSender) -> {
            client.sendEvent(Constants.XIU_WEI_EVINT,data);
            if (ackSender.isAckRequested()) {
                logger.info("xiuwei接受到的消息. message={}", data);
            }
        });
    }
}

De forma predeterminada, aquí se utiliza el socketClient que no especifica un espacio de nombres.

@Component
public class MessageEventHandler {

    private static final Logger logger  = LoggerFactory.getLogger(MessageEventHandler.class);

    public static ConcurrentMap<String, SocketIOClient> socketIOClientMap = new ConcurrentHashMap<>();


    @Resource
    private RedissonClient redisson;

    @Resource
    private SocketIOServer socketIOServer;

    @OnConnect
    public void  onConnect(SocketIOClient client){
        Map<String,Object> clientMap = new HashMap<>(16);
        if(client!=null){
            String room = client.getHandshakeData().getSingleUrlParam("room");
            String nameSpace = client.getNamespace().getName();
            String sessionId = client.getSessionId().toString();
            logger.info("socket连接成功, room={}, sessionId={},namespace={}",room,sessionId,nameSpace);
            if(StringUtils.isNotBlank(room)){
                client.joinRoom(room);
                clientMap.put("rooms",room);
            }
            clientMap.put("createTime", LocalDateTime.now().toString());
            redisson.getBucket(Constants.KEY_ROOM_PREFIX+sessionId).trySet(clientMap);
        }
    }

    /**
     * 客户端关闭连接时触发
     *
     * @param client
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        logger.info("客户端:" + client.getSessionId() + "断开连接");
    }

    /**
     * 客户端事件
     *
     * @param client   客户端信息
     * @param request 请求信息
     * @param msg     客户端发送数据
     */
    @OnEvent(value = "messageevent")
    public void onEvent(SocketIOClient client, AckRequest request, String msg) {
        logger.info("发来消息:" + msg);
        //回发消息
        JSONObject jsonObject = JSON.parseObject(msg);
        String message = jsonObject.getString("message");
        Collection<SocketIOClient> clients = socketIOServer.getBroadcastOperations().getClients();
        for (SocketIOClient clientByRoom : clients) {
            clientByRoom.sendEvent("messageevent", client.getSessionId().toString()+":   "+message);
        }
    }

}

Finalmente, presione activamente el controlador del cliente

 @RequestMapping("/push")
    @ResponseBody
    public SinoHttpResponse<Boolean> pushMsgByService(@RequestBody ChatMessage chatMessage){
        SocketIONamespace namespace = socketIOServer.getNamespace(chatMessage.getNamespace());
        Collection<SocketIOClient> allClients = namespace.getAllClients();
        for (SocketIOClient client : allClients) {
            client.sendEvent(chatMessage.getEventName(),chatMessage.getMessage());
        }
        return SinoHttpResponse.success(true);
    }

Finalmente, publique el html que se puede probar. Mi tecnología front-end es enorme. Aquí uso la demostración escrita por jquery

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>webSocket测试</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <!-- 可选的Bootstrap主题文件(一般不用引入) -->
    <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
    <!--<script type="text/javascript" src="js/jquery-1.7.2.js"></script>-->
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script type="text/javascript">
        $(function(){
            /**
             * 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
             * 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
             **/
            var socket = io.connect("http://localhost:9099?room=F006");
            var firstconnect = true;
            if(firstconnect) {
                console.log("第一次链接初始化");
                //监听服务器连接事件
                socket.on('connect', function(){
                    $("#tou").html("链接服务器成功!");
                     });
                //监听服务器关闭服务事件
                socket.on('disconnect', function(){
                    $("#tou").html("与服务器断开了链接!");
                    });
                //监听服务器端发送消息事件
                socket.on('messageevent', function(data) {
                    $("#msg").html($("#msg").html() + "<br/>" + data);
                });
                firstconnect = false;
            } else {
                socket.socket.reconnect();
            }





            $('#send').bind('click', function() {
                send();
            });



            function send(){
                if (socket != null) {
                    debugger;
                    var message = document.getElementById('message').value;
                    var title = "message";
                    var obj = {message:message,title:title};
                    var str = JSON.stringify(obj);
                    socket.emit("messageevent",str);
                } else {
                    alert('未与服务器链接.');
                }
            }
        });
    </script>

</head>
<body>
<div class="page-header" id="tou">
    webSocket及时聊天Demo程序
</div>
<div class="well" id="msg">
</div>
<div class="col-lg">
    <div class="input-group">
        <input type="text" class="form-control" placeholder="发送信息..." id="message">
        <span class="input-group-btn">
        <button class="btn btn-default" type="button" id="send" >发送</button>
      </span>
    </div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</div><!-- /.row --><br><br>
后台主动发送消息:<button class="btn btn-default" type="button" onclick="mess()">发送</button>
<br><br>
<a href="/user/index">back to index</a>
<script src="http://js.biocloud.cn/jquery/1.11.3/jquery.min.js"></script>
<script>
    function mess() {
        $.ajax({
            type: "POST",
            data:{index : "echo"},
            url: "/websocket/auditing",
            success: function (data) {
                //alert(data);
            }
        });
    }
</script>
</body>
</html>

Finalmente, gracias por mirar, esto es solo una demostración, habrá varios problemas, bienvenido a corregirme

Supongo que te gusta

Origin blog.csdn.net/evil_lrn/article/details/105808364
Recomendado
Clasificación