Netty
Netty是基于Java NIO的网络应用框架。
Netty是一个NIO client-server(客户端-服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供一种新的方式来开发网络应用程序,这种新的方式是的它很容易使用和有很强的扩展性。
Netty的内部实现是复杂的,但是Netty提供了简单一用的api从网络处理代码中解耦业务逻辑。
Netty是完全基于NIO实现的,所以整个Netty都是非阻塞的。
网络应用程序通常需要较高的可扩展性,无论是Netty还是其他基于JAVA NIO的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性,本章将讨论同步(阻塞)和异步(非阻塞)的IO来说明为什么使用异步代码来解决扩展性问题以及如何使用异步。
在学习Netty之前,先回顾一下NIO的通信步骤:
- 创就绪建ServerSocketChannel,为它配置非阻塞模式。
- 绑定监听,配置TCP参数,录入backlog大小
- 创建一个独立的IO线程,用于轮询多路复用器Selector
- 创建Selector,将之前创建的ServerSocketChannel注册到Selector上,并设置监听标志位SelectionKEY.ACCEPT
- 启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的通道。
- 当轮询到了处于就绪的通道时,需要进行判断操作位,如果是ACCEPT状态,说明是新的客户端接入,则调用accept方法接受新的客户端。
- 设置新接入客户端的一些参数,如非阻塞、并将其通道继续注册到Selector之中,设置监听标志位等。
- 如果轮询的通道操作位是READ,则进行读取,构造Buffer对象等。
- 更细节的还有数据没法送完成继续发送的问题。
Netty实现通信的步骤:
- 创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信读写。
- 创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等等。
- 创建一个实际处理数据的类ChannelInitalizer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式、已经实际处理数据的接口。
- 绑定端口,执行同步阻塞方法等待服务器启动即可。
Client.java
public class Client {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup workgroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workgroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture cf1 = b.connect("127.0.0.1",8765).sync();
// ChannelFuture cf2 = b.connect("127.0.0.1",8764).sync();
//buf
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes()));
Thread.sleep(1000);
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes()));
Thread.sleep(1000);
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes()));
// cf1.channel().flush();
cf1.channel().closeFuture().sync();
workgroup.shutdownGracefully();
}
}
Server.java
public class Server {
public static void main(String[] args) throws InterruptedException {
//1 第一个线程组用于接受client端连接的
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//2 第二个线程组用于实际的业务处理操作的
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//3 创建一个辅助类Bootstrap,就是对我们的Server进行一系列的配置
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)//把两个工作线程组加入进来
.channel(NioServerSocketChannel.class)//使用NIOServerSocketChannel这种类型的通道
.childHandler(new ChannelInitializer<SocketChannel>() {//使用childHandler绑定具体的事件处理器
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ServerHandler());
}
})
/**
* 服务器端TCP内核模块维护两个队列,我们称之为A和B
* 客户端向服务器端connect的时候 会发送带有SYN标志的包(第一次握手)
* 服务器收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手)
* 此时TCP内核模块将客户端连接加入A队列中,然后服务器收到客户端发来的ACK时(第三次握手)
* TCP内核模块将客户端从A队列移动到B队列,连接完成,应用程序的accept会返回。
* 也就是说accept从B队列中取出完成三次握手的连接
* A队列和B队列的长度之和是backlog。当A、B队列的长度之和大于backlog时,新连接将会被TCP内核拒绝
* 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户无法连接。
* 要注意的是:backlog对程序的连接数并无影响,backlog影响的知识还没有被accept取出的连接。
*/
//设置TCP缓冲区
.option(ChannelOption.SO_BACKLOG, 128)
//保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定指定的端口进行监听
ChannelFuture f = b.bind(8765).sync();
// ChannelFuture f2 = b.bind(8764).sync();
//将服务器阻塞
f.channel().closeFuture().sync();
// f2.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
ServerHandler.java
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
// ((ByteBuf)msg).release();
try{
//do sth msg
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data,"utf-8");
System.out.println("server:"+request);
//写给客户端
String response = "我是反馈的消息";
ChannelFuture channelFuture = ctx.channel().writeAndFlush(Unpooled.copiedBuffer("Hi Client!".getBytes()));
//写完后断开连接
// channelFuture.addListener(ChannelFutureListener.CLOSE);
// ctx.channel().flush();
}finally{
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
ClientHandler.java
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
// ((ByteBuf)msg).release();
try{
//do sth msg
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data,"utf-8");
System.out.println("client:"+request);
//写给服务器端
/*String response = "我是反馈给服务器的消息";
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("999".getBytes()));*/
}finally{
//读数据必须释放
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}