文章目录

  • 1 新建springboot项目引入jar包
  • 2 创建ServerBootstrap Netty服务端启动类
  • 3 自定义channel初始化设置
  • 4 自定义I/O数据读写处理类
  • 5 启动Netty服务端
    • 5.1 TCP连接测试
    • 5.2 HTTP连接测试

1 新建springboot项目引入jar包

本次采用jdk1.8、springboot2.7.5、Netty4.1.84.Final版本

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- netty依赖 springboot2.x自动导入版本 --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId></dependency>

2 创建ServerBootstrap Netty服务端启动类

主要包括以下几步:

  1. 创建NioEventLoopGroup线程组
  2. 创建ServerBootstrap辅助启动类
  3. 设置group线程组
  4. 设置channel通道的类型
  5. 初始化通道,这一步可以设置连接类型,编解码信息及自定义请求处理器
  6. 绑定端口并启动
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;/*** @author lics* @version 1.0.0* @date 2022/10/31 15:14*/
public class MyNettyServer {public void startNettyServer(int... ports) {/** 配置服务端的NIO线程组* NioEventLoopGroup 是用来处理I/O操作的Reactor线程组* bossGroup:用来接收进来的连接,workerGroup:用来处理已经被接收的连接,进行socketChannel的网络读写,* bossGroup接收到连接后就会把连接信息注册到workerGroup* workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍*/EventLoopGroup bossGroup = new NioEventLoopGroup(2);EventLoopGroup workerGroup = new NioEventLoopGroup();try {// ServerBootstrap 是一个启动NIO服务的辅助启动类ServerBootstrap serverBootstrap = new ServerBootstrap();// 设置group,将bossGroup, workerGroup线程组传递到ServerBootstrapserverBootstrap = serverBootstrap.group(bossGroup, workerGroup);// ServerSocketChannel是以NIO的selector为基础进行实现的,用来接收新的连接,这里告诉Channel通过NioServerSocketChannel获取新的连接serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);// 初始化通道,主要用于网络I/O事件,记录日志,编码、解码消息serverBootstrap = serverBootstrap.childHandler(new MyNettyChannelInitializer());// 绑定端口,同步等待成功ChannelFuture[] channelFutures;ChannelFuture futureTcp;if (ports.length > 0) {channelFutures = new ChannelFuture[ports.length];int i = 0;for (Integer port : ports) {// 绑定端口,同步等待成功 绑定的服务器futureTcp = serverBootstrap.bind(port).sync();channelFutures[i++] = futureTcp;futureTcp.addListener(future -> {if (future.isSuccess()) {System.out.println("netty server 启动成功!" + port);} else {System.out.println("netty server 启动失败!" + port);}});}for (ChannelFuture future : channelFutures) {// 等待服务器监听端口关闭future.channel().closeFuture().sync();}}} catch (InterruptedException e) {e.printStackTrace();} finally {// 退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

3 自定义channel初始化设置

继承ChannelInitializer<Channel>类,重写initChannel()方法

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;/*** 通道初始化** @author lics* @version 1.0.0* @date 2022/10/31 15:16*/
public class MyNettyChannelInitializer extends ChannelInitializer<Channel> {@Overrideprotected void initChannel(Channel channel) {/*** 处理TCP请求*/// ChannelOutboundHandler,依照逆序执行channel.pipeline().addLast("encoder", new StringEncoder());// 属于ChannelInboundHandler,依照顺序执行channel.pipeline().addLast("decoder", new StringDecoder());// 自定义TCP请求的处理器channel.pipeline().addLast(new TcpRequestHandler());/*** 下面代码是处理HTTP请求,测试时请把上面的TCP请求设置注释掉*/// HTTP协议解析,用于握手阶段;此处放开则会处理HTTP请求,TCP请求不会处理channel.pipeline().addLast(new HttpServerCodec());channel.pipeline().addLast(new HttpObjectAggregator(1024 * 1024 * 100));// 自定义解析HTTP请求处理器channel.pipeline().addLast(new HttpRequestHandler());}}

4 自定义I/O数据读写处理类

TCP请求,创建TcpRequestHandler类,继承ChannelInboundHandlerAdapter,重写里面的方法,channelRead()方法读取客户端传入的数据

import java.net.InetSocketAddress;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;/*** I/O数据读写处理类 TCP连接** @author lics* @version 1.0.0* @date 2022/10/31 15:17*/
@Slf4j
public class TcpRequestHandler extends ChannelInboundHandlerAdapter {/*** 从客户端收到新的数据时,这个方法会在收到消息时被调用** @param ctx* @param msg*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("channelRead: " + msg.toString());//回应客户端ctx.write("I got it");}/*** 从客户端收到新的数据、读取完成时调用** @param ctx*/@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {System.out.println("channel Read Complete");ctx.flush();}/*** 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时** @param ctx* @param cause*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {System.out.println("exceptionCaught");cause.printStackTrace();ctx.close();//抛出异常,断开与客户端的连接}/*** 客户端与服务端第一次建立连接时 执行** @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);ctx.channel().read();InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = insocket.getAddress().getHostAddress();//此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接System.out.println("channelActive: " + clientIp + ctx.name());}/*** 客户端与服务端 断连时 执行** @param ctx* @throws Exception*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {super.channelInactive(ctx);InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = insocket.getAddress().getHostAddress();ctx.close(); //断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机System.out.println("channelInactive: " + clientIp);}/*** 服务端当read超时, 会调用这个方法** @param ctx* @param evt* @throws Exception*/@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {super.userEventTriggered(ctx, evt);InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();String clientIp = insocket.getAddress().getHostAddress();ctx.close();//超时时断开连接System.out.println("userEventTriggered: " + clientIp);}@Overridepublic void channelRegistered(ChannelHandlerContext ctx) {System.out.println("channelRegistered");}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) {System.out.println("channelUnregistered");}@Overridepublic void channelWritabilityChanged(ChannelHandlerContext ctx) {System.out.println("channelWritabilityChanged");}}

HTTP请求,创建HttpRequestHandler类,继承SimpleChannelInboundHandler<FullHttpRequest>,重写channelRead0()方法

import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;/*** HTTP连接请求处理** @author lics* @version 1.0.0* @date 2022/11/2 11:03*/
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) {String uri = fullHttpRequest.uri();System.out.println(uri);switch (fullHttpRequest.method().name()) {case "GET":processGetRequest(fullHttpRequest);break;case "POST":if (fullHttpRequest.headers().get("Content-Type").contains("x-www-form-urlencoded")) {processPostFormRequest(fullHttpRequest);} else if (fullHttpRequest.headers().get("Content-Type").contains("application/json")) {processPostJsonRequest(fullHttpRequest);}break;default:}ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer();buf.writeCharSequence("sucess", StandardCharsets.UTF_8);FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);response.headers().set("Content-Type","application/json;charset=UTF-8");response.headers().set("Content-Length",response.content().readableBytes());channelHandlerContext.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}/*** 处理get请求* @param request*/private void processGetRequest(FullHttpRequest request){QueryStringDecoder decoder = new QueryStringDecoder(request.uri());Map<String, List<String>> params = decoder.parameters();params.forEach((key, value) -> System.out.println(key + " ==> "+ value));}/*** 处理post form请求* @param request*/private void processPostFormRequest(FullHttpRequest request){HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);List<InterfaceHttpData> httpDataList = decoder.getBodyHttpDatas();httpDataList.forEach(item -> {if (item.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute){Attribute attribute = (Attribute) item;try {System.out.println(attribute.getName() + " ==> " + attribute.getValue());}catch (Exception e){e.printStackTrace();}}});}/*** 处理post json请求* @param request*/private void processPostJsonRequest(FullHttpRequest request){ByteBuf content = request.content();byte[] bytes = new byte[content.readableBytes()];content.readBytes(bytes);JSONObject jsonObject = JSONObject.parseObject(new String(bytes));jsonObject.getInnerMap().forEach((key,value) -> System.out.println(key + " ==> " + value));}
}

5 启动Netty服务端

import com.primeton.netty.server.MyNettyServer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;/*** 启动类** @author lics* @version 1.0.0* @date 2022/10/31 15:17*/
@EnableAsync
@SpringBootApplication
public class NettyApplication implements CommandLineRunner {public static void main(String[] args) {SpringApplication app = new SpringApplication(NettyApplication.class);app.run(args);}@Async@Overridepublic void run(String... args) {/** 使用异步注解方式启动netty服务端服务*/new MyNettyServer().startNettyServer(5678,8888);}}

启动成功后,可以看到控制台会打印两行输出

5.1 TCP连接测试

使用客户端连接工具进行连接测试

发送 ‘Hello world’ 数据,控制台打印并发送 ‘I got it’ 到客户端

TCP/UDP连接工具下载
链接:https://pan.baidu.com/s/1YMg602U9xPgGVsFZx5zVaw
提取码:144t

5.2 HTTP连接测试

这里进行HTTP连接测试时,要把MyNettyChannelInitializer类中有关TCP连接的代码注掉,否则会连接失败


到此,spring boot整合Netty完成,后面将根据这个demo去跟踪学习Netty的源码,喜欢可以关注一下,大家一起学习,共同进步。

【Netty源码系列(一)】SpringBoot整合Netty实现多端口绑定相关推荐

  1. Netty源码分析第1章(Netty启动流程)----第4节: 注册多路复用

    Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用 Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channe ...

  2. 死磕源码系列【springboot项目打印is not eligible for getting processed by all BeanPostProcessors (for example: n

    标题上打印的日志是info级别的,不影响程序正常使用,但是有代码洁癖的我还是看着不爽,总想找出问题所在,查了很久知道了是IOC容器注册Bean的时候使用到了还未注册到IOC容器中的Bean,也就是某一 ...

  3. 源码系列(Spring/SpringBoot/HashMap)

    SpringBoot源码 效果:加入springBoot注解后,在run方法中传入application.class就能实现自动启动并匹配到对象的controller返回结果. 导入Spring依赖 ...

  4. 死磕源码系列【springboot之ConditionEvaluationReport记录报告和日志条件评估详情源码解析】

    ConditionEvaluationReport用来记录自动化配置过程中条件匹配的详细信息及日志信息: 1.ConditionOutcome类输出条件匹配及日志信息 public class Con ...

  5. Netty源码解读(一)概述

    感谢网友[黄亿华]投递本稿. Netty和Mina是Java世界非常知名的通讯框架.它们都出自同一个作者,Mina诞生略早,属于Apache基金会,而Netty开始在Jboss名下,后来出来自立门户n ...

  6. Netty源码分析第7章(编码器和写数据)----第2节: MessageToByteEncoder

    Netty源码分析第7章(编码器和写数据)---->第2节: MessageToByteEncoder Netty源码分析第七章: Netty源码分析 第二节: MessageToByteEnc ...

  7. 源码解读_入口开始解读Vue源码系列(二)——new Vue 的故事

    作者:muwoo 转发链接:https://github.com/muwoo/blogs/blob/master/src/Vue/2.md 目录 入口开始解读Vue源码系列(一)--造物创世 入口开始 ...

  8. Springboot 整合 Netty 实战(附源码)

    作者:pjmike_pj juejin.im/post/5bd584bc518825292865395d 前言 这一篇文章主要介绍如何用Springboot 整合 Netty,由于本人尚处于学习Net ...

  9. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  10. Netty源码分析系列之常用解码器(下)——LengthFieldBasedFrameDecoder

    扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,Spring源码分析和Java并发编程文章. 前言 在上一篇文章中分析了三个比较简单的解码器,今天接着分析最后一个常用的解码器:Leng ...

最新文章

  1. android 对比win10 耗电,win10系统下像何查看耗电的应用程序
  2. ACM入门之【并查集】
  3. acme云服务器生成证书_使用 acme.sh 申请 SSL 证书并且定期自动更新
  4. Git远程分支的回退
  5. ASP.NET Core开发-Docker部署运行
  6. python interactive window_如果PySide应用程序是从IPython interactive cons运行的,上下文菜单就会消失...
  7. java jna jni_JNA, Java Native开发利器
  8. 电脑两边黑边怎么还原_Mac电脑录制的视频有黑边?如何解决
  9. linux中如何改IP
  10. BZOJ 1016: [JSOI2008]最小生成树计数( kruskal + dfs )
  11. 在mac上安装python版的hanlp/JPype1
  12. kubernetes证书配置相关
  13. HDU 2042 不容易系列之二
  14. samsung q1u android,奢华配置指纹科技 三星Q1U再造UMPC王者
  15. 1324. Print Words Vertically**
  16. FlashVml2.0(WEB上的PhotoShop+Flash、VML最强开发工具)[推荐]
  17. 使用pyBigWig模块查看bigwig文件中的内容
  18. 图像处理: 设计 自定义透明度 水印
  19. 织梦网站木马生成一个php文件夹,dede织梦程序网站安全设置防范木马侵袭
  20. 花园体育馆计算机房音乐教室的英语,四年级英语下册pep知识点复习.ppt

热门文章

  1. 利用接口(vue等)调用thinkphp6(tp6)验证码验证结果总是失败的解决方案
  2. 典型无线复习资料---有这不怕挂科
  3. cesium添加单张图片
  4. 转:时域错误隐藏个人理解_Phinex的博客_雅虎博客_雅虎空间
  5. 主机访问虚拟机Web服务器
  6. 前端传参日期只有年月日,后台给日期设置23时59分59秒和0时0分0秒
  7. redis集群和redis宕机处理方案
  8. linux 查看syn网络日志,Linux下分析SYN flood攻击案例
  9. CSGO常用地图实体列表
  10. 荣耀笔记本linux版硬盘分区,荣耀MagicBook硬盘分区详细教程