SpringBoot+Netty-socketio实现websocket

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实现;

  • 参考地址:http://www.my-lin.cn/2017/04/01/netty-socket.io_for_spring-boot/

一.pom.xml中添加依赖

        
        
            com.corundumstudio.socketio
            netty-socketio
            1.7.11
        

二.修改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 listClient = new ArrayList();
    static final int limitSeconds = 60;
    //线程安全的map
    public static ConcurrentHashMap webSocketMap = new ConcurrentHashMap();

    @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);
        }

    }
}

五.目录结构

SpringBoot+Netty-socketio实现websocket_第1张图片
image.png

六.html内容



 
  
  
  websocket-java-socketio
  
 

Socket.io Test

Waiting for input

hello world!

七.测试

  • 访问浏览器


    SpringBoot+Netty-socketio实现websocket_第2张图片
    socketio-1.jpg
  • 服务器端日志内容:


    SpringBoot+Netty-socketio实现websocket_第3张图片
    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的用户发送消息


    SpringBoot+Netty-socketio实现websocket_第4张图片
    image.png
  • 客户端会收到消息


    SpringBoot+Netty-socketio实现websocket_第5张图片
    socketio-3.jpg

你可能感兴趣的:(SpringBoot+Netty-socketio实现websocket)