当前位置:   article > 正文

Netty实战,Springboot + netty +websocket 实现推送消息

springboot整合netty websocket完整示例

开发者(KaiFaX)

面向全栈工程师的开发者
专注于前端、Java/Python/Go/PHP的技术社区

来源 | blog.csdn.net/weixin_44912855

74ec506e88ac58175e2e473dbd66137f.png

学过 Netty 的都知道,Netty 对 NIO 进行了很好的封装,简单的 API,庞大的开源社区。深受广大程序员喜爱。基于此本文分享一下基础的 netty 使用。实战制作一个 Netty + websocket 的消息推送小栗子。

netty服务器

  1. @Component
  2. public class NettyServer {
  3.     static final Logger log = LoggerFactory.getLogger(NettyServer.class);
  4.     /**
  5.      * 端口号
  6.      */
  7.     @Value("${webSocket.netty.port:8888}")
  8.     int port;
  9.     EventLoopGroup bossGroup;
  10.     EventLoopGroup workGroup;
  11.     @Autowired
  12.     ProjectInitializer nettyInitializer;
  13.     @PostConstruct
  14.     public void start() throws InterruptedException {
  15.         new Thread(() -> {
  16.             bossGroup = new NioEventLoopGroup();
  17.             workGroup = new NioEventLoopGroup();
  18.             ServerBootstrap bootstrap = new ServerBootstrap();
  19.             // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
  20.             bootstrap.group(bossGroup, workGroup);
  21.             // 设置NIO类型的channel
  22.             bootstrap.channel(NioServerSocketChannel.class);
  23.             // 设置监听端口
  24.             bootstrap.localAddress(new InetSocketAddress(port));
  25.             // 设置管道
  26.             bootstrap.childHandler(nettyInitializer);
  27.             // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
  28.             ChannelFuture channelFuture = null;
  29.             try {
  30.                 channelFuture = bootstrap.bind().sync();
  31.                 log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
  32.                 // 对关闭通道进行监听
  33.                 channelFuture.channel().closeFuture().sync();
  34.             } catch (InterruptedException e) {
  35.                 e.printStackTrace();
  36.             }
  37.         }).start();
  38.     }
  39.     /**
  40.      * 释放资源
  41.      */
  42.     @PreDestroy
  43.     public void destroy() throws InterruptedException {
  44.         if (bossGroup != null) {
  45.             bossGroup.shutdownGracefully().sync();
  46.         }
  47.         if (workGroup != null) {
  48.             workGroup.shutdownGracefully().sync();
  49.         }
  50.     }
  51. }

Netty配置

管理全局Channel以及用户对应的channel(推送消息)

  1. public class NettyConfig {
  2.     /**
  3.      * 定义全局单利channel组 管理所有channel
  4.      */
  5.     private static volatile ChannelGroup channelGroup = null;
  6.     /**
  7.      * 存放请求ID与channel的对应关系
  8.      */
  9.     private static volatile ConcurrentHashMap<String, Channel> channelMap = null;
  10.     /**
  11.      * 定义两把锁
  12.      */
  13.     private static final Object lock1 = new Object();
  14.     private static final Object lock2 = new Object();
  15.     public static ChannelGroup getChannelGroup() {
  16.         if (null == channelGroup) {
  17.             synchronized (lock1) {
  18.                 if (null == channelGroup) {
  19.                     channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  20.                 }
  21.             }
  22.         }
  23.         return channelGroup;
  24.     }
  25.     public static ConcurrentHashMap<String, Channel> getChannelMap() {
  26.         if (null == channelMap) {
  27.             synchronized (lock2) {
  28.                 if (null == channelMap) {
  29.                     channelMap = new ConcurrentHashMap<>();
  30.                 }
  31.             }
  32.         }
  33.         return channelMap;
  34.     }
  35.     public static Channel getChannel(String userId) {
  36.         if (null == channelMap) {
  37.             return getChannelMap().get(userId);
  38.         }
  39.         return channelMap.get(userId);
  40.     }
  41. }

管道配置

  1. @Component
  2. public class ProjectInitializer extends ChannelInitializer<SocketChannel> {
  3.     /**
  4.      * webSocket协议名
  5.      */
  6.     static final String WEBSOCKET_PROTOCOL = "WebSocket";
  7.     /**
  8.      * webSocket路径
  9.      */
  10.     @Value("${webSocket.netty.path:/webSocket}")
  11.     String webSocketPath;
  12.     @Autowired
  13.     WebSocketHandler webSocketHandler;
  14.     @Override
  15.     protected void initChannel(SocketChannel socketChannel) throws Exception {
  16.         // 设置管道
  17.         ChannelPipeline pipeline = socketChannel.pipeline();
  18.         // 流水线管理通道中的处理程序(Handler),用来处理业务
  19.         // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
  20.         pipeline.addLast(new HttpServerCodec());
  21.         pipeline.addLast(new ObjectEncoder());
  22.         // 以块的方式来写的处理器
  23.         pipeline.addLast(new ChunkedWriteHandler());
  24.         pipeline.addLast(new HttpObjectAggregator(8192));
  25.         pipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true65536 * 10));
  26.         // 自定义的handler,处理业务逻辑
  27.         pipeline.addLast(webSocketHandler);
  28.     }
  29. }

自定义handler

  1. @Component
  2. @ChannelHandler.Sharable
  3. public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
  4.     private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
  5.     /**
  6.      * 一旦连接,第一个被执行
  7.      */
  8.     @Override
  9.     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  10.         log.info("有新的客户端链接:[{}]", ctx.channel().id().asLongText());
  11.         // 添加到channelGroup 通道组
  12.         NettyConfig.getChannelGroup().add(ctx.channel());
  13.     }
  14.     /**
  15.      * 读取数据
  16.      */
  17.     @Override
  18.     protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
  19.         log.info("服务器收到消息:{}", msg.text());
  20.         // 获取用户ID,关联channel
  21.         JSONObject jsonObject = JSONUtil.parseObj(msg.text());
  22.         String uid = jsonObject.getStr("uid");
  23.         NettyConfig.getChannelMap().put(uid, ctx.channel());
  24.         // 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户ID
  25.         AttributeKey<String> key = AttributeKey.valueOf("userId");
  26.         ctx.channel().attr(key).setIfAbsent(uid);
  27.         // 回复消息
  28.         ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器收到消息啦"));
  29.     }
  30.     @Override
  31.     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
  32.         log.info("用户下线了:{}", ctx.channel().id().asLongText());
  33.         // 删除通道
  34.         NettyConfig.getChannelGroup().remove(ctx.channel());
  35.         removeUserId(ctx);
  36.     }
  37.     @Override
  38.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  39.         log.info("异常:{}", cause.getMessage());
  40.         // 删除通道
  41.         NettyConfig.getChannelGroup().remove(ctx.channel());
  42.         removeUserId(ctx);
  43.         ctx.close();
  44.     }
  45.     /**
  46.      * 删除用户与channel的对应关系
  47.      */
  48.     private void removeUserId(ChannelHandlerContext ctx) {
  49.         AttributeKey<String> key = AttributeKey.valueOf("userId");
  50.         String userId = ctx.channel().attr(key).get();
  51.         NettyConfig.getChannelMap().remove(userId);
  52.     }
  53. }

推送消息接口及实现类

  1. public interface PushMsgService {
  2.     /**
  3.      * 推送给指定用户
  4.      */
  5.     void pushMsgToOne(String userId, String msg);
  6.     /**
  7.      * 推送给所有用户
  8.      */
  9.     void pushMsgToAll(String msg);
  10. }
  1. @Service
  2. public class PushMsgServiceImpl implements PushMsgService {
  3.     @Override
  4.     public void pushMsgToOne(String userId, String msg) {
  5.         Channel channel = NettyConfig.getChannel(userId);
  6.         if (Objects.isNull(channel)) {
  7.             throw new RuntimeException("未连接socket服务器");
  8.         }
  9.         channel.writeAndFlush(new TextWebSocketFrame(msg));
  10.     }
  11.     @Override
  12.     public void pushMsgToAll(String msg) {
  13.         NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));
  14.     }
  15. }

测试

c5327cb7b28250619a0ed530f37d349b.png

链接服务器

e85fa1a622b74cef9b7464dda1487930.png

发送消息

cfb71e3d1e6cd19699f273e7bacb6a93.png d92f82d1bdc74e5253973c0cf4143f27.png

调用接口,往前端推送消息!

c2619262f38b76b109994cad94e52b12.png

OK!

一个简单的 netty 小栗子就完成了。

源码地址:https://gitee.com/dugt/springboot-netty-demo


1. 回复“m”可以查看历史记录;

2. 回复“h”或者“帮助”,查看帮助;

   开发者已开通多个技术群交流学习,请加若飞微信:1321113940  (暗号k)进开发群学习交流

  说明:我们都是开发者。视频或文章来源于网络,如涉及版权或有误,请您与若飞(1321113940)联系,将在第一时间删除或者修改,谢谢!

6349fa2839be5115231abbed4f657e17.png

开 发 者 : KaiFaX

面向全栈工程师的开发者
专注于前端、Java/Python/Go/PHP的技术社区

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/973871
推荐阅读
相关标签
  

闽ICP备14008679号