美文网首页im
SpringBoot+Netty-socketio实现webso

SpringBoot+Netty-socketio实现webso

作者: liangxifeng833 | 来源:发表于2018-07-07 18:15 被阅读1054次

socket.io是js实现的,websocket框架,为了解决浏览器不兼容问题而设计
socket.io.js下载地址:https://cdnjs.com/libraries/socket.io
常用的方式是,前端使用socket.io.js,后端使用node.js实现socket.io的接口,可是我们的架构后端使用的是java,所以我使用的是netty-socketio,基于spring-boot实现;

一.pom.xml中添加依赖

        <!-- https://mvnrepository.com/artifact/com.corundumstudio.socketio/netty-socketio -->
        <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.11</version>
        </dependency>

二.修改SpringBoot启动类

package domain;

import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import domain.util.YmlConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;


/**
 * Spring Boot 应用启动类
 *
 * @author : liangxifeng
 * @date : 2018-1-19
 */
// Spring Boot 应用的标识
@SpringBootApplication
//如果mybatis中service实现类中加入事务注解,需要此处添加该注解
@EnableTransactionManagement
// mapper 接口类扫描包配置
@MapperScan("domain.dao")
public class AdverCenterApplication extends SpringBootServletInitializer {
    //读取配置文件
    @Autowired
    private YmlConfig ymlConfig;
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(AdverCenterApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(AdverCenterApplication.class, args);
    }

    /**
     * 注册netty-socketio服务端
     * @author liangxifeng 2018-07-07
     * @return
     */
    @Bean
    public SocketIOServer socketIOServer() {
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();

        String os = System.getProperty("os.name");
        if(os.toLowerCase().startsWith("win")){   //在本地window环境测试时用localhost
            System.out.println("this is  windows");
            config.setHostname("localhost");
        } else {
            config.setHostname("192.168.9.209");
        }
        config.setPort("9092");

        /*config.setAuthorizationListener(new AuthorizationListener() {//类似过滤器
            @Override
            public boolean isAuthorized(HandshakeData data) {
                //http://localhost:8081?username=test&password=test
                //例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证
                // String username = data.getSingleUrlParam("username");
                // String password = data.getSingleUrlParam("password");
                return true;
            }
        });*/

        final SocketIOServer server = new SocketIOServer(config);
        return server;
    }

    /**
     * tomcat启动时候,扫码socket服务器并注册
     * @param socketServer
     * @return
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }
}

三.在项目服务启动的时候启动socket.io服务, 新增ServerRunner.java

package domain.websocketio;

import com.corundumstudio.socketio.SocketIOServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 在项目服务启动的时候启动socket.io服务
 * @author liangxifeng 2018-07-07
 */
@Component
@Order(value=1)
@Slf4j
public class ServerRunner implements CommandLineRunner {

    private final SocketIOServer server;


    @Autowired
    public ServerRunner(SocketIOServer server) {
        this.server = server;
    }

    @Override
    public void run(String... args) throws Exception {
        server.start();
        log.info("socket.io启动成功!");
    }

}

四.接收前台用户信息类 MessageInfo.java

package domain.websocketio;

import lombok.ToString;
import org.springframework.stereotype.Component;

/**
 * 接收前台用户信息类
 * @author liangxifeng 2018-07-07
 */
@Component
@ToString
public class MessageInfo {

    String msgContent;

    public String getMsgContent() {
        return this.msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;

    }
}

消息事件,作为后端与前台交互MessageEventHandler.java

package domain.websocketio;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import domain.websocket.MyWebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 消息事件,作为后端与前台交互
 * @authoer liangxifeng 2018-07-07
 */
@Component
public class MessageEventHandler {
    public static SocketIOServer socketIoServer;
    static ArrayList<UUID> listClient = new ArrayList<UUID>();
    static final int limitSeconds = 60;
    //线程安全的map
    public static ConcurrentHashMap<String,SocketIOClient> webSocketMap = new ConcurrentHashMap<String, SocketIOClient>();

    @Autowired
    public MessageEventHandler(SocketIOServer server) {
        this.socketIoServer = server;
    }

    /**
     * 客户端连接的时候触发,前端js触发:socket = io.connect("http://192.168.9.209:9092");
     * @param client
     */
    @OnConnect
    public void onConnect(SocketIOClient client) {
        String mac = client.getHandshakeData().getSingleUrlParam("mac");
        listClient.add(client.getSessionId());
        //以mac地址为key,SocketIOClient 为value存入map,后续可以指定mac地址向客户端发送消息
        webSocketMap.put(mac,client);
        //socketIoServer.getClient(client.getSessionId()).sendEvent("message", "back data");
        System.out.println("客户端:" + client.getSessionId() + "已连接,mac="+mac);
    }

    /**
     * 客户端关闭连接时触发:前端js触发:socket.disconnect();
     * @param client
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        System.out.println("客户端:" + client.getSessionId() + "断开连接");
    }

    /**
     * 自定义消息事件,客户端js触发:socket.emit('messageevent', {msgContent: msg}); 时触发
     * 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
     * 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
     * @param client 客户端信息
     * @param request 请求信息
     * @param data 客户端发送数据{msgContent: msg}
     */
    @OnEvent(value = "messageevent")
    public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data) {
        System.out.println("发来消息:" + data);
        //服务器端向该客户端发送消息
        //socketIoServer.getClient(client.getSessionId()).sendEvent("messageevent", "你好 data");
        client.sendEvent("messageevent","我是服务器都安发送的信息");
    }

    public static void sendBuyLogEvent() {   //这里就是向客户端推消息了
        //String dateTime = new DateTime().toString("hh:mm:ss");

        for (UUID clientId : listClient) {
            if (socketIoServer.getClient(clientId) == null) continue;
            socketIoServer.getClient(clientId).sendEvent("enewbuy", "当前时间", 1);
        }

    }
}

五.目录结构

image.png

六.html内容

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>websocket-java-socketio</title>
  <script src="/static/js/socket.io.js"></script></script>
 </head>
<body>
<h1>Socket.io Test</h1>
<div><p id="status">Waiting for input</p></div>
<div><p id="message">hello world!</p></div>
<button id="connect" onClick='connect()'/>Connect</button>
<button id="disconnect" onClick='disconnect()'>Disconnect</button>
<button id="send" onClick='send()'/>Send Message</button>
</body>

<script type="text/javascript">

/**
* 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
* 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
**/
    var socket = io.connect("http://192.168.9.209:9092?mac=2"); 
    var firstconnect = true;
 
function connect() {
    if(firstconnect) {

        //socket.on('reconnect', function(){ status_update("Reconnected to Server"); });
        //socket.on('reconnecting', function( nextRetry ){ status_update("Reconnecting in "
        //+ nextRetry + " seconds"); });
        //socket.on('reconnect_failed', function(){ message("Reconnect Failed"); });
        //firstconnect = false;
    } else {
        socket.socket.reconnect();
    }
}

//监听服务器连接事件
socket.on('connect', function(){ status_update("Connected to Server"); });
//监听服务器关闭服务事件
socket.on('disconnect', function(){ status_update("Disconnected from Server"); });
//监听服务器端发送消息事件
    socket.on('messageevent', function(data) { 
      message(data)
      //console.log("服务器发送的消息是:"+data); 
    }); 

//断开连接
function disconnect() {
    socket.disconnect();
}
 
function message(data) {
    document.getElementById('message').innerHTML = "Server says: " + data;
}
 
function status_update(txt){
    document.getElementById('status').innerHTML = txt;
}
 
function esc(msg){
    return msg.replace(/</g, '<').replace(/>/g, '>');
}
//点击发送消息触发 
function send() {   
    //console.log("点击了发送消息,开始向服务器发送消息")
            var msg = "我很好的,是的."; 
            socket.emit('messageevent', {msgContent: msg});  
}; 
</script>
</html>

七.测试

  • 访问浏览器


    socketio-1.jpg
  • 服务器端日志内容:


    socketio-2.jpg

服务器指定mac地址用户向客户端发送消息

package domain.controller;

import com.corundumstudio.socketio.SocketIOClient;
import domain.domain.DomainResponse;
import domain.domain.Exhibition;
import domain.service.interfaces.exhibition.InsertExhibitionService;
import domain.websocket.PayWebSocket;
import domain.websocketio.MessageEventHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/exhibiton")
public class ExhibitonController {
    @Autowired
    private InsertExhibitionService insertExhibitionService;
    @GetMapping(value = "/sendMsg/{mac}")
    /**
     * 指定某个websocket发送从服务器发送到浏览器,服务器主动推送
     */
    private DomainResponse testSendMsg(@PathVariable("mac") String mac ) throws  Exception{
        DomainResponse msg = new DomainResponse(Integer.parseInt(mac),mac,"指定给"+mac+"用户发消息");
        PayWebSocket payWebSocket = PayWebSocket.payWebSocketMap.get(mac);
        if(payWebSocket == null)
        {
            return new DomainResponse(0,"没有该用户",0);
        }
        payWebSocket.sendMessage(msg);
        return msg;
    }

    /**
     * web socketio方式指定用户发送消息
     * @param mac
     * @return
     */
    @GetMapping(value = "/socketIo/{mac}")
    private DomainResponse socketIoTest(@PathVariable("mac") String mac){
        SocketIOClient client = MessageEventHandler.webSocketMap.get(mac);
        DomainResponse msg = new DomainResponse(Integer.parseInt(mac),mac,"指定给"+mac+"用户发消息");
        client.sendEvent("messageevent","haha======++");
        return msg;
    }
}

测试

  • 指定mac地址为2的用户发送消息


    image.png
  • 客户端会收到消息


    socketio-3.jpg

相关文章

网友评论

    本文标题:SpringBoot+Netty-socketio实现webso

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