他 大本文主要讲一下Netty服务器的工作原理以及编写方式。
Netty是一种NIO(同步非阻塞)形式的服务器,这种方式有什么好处呢?
最大的好处莫过于NIO是零拷贝的,也就是说数据可以直接从IO读至应用程序所开辟的内存中,因此Netty可以用于开发高性能的服务端。
NIO与其他形式IO的对比:
需要理解 同步,异步,阻塞,非阻塞的概念
![cf12c1f3f2d6e6a913679fa4ba1837cd.png](https://i-blog.csdnimg.cn/blog_migrate/2e92e0e70328081cd03542b104761bad.jpeg)
![f80ce617723714e76bd6bf4c4cfdeb63.png](https://i-blog.csdnimg.cn/blog_migrate/488fabaa27f133760555296e381e89d6.jpeg)
同步和异步是针对通讯方式,而阻塞非阻塞是针对线程的等待状态。
BIO:Block IO,同步阻塞,不发处理效率比较低,线程之间通信耗时也比较久,比较依赖网速和带宽。
![13620b334b8c67e230512cc35c935b6b.png](https://i-blog.csdnimg.cn/blog_migrate/095ad91e9ff0309cd19add37e944b221.jpeg)
NIO:Non-Block IO, 同步非阻塞
![28a47b61cc6699da09641cd1fa56d350.png](https://i-blog.csdnimg.cn/blog_migrate/704da33df19a4260675eeae4cdf311c0.jpeg)
了解完这些之后,开始试图理解Netty的原理。
在使用NIO时,它的类库比较复杂,需要具备较为深厚的多线程编程功底,对新手不用好,而且原生NIO中存在bug,而且在客户端会产生断线重连,网络不稳定的问题。
所以采用Netty,原因在于其API简单。
Netty有三种线程模型:
(1)单线程模型
![748f58bb1dd512674acec94c5fb2faa3.png](https://i-blog.csdnimg.cn/blog_migrate/6f863572daf84271d0c44099d0a5a591.jpeg)
(2)多线程模型(并发量更高)
![dda69e463b48852a1db403632e7ba786.png](https://i-blog.csdnimg.cn/blog_migrate/3fde392460ccfbf456f180230d3f1bb1.jpeg)
(3)主从线程模型:
![3e0bbc07d45e58a2df343660de38b9f8.png](https://i-blog.csdnimg.cn/blog_migrate/ac88ce66e987556e6e76362cf3f4a7e2.jpeg)
第三种是最常见的,具体操作步骤如下:
(1)构建一对主从线程组
(2)定义服务器的启动类
(3)为服务器设置Channel
(4)设置处理从线程池的助手类初始化器
(5)监听启动和关闭服务器
在IDEA中开始书写:
新建Maven项目,在mvn中央仓库中找Netty的pom文件(选择4.1.25版本),引入:
<groupId>com.imooc</groupId>
<artifactId>netty-server</artifactId>
<version>1.0-SNAPSHOT</version>
新建NettyServer启动类:(定义服务器所需要的各个模块)
//功能: 实现客户端发送一个请求,服务器会返回hello netty
public class HelloServer {
public static void main(String[] args) throws InterruptedException {
// 定义一对线程组
//主线程组 利用NIO的线程组new出来 用于接收客户端的连接,但是不做任何处理
EventLoopGroup bossGroup = new NioEventLoopGroup();
//从线程组 让线程组去做相应的任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//服务端的启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//将bossGroup和workerGroup丢进来,在server中设置两个线程组
//将channel的类型设置为NIO双向通道
//设置从线程组的助手类处理器,用于处理workGroup
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new HelloServerInitializer());
//启动server: server绑定端口号,sync方法设置为同步的方式,等待8088端口启动完毕
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
//设置关闭对应的的监听 sync设置为同步的方式
channelFuture.channel().closeFuture().sync();
} finally {
//用优雅的方式关闭线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
接着设置Channel的初始化器:
![a1486d00743248cf4c6b814500730fb4.png](https://i-blog.csdnimg.cn/blog_migrate/8d0256c58c9fe5404fe091fd62dfa1e8.jpeg)
//功能:初始化器,channel注册之后,会执行里面相应的初始化方法(也就是将handler逐一添加)
//socketChannel适用于通信的channel类型
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {
//添加handler
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 通过SocketChannel需获取对应的管道
ChannelPipeline pipeline = channel.pipeline();
//将自定义的和netty所提供的handler都添加至pipeline中
//HttpServerCodec是netty自己提供的助手类,可以理解为拦截器(第一个参数为可以自己命名的助手类名字)
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
//添加自定义的助手类,返回"hello netty~"
pipeline.addLast(":customHandler", new CustomHandler());
}
}
自定义Handler助手类:
//功能:创建自定义助手类
//SimpleChannelInboundHandler: 对于请求来讲,其实相当于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> {
//功能:从缓冲区中读数据 (ChannelHandlerContext为channel的上下文对象)
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//获取channel
Channel channel = ctx.channel();
if(msg instanceof HttpRequest){
//打印显示客户端远程的地址
System.out.println(channel.remoteAddress());
//定义发送内容消息(通过缓冲区) 将数据写到缓冲区
ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8);
//构建一个HttpResponse 参数:(1)http版本号 (2)状态(例如200,404)(3)相应的内容
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
//功能:为响应增加数据类型和长度
//返回textplain即可,因为是返回字符串
//返回可读的长度
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
//把response写入缓冲区中并刷到客户端
ctx.writeAndFlush(response);
}
}
启动后,在浏览器中访问localhost:8090,即可访问到hello Netty。