在springboot项目中集成Netty进行消息发送和接收
概述
本文通过一个简单的 demo 来介绍 Netty 在 spring boot 项目中的使用,其中包括了服务器端和客户端的启动代码,客户端向服务器端发送文本消息。 maven 依赖 io.netty netty-all 4.1.76.Final 关键点服务器端在启动的时候开放一个端口:19080 客户端在启动的时候通过 ip 和 端口连上服务器端 客户端和服务器端都通过 Channel 对象向彼此发送数据 服务器和客户端都通过继承 ChannelInboundHandlerAdapter 类实现对消息的读取和回写等操作 服务器和客户端都通过 StringDecoder 和 StringEncoder 实现对消息的解码和转码操作 服务器和客户端启动的时候都会阻塞当前线程,因此需要在一个单独的线程中进行启动 消息发送的例子本例是一个 spring boot web 项目,项目占用了 8080 端口 服务器端在启动的时候开放 19080 端口( 注意不要和 web 端口冲突了 ) 客户端在启动的时候连上服务器端 通过 web api 向客户端发送数据,客户端再通过 Channel 对象向服务器端发送数据 服务器接收到客户端数据后也通过 Channel 对象向客户端发送数据 server 服务器端通过 @PostConstruct 注解的方法进行启动,具体如下 package com.ckjava.test.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.net.InetSocketAddress; import java.util.concurrent.ForkJoinPool; @Slf4j @Component public class HelloWorldServer { private static final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private static final EventLoopGroup workerGroup = new NioEventLoopGroup(); public void startServer(int port) { try { ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer() { protected void initChannel(SocketChannel ch) throws Exception { // ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); ch.pipeline().addLast("decoder", new StringDecoder()); ch.pipeline().addLast("encoder", new StringEncoder()); ch.pipeline().addLast(new HelloWorldServerHandler()); } }).option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 绑定端口,开始接收进来的连接 sbs.bind(port).addListener(future -> { log.info(String.format("服务器启动成功,并监听端口:%s ", port)); }); } catch (Exception e) { log.error("启动 netty 服务器端出现异常", e); } } // 服务器端启动,并绑定 19080 端口 @PostConstruct public void init() { ForkJoinPool.commonPool().submit(() -> startServer(19080)); } @PreDestroy public void destroy() { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } 服务器端 HelloWorldServerHandler 如下 package com.ckjava.test.server; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; @Slf4j public class HelloWorldServerHandler extends ChannelInboundHandlerAdapter { // 服务器端读取到 客户端发送过来的数据,然后通过 Channel 回写数据 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { log.info(String.format("服务器端读取到从客户端:%s 发送过来的数据:%s", ctx.channel().remoteAddress(), msg.toString())); ctx.channel().writeAndFlush(String.format("server write:%s", msg)); } // 捕获到异常的处理 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } client 客户端通过 @PostConstruct 注解的方法进行启动,具体如下 package com.ckjava.test.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ForkJoinPool; @Slf4j @Component public class HelloWorldClient { static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); private final EventLoopGroup group = new NioEventLoopGroup(); private ChannelFuture mChannelFuture = null; private final ThreadLocal mChannel = new ThreadLocal<>(); public void startClient(String host, int port) { // Configure the client. try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("decoder", new StringDecoder()); p.addLast("encoder", new StringEncoder()); p.addLast(new HelloWorldClientHandler()); } }); mChannelFuture = b.connect(host, port).addListener(future -> { log.info(String.format("客户端启动成功,并监听端口:%s ", port)); }); } catch (Exception e) { log.error("启动 netty 客户端出现异常", e); } } /** * 客户端通过 Channel 对象向服务器端发送数据 * @param data 文本数据 */ public void send(String data) { try { if (mChannel.get() == null) { mChannel.set(mChannelFuture.channel()); } mChannel.get().writeAndFlush(data); } catch (Exception e) { log.error(this.getClass().getName().concat(".send has error"), e); } } // 客户端启动,并连上服务器端 @PostConstruct public void init() { ForkJoinPool.commonPool().submit(() -> startClient("127.0.0.1", 19080)); } @PreDestroy public void destroy() { group.shutdownGracefully(); } } 客户端 HelloWorldClientHandler 实现如下 package com.ckjava.test.client; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; @Slf4j public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter { // 客户端激活监听 @Override public void channelActive(ChannelHandlerContext ctx) { log.info("客户端激活!"); } // 客户端读取从服务器端发送过来的消息 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { log.info(String.format("客户端读取从服务器端发送过来的数据:%s", msg)); } // 捕获异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } web api 数据发送入口这里只是通过 package com.ckjava.test.web; import com.ckjava.test.client.HelloWorldClient; import io.swagger.annotations.Api; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author ckjava * @date 2022/4/18 23:50 */ @Api @RequestMapping(produces = "application/json;charset=utf-8") @RestController public class HelloNettyCtrl { @Resource private HelloWorldClient mHelloWorldClient; @GetMapping("/nettyClient") public void nettyClient(@RequestParam String data) throws Exception { mHelloWorldClient.send(data); } } 测试执行如下请求 curl -X GET "http://localhost:8080/nettyClient?data=%E4%BD%A0%E5%A5%BD%20ckjava" -H "accept: application/json;charset=utf-8" 输出如下 22:36:00.178 [nioEventLoopGroup-4-1] INFO c.c.t.server.HelloWorldServerHandler - 服务器端读取到从客户端:/127.0.0.1:9196 发送过来的数据:你好 ckjava 22:36:00.178 [nioEventLoopGroup-2-1] INFO c.c.t.client.HelloWorldClientHandler - 客户端读取从服务器端发送过来的数据:server write:你好 ckjava
俄罗斯研发猴痘病毒检测盒已完成实验室测试俄罗斯矢量病毒学与生物技术国家科学中心(下称矢量中心)科研人员研制出了一种自动操作的检测试剂盒(下称检测盒),可用于检测包括猴痘病毒在内的正痘病毒。针对该检测盒的实验室测试已顺利完
nssm,一个可以把任何exe注册为系统服务的利器在Windows下把一个exe可执行文件注册为系统服务有很多种方法,常用的有sc命令instsrvsrvany以及本文重点介绍的nssm。引言这阵子为单位编写了一个小工具,作用是禁
是时候放弃燃油车?小鹏P5给出了答案中国从不缺乏造车狂人。早年间,李书福曾放出豪言,称汽车就是两排沙发加四个轮子。人们对此一笑了之,却万万没想到,20年后吉利长城比亚迪变身为国人的骄傲。今天,老剧本正在重演。有人讽刺
大型超市为何出现关闭潮?大家有没有留意到,最近上超市购物的频率没有以前那么多了,日常生活都是社区团购比较多,周边的大型商场人流越来越少了,没有以前那种繁华的景象了。商业变幻时代沉浮,随着互联网电商新零售的
永辉超市全渠道数字化转型策略打造科技永辉科学技术是第一生产力,科技的飞速发展给人类经济发展带来了极大的推动。永辉超市在今年九月提出科技永辉战略,坚定执行全渠道数字化转型策略,通过数字化手段,逐步实现三个30的落地,分别是
建筑行业数字化转型用互联网思维数字化理念重新定义建筑产品建筑业是一个古老的行业,同时又是一个崭新的行业,新时代背景下,互联网大数据云计算BIM技术,都在不断的冲击着建筑业,这是一个很缺数据的行业,如何乘上数字化的东风,在新时代让新建筑变
typescript学习15对象类型JavaScript里面使用对象来传递数据,TypeScript里面称这个为对象类型。对象类型可以使用三种方式来定义1。匿名方式2。interface类型3。别名方式匿名方
办移动套餐我被贷款3年编辑同志2021年6月30号,我去移动公司营业厅办个副卡,营业员给我推了一个98元购机套餐,当时只说3年不能换号码,是和包支付。要不是我去年要贷款根本不知道是一笔贷款,还分期每月从
华人首富赵长鹏,真的一夜返贫了吗?有行业人士认为,币圈暴涨暴跌是常态,这也导致赵长鹏的身价波动较大。而在全球对加密货币严监管以及美联储加息的背景下,不排除未来赵长鹏身价再度大幅缩水。返贫了(Pooragain)。5
无人叉车4。9万起,中力智能搬运新模式导入成本有多低中力致力于打造全流程全方位全面数字化的物料搬运元宇宙。文叶家淇近期,浙江中力股份有限公司(以下简称中力)对外发布了场内物流五大新模式及智金刚移动机器人新品,旨在通过高效的人机协同模
你知道2001年最赚钱的互联网项目竟是移动梦网吗?本文来自浪潮三十年之第八章2001惊天逆转未经容许请勿转载2000年9月以来,中国互联网产业正在度过一个有史以来最寒冷的冬天。与此同时,移动热浪正在席卷中国,小灵通的出现让移动通讯