前言

Netty-SocketIO是一个开源的、基于Netty的、Java版的即时消息推送项目。通过Netty-SocketIO,我们可以轻松的实现服务端主动向客户端推送消息的场景,比如说股票价格变化、K线图、消息提醒等。它和websocket有相同的作用,只不过Netty-SocketIO可支持所有的浏览器。

传输流程

在这里插入图片描述

代码实现

pom

  <!--    docker io 通信   -->
        <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.19</version>
        </dependency>

Event

/**
 * @author lanys
 * @Description:
 * @date 26/8/2021 上午11:13
 */

public interface Event {

    /*****************************************聊天消息相关[start]********************************************/

    /**
     * 派对消息发送
     */
    String ChatAcceptLineMsg = "partyAcceptLineMsg";

/*****************************************聊天消息相关[end]********************************************/

}

NettySocketIoConfig

/**
 * @author lanys
 * @Description: 配置 Socket 服务器
 * @date 26/8/2021 上午11:19
 */
@Configuration
public class NettySocketIoConfig {

    /**
     * 配置 Socket 服务器
     *
     * @return SocketIOServer
     */
    @Bean
    public SocketIOServer socketIOServer(){
        com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
        configuration.setHostname("127.0.0.1");
        configuration.setPort(9094);
        return new SocketIOServer(configuration);
    }

    /**
     * SpringAnnotationScanner 用于扫描 Netty-socketIo @OnConnect @OnEvent
     *
     * @return  Spring 注解扫描器
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(){
        return new SpringAnnotationScanner(socketIOServer());
    }
}

Message

/**
 * @author lanys
 * @Description: socket 接收实体
 * @date 26/8/2021 上午11:42
 */
@Data
public class Message {
    private String name;
    private String msg;
}

PartySocketIoStoreName

/**
 * @author lanys
 * @Description:  派对套接字key存储
 * @date 23/8/2021 上午10:44
 */
public class PartySocketIoStoreName {

    /**
     * 房间名
     */
    public static String ROOM = "room";

    /**
     * 房间名
     */
    public static String ROOM_USER = "roomUser";


}

ChatRoomEventHandler

 /**
 * @author lanys
 * @Description:  聊天事件
 * @date 26/8/2021 上午11:02
 */
@Component
@Slf4j
public class ChatRoomEventHandler {


    @Autowired
    private SocketIOServer socketIOServer;


    /**
     * 连接
     *
     * @param client  client
     */
    @OnConnect
    public void clientChatOnConnect(SocketIOClient client){
       String id = client.getHandshakeData().getSingleUrlParam("id");
       //存储SocketIOClient
       client.joinRoom(PartySocketIoStoreName.ROOM);
       //回发消息
       client.sendEvent("message", "onConnect back");
       System.out.println("客户端:" + client.getSessionId() + "已连接,id=" + id);
   }

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

    /**
     * 客户端事件
     *
     * @param client   客户端信息
     * @param request 请求信息
     * @param data     客户端发送数据
     */
    @OnEvent(value = Event.ChatAcceptLineMsg)
    public void onEvent(SocketIOClient client, AckRequest request, Message data) {
        System.out.println(data.getName() + "发来消息:" + data.getMsg());

        request.sendAckData(Event.ChatAcceptLineMsg, "我是服务器发来的信息");
        //广播消息
        socketIOServer.getRoomOperations(PartySocketIoStoreName.ROOM).sendEvent(Event.ChatAcceptLineMsg, data.getName() + ": " + data.getMsg());
    }

}

client.joinRoom(PartySocketIoStoreName.ROOM); 类似一个HashMap,将client信息就行存储,等触发客户端事件,根据组的用户信息进行广播发送

SocketIoBootApplication

/**
 * @author lanys
 * @Description: 启动类
 * @date 26/8/2021 上午9:47
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class SocketIoBootApplication  implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(SocketIoBootApplication.class,args);
    }

    @Autowired
    private SocketIOServer socketIOServer;

    @Override
    public void run(String... args) throws Exception {
        socketIOServer.start();
        System.out.println("socket.io启动成功!");
    }
}

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Netty视频弹幕实现 Author:Binhao Liu</title>
    <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
    <style type="text/css" media="screen">
        * {
            margin: 0px;
            padding: 0px
        }

        html, body {
            height: 100%
        }

        body {
            overflow: hidden;
            background-color: #FFF;
            text-align: center;
        }

        .flex-column {
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            align-items: center;
        }

        .flex-row {
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }

        .wrap {
            overflow: hidden;
            width: 70%;
            height: 600px;
            margin: 100px auto;
            padding: 20px;
            background-color: transparent;
            box-shadow: 0 0 9px #222;
            border-radius: 20px;
        }

        .wrap .box {
            position: relative;
            width: 100%;
            height: 90%;
            background-color: #000000;
            border-radius: 10px
        }

        .wrap .box span {
            position: absolute;
            top: 10px;
            left: 20px;
            display: block;
            padding: 10px;
            color: #336688
        }

        .wrap .send {
            display: flex;
            width: 100%;
            height: 10%;
            background-color: #000000;
            border-radius: 8px
        }

        .wrap .send input {
            width: 40%;
            height: 60%;
            border: 0;
            outline: 0;
            border-radius: 5px 0px 0px 5px;
            box-shadow: 0px 0px 5px #d9d9d9;
            text-indent: 1em
        }

        .wrap .send .send-btn {
            width: 100px;
            height: 60%;
            background-color: #fe943b;
            color: #FFF;
            text-align: center;
            border-radius: 0px 5px 5px 0px;
            line-height: 30px;
            cursor: pointer;
        }

        .wrap .send .send-btn:hover {
            background-color: #4cacdc
        }
    </style>
</head>

<body>
<div class="wrap flex-column">
    <div class="box">
        <video src="./imgs/s1.mp4" width="100%" height="100%" controls autoplay></video>
    </div>
    <div class="send flex-row">

        <input type="text" class="con" placeholder="弹幕发送[]~(^v^)~*"/>

        <div class="send-btn" onclick="javascript:sendMsg(document.querySelector('.con').value)">发送</div>
    </div>
    
    <button id="connect" onClick='connect()'>Connect</button>
</div>
<div><p id="broadcast">广播区域</p></div>
<div><p id="status">等待连接</p></div>
<div><p id="message">你好!</p></div>
<script type="text/javascript">

     /**
   * 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
   * 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
   **/
   var socket;

//请求连接
function connect() {
  if(socket!=null)return;
  socket = io.connect("http://localhost:9094?id=123456")
  //监听服务器连接事件
  socket.on('connect', function()
  { status_update("连接到服务器");
  });
  //监听服务器关闭服务事件
  socket.on('disconnect', function(){
    status_update("从服务器中断开");
  });
  //监听服务器端发送消息事件
  socket.on('partyAcceptLineMsg', function(data) {
    message(data)
  });
  // //监听服务器广播事件
  // socket.on('Broadcast', function(data) {
  //   Broadcast(data)
  // });
}

//断开连接
function disconnect() {
  socket.disconnect();
  socket=null;
}

//显示服务器发来的消息
function message(data) {
    // document.getElementById('responseText').innerHTML = "Server says: " + data;
    //var ta = document.getElementById('responseText');
    //ta.value += event.data+"\r\n";
    createEle(data);
  }
  //显示当前状态
  function status_update(txt){
    document.getElementById('status').innerHTML = txt;
  }
  //显示服务器广播
  function Broadcast(data) {
    // document.getElementById('responseText').innerHTML = "广播: " + data;
  }
  //点击发送消息触发
  function send() {
    var msg = document.getElementById('input').value;
    socket.emit('partyAcceptLineMsg', {name: '小明', msg: msg});
    var ta =  document.getElementById('responseText');
    ta.value += "小明: "+msg+"\r\n";
  };

   function sendMsg(msg) {
       console.log("开始发送");
       console.log(msg);
    socket.emit('partyAcceptLineMsg', {name: '小明', msg: msg});
    console.log("发送结束");
    }


    //1.获取元素
    var oBox = document.querySelector('.box');   //获取.box元素
    var cW = oBox.offsetWidth;   //获取box的宽度
    var cH = oBox.offsetHeight;   //获取box的高度
    function createEle(txt) {
        //动态生成span标签
        var oMessage = document.createElement('span');   //创建标签
        oMessage.innerHTML = txt;   //接收参数txt并且生成替换内容
        oMessage.style.left = cW + 'px';  //初始化生成位置x
        oBox.appendChild(oMessage);   //把标签塞到oBox里面
        roll.call(oMessage, {
            //call改变函数内部this的指向
            timing: ['linear', 'ease-out'][~~(Math.random() * 2)],
            color: '#' + (~~(Math.random() * (1 << 24))).toString(16),
            top: random(0, cH),
            fontSize: random(16, 32)
        });
    }

    function roll(opt) {
        //弹幕滚动
        //如果对象中不存在timing 初始化
        opt.timing = opt.timing || 'linear';
        opt.color = opt.color || '#fff';
        opt.top = opt.top || 0;
        opt.fontSize = opt.fontSize || 16;
        this._left = parseInt(this.offsetLeft);   //获取当前left的值
        this.style.color = opt.color;   //初始化颜色
        this.style.top = opt.top + 'px';
        this.style.fontSize = opt.fontSize + 'px';
        this.timer = setInterval(function () {
            if (this._left <= 100) {
                clearInterval(this.timer);   //终止定时器
                this.parentNode.removeChild(this);
                return;   //终止函数
            }
            switch (opt.timing) {
                case 'linear':   //如果匀速
                    this._left += -2;
                    break;
                case 'ease-out':   //
                    this._left += (0 - this._left) * .01;
                    break;
            }
            this.style.left = this._left + 'px';
        }.bind(this), 1000 / 60);
    }

    function random(start, end) {
        //随机数封装
        return start + ~~(Math.random() * (end - start));
    }

    var aLi = document.querySelectorAll('li');   //10

    function forEach(ele, cb) {
        for (var i = 0, len = aLi.length; i < len; i++) {
            cb && cb(ele[i], i);
        }
    }

    forEach(aLi, function (ele, i) {
        ele.style.left = i * 100 + 'px';
    });
    //产生闭包
    var obj = {
        num: 1,
        add: function () {
            this.num++;   //obj.num = 2;
            (function () {
                console.log(this.num);
            })
        }
    };
    obj.add();//window

</script>
</body>
</html>

效果展示

在这里插入图片描述

总结

开始入门,就是这么简单!!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐