现在大多数项目都是基于spring boot进行开发,所以我们以spring boot作为开发框架来使用netty。使用spring boot的一个好处就是能给将netty的业务拆分出来,并通过spring cloud整合到项目中。

  我们以一个简单的客户端发送消息到服务的场景编写一个实例。

一、服务端模块

  netty中服务端一般分为两个类,一个是启动配置类,另一个是消息的逻辑处理类,但是首先我们要配置spring boot的启动类,启动netty

  

@SpringBootApplication
public class DemoApplication implements CommandLineRunner { @Autowired
NettyServer nettyServer; public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
} @Override
public void run(String... args) throws Exception {
nettyServer.startServer();
}
}

  1.启动配置类

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; /**
* Netty
* 服务端
*/
@Configuration
public class NettyServer { //四个处理请求的逻辑类
@Autowired
ServerInboundHandler serverInboundHandler; @Autowired
ServerInboundGetTimeHandler serverInboundGetTimeHandler; @Autowired
ServerLastOutboundHandler serverLastOutboundHandler; @Autowired
ServerOutboundHandler serverOutboundHandler; public void startServer() {
System.out.println("服务端启动成功");
//创建两个线程组,用于接收客户端的请求任务,创建两个线程组是因为netty采用的是反应器设计模式
//反应器设计模式中bossGroup线程组用于接收
EventLoopGroup bossGroup = new NioEventLoopGroup();
//workerGroup线程组用于处理任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
//创建netty的启动类
ServerBootstrap bootstrap = new ServerBootstrap();
//创建一个通道
ChannelFuture f = null;
try {
bootstrap.group(bossGroup, workerGroup) //设置线程组
.channel(NioServerSocketChannel.class) //设置通道为非阻塞IO
.option(ChannelOption.SO_BACKLOG, 128) //设置日志
.option(ChannelOption.SO_RCVBUF, 32 * 1024) //接收缓存
.childOption(ChannelOption.SO_KEEPALIVE, true)//是否保持连接
.childHandler(new ChannelInitializer<SocketChannel>() {
//设置处理请求的逻辑处理类
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//ChannelPipeline是handler的任务组,里面有多个handler
ChannelPipeline pipeline = ch.pipeline();
//逻辑处理类
pipeline.addLast(serverLastOutboundHandler);
pipeline.addLast(serverOutboundHandler);
pipeline.addLast(serverInboundHandler);
pipeline.addLast(serverInboundGetTimeHandler);
}
}); f = bootstrap.bind(84).sync();//阻塞端口号,以及同步策略
f.channel().closeFuture().sync();//关闭通道
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅退出
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} } }

  2.启动配置类中的各个组件

  1)EventLoop 与 EventLoopGroup

  EventLoop 好比一个线程,1个EventLoop 可以服务多个channel,而一个channel只会有一个EventLoop 。EventLoop 在netty中就是负责整个IO操作,包括从消息的读取、编码以及后续 ChannelHandler 的执行,这样做的好处就是避免了线程中的上下文切换时,大量浪费资源情况。

  EventLoopGroup 是负责分配EventLoop到新创建的channel,EventLoopGroup 就好比线程池,它里面包含多个EventLoop。

  2)BootStrap

  BootStrap 是netty中的引导启动类也就是一个工厂配置类,可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化,所以我们主要来看它的几个常用的配置方法。

  ① gruop() 方法

  gruop()方法用于配置netty中的线程组,也就是我们的EventLoopGroup ,在服务端中需要配置两个线程组,这是因为netty中采用的是反应器设计模式(reactor ),我们知道反应器设计模式中是需要两个线程组,一个用于接收用户的请求,另一个用于处理请求的内容。

  ② channel() 方法

  channel()方法用于配置通道的IO类型,IO类型有两个:阻塞IO(BIO)OioServerSocketChannel;非阻塞IO(NIO)NioServerSocketChannel。

  ③ childHandler () 方法

  用于设置处理请求的适配器,这个在下面详细介绍。

  ④ childOption() 方法

  给每条child channel连接设置一些TCP底层相关的属性,比如上面,我们设置了两种TCP属性,其中 ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开

  ⑤ option

  给每条parent channel 连接设置一些TCP底层相关的属性。

  关于option的属性有:

  SO_RCVBUF ,SO_SNDBUF:用于设置TCP连接中使用的两个缓存区。

  TCP_NODELAY:立即发送数据,采用的是Nagle算法。Nagle算法是当小数据过多时,就会将这些小数据碎片连接成更大的报文,从而保证发送的报文数量最小。所以如果数据量小就要禁用这个算法,netty默认是禁用的值为true。

  通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启。

  SO_KEEPALIVE:底层TCP协议的心跳机制。Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。

  SO_REUSEADDR:Socket参数,地址复用,默认值False

  SO_LINGER:Socket参数,关闭Socket的延迟时间,默认值为-1,表示禁用该功能。

  SO_BACKLOG:Socket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128。

  SO_BROADCAST:Socket参数,设置广播模式。

  3)ChannelFuture

  我们知道netty中的所有IO操作都是异步的,这意味着任何IO调用都会立即返回,不管结果如果状态如果。而ChannelFuture 的存在就是为了解决这一问题,它会提供IO操作中有关的信息、结果或状态。

  ChannelFuture 一共有两个状态:

  未完成状态:当IO操作开始时,将创建一个新的ChannelFuture 对象,此时这个对象既没有操作成功也没有失败,那么就说这个对象就是未完成的状态。简单来说未完成指创建了对象且没有完成IO操作。

  已完成状态:当IO操作完成后,不管操作是成功还是失败,future都是标记已完成的,失败时也会有对应的具体失败信息。

3.消息逻辑处理类  

  可以看到我一共在pipeline里面配置了4个handler,这是为了查看inboundhandler和outboundhandler的数据传递方式,以及每个handler的执行顺序

   ServerInboundGetTimeHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.context.annotation.Configuration; import java.text.SimpleDateFormat;
import java.util.Date; /**
* Inbound处理类
* 给客户端返回一个时间戳
*/
@Configuration
public class ServerInboundGetTimeHandler extends ChannelInboundHandlerAdapter { /**
* 获取客户端的内容类
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//将传递过来的内容转换为ByteBuf对象
ByteBuf buf = (ByteBuf) msg;
//和文件IO一样,用一个字节数组读数据
byte[] reg = new byte[buf.readableBytes()];
buf.readBytes(reg);
//将读取的数据转换为字符串
String body = new String(reg, "UTF-8");
//给客户端传递的内容,同样也要转换成ByteBuf对象
Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
String respMsg = body+ft.format(dNow);
System.out.println("服务器当前时间是:"+ft.format(dNow));
ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
//调用write方法,通知并将数据传给outboundHand
ctx.write(respByteBuf); } /**
* 刷新后才将数据发出到SocketChannel
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} /**
* 关闭
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}

  ServerInboundHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.context.annotation.Configuration; /**
* Inbound处理类,是用来处理客户端发送过来的信息
* Sharable 所有通道都能使用的handler
*/
@Configuration
@ChannelHandler.Sharable
public class ServerInboundHandler extends ChannelInboundHandlerAdapter { /**
* 获取客户端的内容类
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//将传递过来的内容转换为ByteBuf对象
ByteBuf buf = (ByteBuf) msg;
//和文件IO一样,用一个字节数组读数据
byte[] reg = new byte[buf.readableBytes()];
buf.readBytes(reg);
//将读取的数据转换为字符串
String body = new String(reg, "UTF-8");
System.out.println( "服务端接收的信息是: " + body);
//给客户端传递的内容,同样也要转换成ByteBuf对象
String respMsg = "你好我是服务端,当前时间是:";
ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
//调用fireChannelRead方法,通知并将数据传给下一个handler
ctx.fireChannelRead(respByteBuf); } /**
* 刷新后才将数据发出到SocketChannel
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} /**
* 关闭
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}

  ServerLastOutboundHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.springframework.context.annotation.Configuration; /**
* Outbound表示服务器发送的handler
*/
@Configuration
public class ServerLastOutboundHandler extends ChannelOutboundHandlerAdapter { /**
* 服务端要传递消息的方法
* @param ctx
* @param msg
* @param promise
* @throws Exception
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//将传递过来的内容转换为ByteBuf对象
ByteBuf buf = (ByteBuf) msg;
//和文件IO一样,用一个字节数组读数据
byte[] reg = new byte[buf.readableBytes()];
buf.readBytes(reg);
String body=new String(reg,"UTF-8");
String respMsg = body+"\n1.吃饭 2.睡觉";
System.out.println("服务端要发送的消息是:\n"+respMsg);
ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
ctx.write(respByteBuf);
ctx.flush(); //ctx.write()方法执行后,需要调用flush()方法才能令它立即执行
}
}

  ServerOutboundHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.springframework.context.annotation.Configuration; /**
* Outbound表示服务器发送的handler
*/
@Configuration
public class ServerOutboundHandler extends ChannelOutboundHandlerAdapter{ /**
* 服务端要传递消息的方法
* @param ctx
* @param msg
* @param promise
* @throws Exception
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//将传递过来的内容转换为ByteBuf对象
ByteBuf buf = (ByteBuf) msg;
//和文件IO一样,用一个字节数组读数据
byte[] reg = new byte[buf.readableBytes()];
buf.readBytes(reg);
String body=new String(reg,"UTF-8");
System.out.println("serverOutbound的内容:\n"+body);
String respMsg = body+"\n请问你需要操作什么任务";
ByteBuf respByteBuf = Unpooled.copiedBuffer(respMsg.getBytes());
ctx.write(respByteBuf);
ctx.flush(); //ctx.write()方法执行后,需要调用flush()方法才能令它立即执行
}
}

4.channelHandler中的各个组件

  1)channel

  channel的本质就是一个socket连接,是服务端与客户端连接的通道。channel除了连接客户端与服务端外,还能监控通道的状态,如:什么时候传输、传输完成情况都能监控到。

  channel的一个有四个状态:

  channelReistered:channel注册到一个EventLoop,此时为注册状态

  channelUnregistered:channel已经创建好了还未进行注册,此时为未注册状态

  channelActive:客户端与服务端连接后,channel会变为活跃状态,此时可以接收和发送数据

  channelInactive:非活跃状态,没有连接远程主机的时候。

  channel的生命周期状态变化大致如图:

  

   2)channelHandler

  channelHandler就是我们处理数据逻辑的地方,它一共分为两大类:InboundHandler和呕Outboundhandler。InboundHandler用于处理输入的数据和改变channel状态类型,OutboundHandler用于回写给外界的数据。

  channelHandler的执行顺序:

  InboundHandler:顺序执行

  OutboundHandler:逆序执行

  在channelHandler的执行过程中,InboundHandler会覆盖后面的OutboundHandler,所以在开发中应该先执行OutboundHandler再执行InboundHandler

  3)channelPipeline

  管理channelHandler的有序容器,它里面可以有多个channelHandler。

  channel、channelHandler、channelPipeline三者的关系:

  一个channel有一个容器channelPipeline,容器中有多个channelHandler。创建channel时会自动创建一个channelPipeline,每个channel都有一个管理它的channelPipeline,这个关联是永久的。

二、客户端代码

  netty中客户端的各个组件都是和服务端一样的,所以不用再介绍客户端的组件

  1.配置类代码

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 org.springframework.context.annotation.Configuration; import java.net.InetSocketAddress; /**
* netty 客户端类
*/
@Configuration
public class NettyClient { public static void main(String[] args) {
//客户端只需要创建一个线程就足够了
EventLoopGroup group = new NioEventLoopGroup();
try {
//客户端启动类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)//设置线程组
.channel(NioSocketChannel.class)//设置通道类型
.remoteAddress(new InetSocketAddress("127.0.0.1", 84))//设置IP和端口
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandler());
}
});
//阻塞通道
ChannelFuture channelFuture = bootstrap.connect().sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) { } finally {
group.shutdownGracefully();
}
} }

  2.逻辑处理类代码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil; /**
* 客户端逻辑处理类
*/
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> { /**
* 发送给服务器消息的方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("你好,我是客户端", CharsetUtil.UTF_8));
} /**
* 回调方法,接收服务器发送的消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println( msg.toString(CharsetUtil.UTF_8));
} /**
* 在处理过程中引发异常时被调用
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

测试结果,先启动服务端:

 

然后启动客户端:

 

最后再来看看服务端:

Netty学习第四章 spring boot整合netty的使用的更多相关文章

  1. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

  2. (转)Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理

    http://www.ityouknow.com/springboot/2017/06/26/spring-boot-shiro.html 这篇文章我们来学习如何使用 Spring Boot 集成 A ...

  3. Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理

    这篇文章我们来学习如何使用 Spring Boot 集成 Apache Shiro .安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在 Java 领域一般有 Spring S ...

  4. 从.Net到Java学习第四篇——spring boot+redis

    从.Net到Java学习系列目录 “学习java已经十天,有时也怀念当初.net的经典,让这语言将你我相连,怀念你......”接上一篇,本篇使用到的框架redis.FastJSON. 环境准备 安装 ...

  5. spring Boot 学习(四、Spring Boot与任务)

    一.异步任务 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在 处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用 多线程来完成此类任务,其实,在Spri ...

  6. kafka学习(五)Spring Boot 整合 Kafka

    文章更新时间:2020/06/08 一.创建Spring boot 工程 创建过程不再描述,创建后的工程结构如下: POM文件中要加入几个依赖: <?xml version="1.0& ...

  7. dubbo学习(十)spring boot整合dubbo

    工程搭建与配置 生产者 1.创建一个生产者的spring boot工程,配置好依赖,并把接口实现类文件夹复制到新的工程里 2.pom.xml配置dubbo的相关依赖 <!-- Dubbo Spr ...

  8. 【Spring Boot学习之三】Spring Boot整合数据源

    环境 eclipse 4.7 jdk 1.8 Spring Boot 1.5.2 一.Spring Boot整合Spring JDBC 1.pom.xml <project xmlns=&quo ...

  9. Spring Boot初识(4)- Spring Boot整合JWT

    一.本文介绍 上篇文章讲到Spring Boot整合Swagger的时候其实我就在思考关于接口安全的问题了,在这篇文章了我整合了JWT用来保证接口的安全性.我会先简单介绍一下JWT然后在上篇文章的基础 ...

随机推荐

  1. oracle em启动问题

    这种情况出现的可能性是(1)主机IP地址改变,(2)主机名改变,(3)移植到全新的主机,(4)监听程序未启动,5)oracle服务也检查一下 关于orcl的启动: emctl start dbcons ...

  2. spring 中的一些注解功能--不定更新

    1@Qualifier注解? 下面的示例将会在Customer的person属性中自动装配Person的值. public class Customer { @Autowired private Pe ...

  3. java基础笔记1--关于线程死锁

    关于线程死锁 什么是死锁: 在编写多线程的时候,必须要注意资源的使用问题,如果两个或多个线程分别拥有不同的资源, 而同时又需要对方释放资源才能继续运行时,就会发生死锁. 简单来说:死锁就是当一个或多个 ...

  4. Failure to find com.oracle:ojdbc6:jar:11.2.0.1.0

    报错原因:oracle的ojdbc.jar是收费的,maven的中央仓库是没有的,需要下载到本地,然后打包进maven仓库 1.下载ojdbc6-11.2.0.1.0.jar包 http://cent ...

  5. Python Requests post方法中data与json参数问题

    1.data参数 你想要发送一些编码为表单形式的数据——非常像一个 HTML 表单.要实现这个,只需简单地传递一个字典给 data 参数.你的数据字典在发出请求时会自动编码为表单形式,header默认 ...

  6. 深入理解java:2.3. 并发编程 java.util.concurrent包

    JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...

  7. [Web 前端] 024 js 的定时器及函数

    1. Javascript 定时器 1.1 计时事件 设定一个间隔,时间到了后准时执行代码,此为"计时事件" 1.2 作用 1.制作动画 2.异步操作 1.3 定时器的类型及语法 ...

  8. 数位dp相关

    经典的数位Dp是要求统计符合限制的数字的个数. 一般的形式是:求区间[n,m]满足限制f(1). f(2). f(3)等等的数字的数量是多少. 条件 f(i) 一般与数的大小无关,而与数的组成有关. ...

  9. P1067多项式输出

    这道题是2009普及组的题,仍然是一个字符串+模拟.(蒻到先不刷算法) 这道题的题干给了很多的提示,也很全面,但是当我把种种情况都考虑到了后,在写代码的过程中仍然出现了很多的错误,wa了三四次.其实导 ...

  10. Python 入门之代码块、小数据池 与 深浅拷贝

    Python 入门之代码块.小数据池 与 深浅拷贝 1.代码块 (1)一个py文件,一个函数,一个模块,终端中的每一行都是代码块 (代码块是防止我们频繁的开空间降低效率设计的,当我们定一个变量需要开辟 ...