SpringBoot+Netty-socketio實(shí)現(xiàn)websocket

socket.io是js實(shí)現(xiàn)的隅居,websocket框架苟穆,為了解決瀏覽器不兼容問題而設(shè)計(jì)
socket.io.js下載地址:https://cdnjs.com/libraries/socket.io
常用的方式是削饵,前端使用socket.io.js加叁,后端使用node.js實(shí)現(xiàn)socket.io的接口扮休,可是我們的架構(gòu)后端使用的是java锣枝,所以我使用的是netty-socketio厢拭,基于spring-boot實(shí)現(xiàn);

一.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啟動(dòng)類

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 應(yīng)用啟動(dòng)類
 *
 * @author : liangxifeng
 * @date : 2018-1-19
 */
// Spring Boot 應(yīng)用的標(biāo)識(shí)
@SpringBootApplication
//如果mybatis中service實(shí)現(xiàn)類中加入事務(wù)注解撇叁,需要此處添加該注解
@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);
    }

    /**
     * 注冊(cè)netty-socketio服務(wù)端
     * @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環(huán)境測(cè)試時(shí)用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
                //例如果使用上面的鏈接進(jìn)行connect供鸠,可以使用如下代碼獲取用戶密碼信息,本文不做身份驗(yàn)證
                // String username = data.getSingleUrlParam("username");
                // String password = data.getSingleUrlParam("password");
                return true;
            }
        });*/

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

    /**
     * tomcat啟動(dòng)時(shí)候陨闹,掃碼socket服務(wù)器并注冊(cè)
     * @param socketServer
     * @return
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }
}

三.在項(xiàng)目服務(wù)啟動(dòng)的時(shí)候啟動(dòng)socket.io服務(wù), 新增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;

/**
 * 在項(xiàng)目服務(wù)啟動(dòng)的時(shí)候啟動(dòng)socket.io服務(wù)
 * @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啟動(dòng)成功楞捂!");
    }

}

四.接收前臺(tái)用戶信息類 MessageInfo.java

package domain.websocketio;

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

/**
 * 接收前臺(tái)用戶信息類
 * @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;

    }
}

消息事件,作為后端與前臺(tái)交互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;

/**
 * 消息事件正林,作為后端與前臺(tái)交互
 * @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;
    }

    /**
     * 客戶端連接的時(shí)候觸發(fā)泡一,前端js觸發(fā):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,后續(xù)可以指定mac地址向客戶端發(fā)送消息
        webSocketMap.put(mac,client);
        //socketIoServer.getClient(client.getSessionId()).sendEvent("message", "back data");
        System.out.println("客戶端:" + client.getSessionId() + "已連接,mac="+mac);
    }

    /**
     * 客戶端關(guān)閉連接時(shí)觸發(fā):前端js觸發(fā):socket.disconnect();
     * @param client
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        System.out.println("客戶端:" + client.getSessionId() + "斷開連接");
    }

    /**
     * 自定義消息事件,客戶端js觸發(fā):socket.emit('messageevent', {msgContent: msg}); 時(shí)觸發(fā)
     * 前端js的 socket.emit("事件名","參數(shù)數(shù)據(jù)")方法觅廓,是觸發(fā)后端自定義消息事件的時(shí)候使用的,
     * 前端js的 socket.on("事件名",匿名函數(shù)(服務(wù)器向客戶端發(fā)送的數(shù)據(jù)))為監(jiān)聽服務(wù)器端的事件
     * @param client 客戶端信息
     * @param request 請(qǐng)求信息
     * @param data 客戶端發(fā)送數(shù)據(jù){msgContent: msg}
     */
    @OnEvent(value = "messageevent")
    public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data) {
        System.out.println("發(fā)來消息:" + data);
        //服務(wù)器端向該客戶端發(fā)送消息
        //socketIoServer.getClient(client.getSessionId()).sendEvent("messageevent", "你好 data");
        client.sendEvent("messageevent","我是服務(wù)器都安發(fā)送的信息");
    }

    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", "當(dāng)前時(shí)間", 1);
        }

    }
}

五.目錄結(jié)構(gòu)

image.png

六.html內(nèi)容

<!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("事件名","參數(shù)數(shù)據(jù)")方法鼻忠,是觸發(fā)后端自定義消息事件的時(shí)候使用的,
* 前端js的 socket.on("事件名",匿名函數(shù)(服務(wù)器向客戶端發(fā)送的數(shù)據(jù)))為監(jiān)聽服務(wù)器端的事件
**/
    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();
    }
}

//監(jiān)聽服務(wù)器連接事件
socket.on('connect', function(){ status_update("Connected to Server"); });
//監(jiān)聽服務(wù)器關(guān)閉服務(wù)事件
socket.on('disconnect', function(){ status_update("Disconnected from Server"); });
//監(jiān)聽服務(wù)器端發(fā)送消息事件
    socket.on('messageevent', function(data) { 
      message(data)
      //console.log("服務(wù)器發(fā)送的消息是:"+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, '>');
}
//點(diǎn)擊發(fā)送消息觸發(fā) 
function send() {   
    //console.log("點(diǎn)擊了發(fā)送消息,開始向服務(wù)器發(fā)送消息")
            var msg = "我很好的,是的."; 
            socket.emit('messageevent', {msgContent: msg});  
}; 
</script>
</html>

七.測(cè)試

  • 訪問瀏覽器


    socketio-1.jpg
  • 服務(wù)器端日志內(nèi)容:


    socketio-2.jpg

服務(wù)器指定mac地址用戶向客戶端發(fā)送消息

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}")
    /**
     * 指定某個(gè)websocket發(fā)送從服務(wù)器發(fā)送到瀏覽器杈绸,服務(wù)器主動(dòng)推送
     */
    private DomainResponse testSendMsg(@PathVariable("mac") String mac ) throws  Exception{
        DomainResponse msg = new DomainResponse(Integer.parseInt(mac),mac,"指定給"+mac+"用戶發(fā)消息");
        PayWebSocket payWebSocket = PayWebSocket.payWebSocketMap.get(mac);
        if(payWebSocket == null)
        {
            return new DomainResponse(0,"沒有該用戶",0);
        }
        payWebSocket.sendMessage(msg);
        return msg;
    }

    /**
     * web socketio方式指定用戶發(fā)送消息
     * @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+"用戶發(fā)消息");
        client.sendEvent("messageevent","haha======++");
        return msg;
    }
}

測(cè)試

  • 指定mac地址為2的用戶發(fā)送消息


    image.png
  • 客戶端會(huì)收到消息


    socketio-3.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帖蔓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瞳脓,更是在濱河造成了極大的恐慌塑娇,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劫侧,死亡現(xiàn)場(chǎng)離奇詭異埋酬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)烧栋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門写妥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人审姓,你說我怎么就攤上這事珍特。” “怎么了魔吐?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵扎筒,是天一觀的道長(zhǎng)莱找。 經(jīng)常有香客問我,道長(zhǎng)嗜桌,這世上最難降的妖魔是什么奥溺? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮症脂,結(jié)果婚禮上谚赎,老公的妹妹穿的比我還像新娘。我一直安慰自己诱篷,他們只是感情好壶唤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棕所,像睡著了一般闸盔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琳省,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天迎吵,我揣著相機(jī)與錄音,去河邊找鬼针贬。 笑死击费,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桦他。 我是一名探鬼主播蔫巩,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼快压!你這毒婦竟也來了圆仔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蔫劣,失蹤者是張志新(化名)和其女友劉穎坪郭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脉幢,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歪沃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫌松。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪曙。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豆瘫,靈堂內(nèi)的尸體忽然破棺而出珊蟀,到底是詐尸還是另有隱情菊值,我是刑警寧澤外驱,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布育灸,位于F島的核電站,受9級(jí)特大地震影響昵宇,放射性物質(zhì)發(fā)生泄漏磅崭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一瓦哎、第九天 我趴在偏房一處隱蔽的房頂上張望砸喻。 院中可真熱鬧,春花似錦蒋譬、人聲如沸割岛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽癣漆。三九已至,卻和暖如春剂买,著一層夾襖步出監(jiān)牢的瞬間惠爽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工瞬哼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婚肆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓坐慰,卻偏偏與公主長(zhǎng)得像较性,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讨越,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容