美文网首页工作生活
Netty+WebSocket+SpringBoot

Netty+WebSocket+SpringBoot

作者: AbyssLich | 来源:发表于2019-07-03 11:43 被阅读0次

pom文件引入

        <!--    websocket    -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.36.Final</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>

Application文件修改

端口信息自行修改
public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args);
        try {
            System.out.println("http://127.0.0.1:8083/ws/index");
            new NettyServer(12345).start();
        }catch(Exception e) {
            System.out.println("NettyServerError:"+e.getMessage());
        }
    }

NettyServer服务启动类

包含基本配置信息
public class NettyServer {

    private final int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap sb = new ServerBootstrap();
            sb.option(ChannelOption.SO_BACKLOG, 1024);
            sb.group(group, bossGroup) // 绑定线程池
                    .channel(NioServerSocketChannel.class) // 指定使用的channel
                    .localAddress(this.port)// 绑定监听端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("收到新连接");
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            ch.pipeline().addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10));
                            ch.pipeline().addLast(new MyWebSocketHandler());//自定义助手类
                        }
                    });
            ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
            System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
            cf.channel().closeFuture().sync(); // 关闭服务器通道
        } finally {
            group.shutdownGracefully().sync(); // 释放线程池资源
            bossGroup.shutdownGracefully().sync();
        }
    }
}

MyChannelHandlerPool

通道组池,管理所有websocket连接

channelIdMap用来存储用户id与通道id关系,用来实现点对点通讯,本来准备使用redis管理,遇到@Resource无论如何都是null的问题,暂时使用Map管理。

public class MyChannelHandlerPool {

    public MyChannelHandlerPool(){}

    public static Map<String, ChannelId> channelIdMap = new HashMap<>();

    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

}

MyWebSocketHandler

WebSocket处理器,处理websocket连接相关

该类继承了SimpleChannelInboundHandler<TextWebSocketFrame>并重写部分生命周期以实现部分需求

public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端建立连接,通道开启!");

        //添加到channelGroup通道组
        MyChannelHandlerPool.channelGroup.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端断开连接,通道关闭!");
        //添加到channelGroup 通道组
        MyChannelHandlerPool.channelGroup.remove(ctx.channel());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("客户端收到服务器数据:" + msg.text());
        String msgText = msg.text();
        JSONObject jsonObject = JSONObject.parseObject(msgText);
        Map<String, Object> map = jsonObject;
        String longId = ctx.channel().id().asLongText();
        if (map.containsKey("login")) {
            login(map, ctx.channel().id());
        } else {
            sendToSomeone(map);
        }
    }

    //ping、pong
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        //用于触发用户事件,包含触发读空闲、写空闲、读写空闲
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.ALL_IDLE) {
                Channel channel = ctx.channel();
                //关闭无用channel,以防资源浪费
                channel.close();
            }
        }
    }

    private void sendAllMessage(String message){
        //收到信息后,群发给所有channel
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(message));
    }
    private void sendToSomeone(Map<String, Object> map) {
//        ChannelId channelId = (ChannelId) redisOperator.getObject(6, map.get("toId").toString());
        ChannelId fromId = MyChannelHandlerPool.channelIdMap.get(map.get("fromId"));
        MyChannelHandlerPool.channelGroup.find(fromId).writeAndFlush(new TextWebSocketFrame(map.toString()));

        ChannelId toId = MyChannelHandlerPool.channelIdMap.get(map.get("toId"));
        MyChannelHandlerPool.channelGroup.find(toId).writeAndFlush(new TextWebSocketFrame(map.toString()));
    }

    private void login(Map<String, Object> map, ChannelId channelId) {
        String uid = map.get("login").toString();
//        redisOperator.set(6, uid, channelId);
        MyChannelHandlerPool.channelIdMap.put(uid, channelId);
    }
关于SimpleChannelInboundHandler其他的生命周期
WX20190703-113539.png

测试

方便测试引入额外html依赖pom文件
        <!--添加static和templates的依赖/html-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
修改配置文件
  spring:
    thymeleaf:
      prefix: classpath:/templates/
一个简单的Controller
@Controller
@RequestMapping("/ws")
@Slf4j
public class IndexController {
    @Resource
    private RedisOperator redisOperator;

    @GetMapping("/index")
    public ModelAndView  index(){
        ModelAndView mav=new ModelAndView("socket");
        mav.addObject("uid", RandomUtil.getRandomLetter(6, RandomUtil.CodeType.ALL_NUMBER));
        return mav;
    }
}
/resource/templates/socket.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Netty-Websocket</title>
    <script type="text/javascript">
        // by zhengkai.blog.csdn.net
        var socket;
        if(!window.WebSocket){
            window.WebSocket = window.MozWebSocket;
        }
        if(window.WebSocket){
            socket = new WebSocket("ws://127.0.0.1:12345/ws");
            socket.onmessage = function(event){
                var ta = document.getElementById('responseText');
                ta.value += event.data+"\r\n";
            };
            socket.onopen = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "Netty-WebSocket服务器。。。。。。连接  \r\n";
                var uid = document.getElementById("uid").value;
                login(uid);
            };
            socket.onclose = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "Netty-WebSocket服务器。。。。。。关闭 \r\n";
            };
        }else{
            alert("您的浏览器不支持WebSocket协议!");
        }
        function send(fromId, msg, toId){
            if(!window.WebSocket){return;}
            if(socket.readyState == WebSocket.OPEN){
                var jsonObject = {};
                jsonObject["fromId"] = fromId;
                jsonObject["msg"] = msg;
                jsonObject["toId"] = toId;

                socket.send(JSON.stringify(jsonObject));
            }else{
                alert("WebSocket 连接没有建立成功!");
            }
        }
        function login(fromId){
            if(!window.WebSocket){return;}
            if(socket.readyState == WebSocket.OPEN){
                var jsonObject = JSON.parse("{}");
                jsonObject["login"] = fromId;
                socket.send(JSON.stringify(jsonObject));
            }else{
                alert("WebSocket 连接没有建立成功!");
            }
        }

    </script>
</head>
<body>
<form onSubmit="return false;">
    <label>ID</label><input type="text" id="uid" name="uid" th:value="${uid}" /><br />
    <label>TEXT</label><input type="text" name="message" value="这里输入消息" /> <br />
    <label>TEXT</label><input type="text" name="toUser" value="这里输入对方id" /> <br />
    <br /> <input type="button" value="发送ws消息"
                  onClick="send(this.form.uid.value, this.form.message.value, this.form.toUser.value)" />
    <hr color="black" />
    <h3>服务端返回的应答消息</h3>
    <textarea id="responseText" style="width: 1024px;height: 300px;"></textarea>
</form>
</body>
</html>

相关文章

  • Netty+WebSocket+SpringBoot

    pom文件引入 Application文件修改 端口信息自行修改 NettyServer服务启动类 包含基本配置信...

网友评论

    本文标题:Netty+WebSocket+SpringBoot

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