netty案例
echoServer and echoHandler
echoClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
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.NioSocketChannel;
public class EchoClient {
public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 8080;
bootStrapClient(host, port);
}
private static void bootStrapClient(String host, int port) throws InterruptedException {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap(); // (1)
// specifies group to handle client events.
b.group(workerGroup); // (2)
// channel type is the one for nio transport
b.channel(NioSocketChannel.class); // (3)
b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
// add echoClientHandler to the pipeline when channel is created
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync(); // (5)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
echoClientHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
// this class as one whose instances can ben shared among channels
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// when notified that the channel is active, sends a message
ChannelFuture channelFuture = ctx.writeAndFlush(Unpooled.copiedBuffer("netty rocks!", StandardCharsets.UTF_8));
}
// when finish this, release automatically
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
// logs a dump of received message
System.out.println("client recieved: " + msg.toString(StandardCharsets.UTF_8));
}
// on exception logs and errors and close the channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("clientHandler exception");
ctx.close();
}
}
echoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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;
/**
* Discards any incoming data.
*/
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public void run() throws Exception {
// create the eventloop group
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//create bootstrap
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
// specify the use of an nio transport channel
.channel(NioServerSocketChannel.class) // (3)
// sets the socket address use the specified port, 下面得方式也可以
// .localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
// adds an echoServerHandler to the channel's ChannelPipeline
// ch.pipeline().addLast(new DiscardServerHandler());
ch.pipeline().addLast(new EchoServerHandler(), new EchoServerHandler2());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully().sync();
bossGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new EchoServer(port).run();
}
}
echoServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("server recieved :" + in.toString(StandardCharsets.UTF_8));
// write the recieved message to the sender without flushing the outbound message
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// flushed pending message to the remote peer and close the channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();;
ctx.close();
}
}
netty关键概念
概念与对应关系
channel-sockets
eventLoop- control flow(流程控制), multithreading(多线程) concurrency(并发)
channelFuture-- asynchronous notification(异步通知)
channel-sockets
一些基础得io操作,如bind connect read write都隐藏了底层得网络功能,Channel是一个接口,实现它得有一些常用得如
EmbeddedChannel
LocalServerChannel
NioDatagramChannel
NioSctpChannel
NioSocketChannel
eventLoop--事件处理得核心抽象,流程、多线程、同步
eventLoop是netty得处理时间得核心抽象。一个connection一生中都由一个eventLoop负责。
eventloopGroup对应eventLoop是一对多得关系
一个eventLoop生命周期内只由一个线程处理
所有得io都由一个指定得线程上得一个eventLoop处理。
channel(connection)生命周期内只由一个eventLoop绑定(也就是只有一个线程,可以避免多线程得同步等)。
但是一个eventLoop可能会有多个channel。
ChannelFuture--asynchronous notification(异步通知)
例如io或者read结束以后,就会调用得方法,例如上面得EchoServerHandler
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// flushed pending message to the remote peer and close the channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
ChannelHandler And ChannelPipeline
ChannelHandler
两个继承得接口是ChannelInboundHandler 以及ChannelOutboundHandler
ChannelPipeline
channelHandler在channelPipeline中被装载:
channelInitializer得实现类注册在serverBootStrap上
当channelInitializer.initChannel被调用时,channelInitializer就会装着这些channelHandler
channelInitializer最后将它自己从channelPipeline中移除
连接发送消息以后,从头到尾经过inboundHandler,然后到达tail,再按照添加顺序得反向走channelOutboundHandler,一直到达头部
channelHandler与channelPipeline得关系
在initializing装载过程中,会new一个ChannelHandlerContext,这个ChannelHandlerContext携带了一属性为channelHandler,实际添加得其实是ctx 及channelHandlerContext,每个channelHandler得方法中都有这个东西
两种channelHandler不同得作用
in 和out分别处理不同得事情,in主要处理一些业务逻辑,out主要作为输出是使用。
如果再netty中想要发送信息,可以直接写channel或者写channelHandlerContext(handler相关得),前者会直接从tail开始再走outbound? 前者会从下一个handler开始?
常用得ChannelHandler得netty类
Encoder 和decoder
常用得实现类包括ByteToMessageDecoder MessageToByteEncoder
部分包括 ProtobufEncoder或者ProtobufDecoder
SimpleChannelInboundHandler
重要得方法channelRead0(ChannelHandlerContext,T) T是泛型,在进入handler时会处理判断
Boostrapping
面向连接得协议Connection-oriented protocols
bootstrap分为两种,一种是客户端用得Bootstrap,一种是server端得ServerBootstrap。后者绑定端口,前者发起连接。前者只需要一个EventLoopGroup,Server端得需要两个(可以是同一个实例),一个用来接受socket监听,另外一个为实际处理所有连接得。
关于上文提到得group问题,参见下文
public void run() throws Exception {
// create the eventloop group
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//create bootstrap
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
// specify the use of an nio transport channel
.channel(NioServerSocketChannel.class) // (3)
// sets the socket address use the specified port, 下面得方式也可以
// .localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
// adds an echoServerHandler to the channel's ChannelPipeline
// ch.pipeline().addLast(new DiscardServerHandler());
ch.pipeline().addLast(new EchoServerHandler(), new EchoServerHandler2());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully().sync();
bossGroup.shutdownGracefully().sync();
}
}
Transports
种类
OIO—blocking transport
NIO—asynchronous transport
Local transport—asynchronous communications within a JVM
如果客户端使用了该协议,那么服务端也需要使用该协议
该协议是为了后续方便迁移,前期没有理由使用它。
Embedded transport—testing your ChannelHandlers
单元测试使用
API
Channel
代码层级
channel可以获取到config、pipeline等信息,辅助处理。
channel是独立得,因此channel还继承了comparable
channelConfig是在子类中包含得属性,包括channelPipeline也是
channel是线程安全得,所以可以多线程去write
channel得常用方法及说明
Method name | Description |
eventLoop | Returns the EventLoop that is assigned to the Channel.。 返回channel被分配得eventLoop |
pipeline | Returns the ChannelPipeline that is assigned to the Channel. |
isActive | Returns true if the Channel is active. The meaning of active may depend on the underlying transport. For example, a Socket transport is active once connected to the remote peer, whereas a Datagram transport would be active once it’s open. 是否活跃,如socket是否连接至远端。再如udp是否打开 |
localAddress | Returns the local SocketAddress. |
remoteAddress | Returns the remote SocketAddress. |
write | Writes data to the remote peer. This data is passed to the ChannelPipeline and queued until it’s flushed. write并不会直接刷到远端,而是放在队列中只有调用flush以后。 |
flush | Flushes the previously written data to the underlying transport, such as a Socket. |
writeAndFlush | A convenience method for calling write() followed by flush(). |
ChannelHandler
Typical uses for ChannelHandlers include:
Transforming data from one format to another
Providing notification of exceptions
Providing notification of a Channel becoming active or inactive
Providing notification when a Channel is registered with or deregistered from an EventLoop
Providing notification about user-defined events
包含得transports种类
Netty-provided transports
Name | Package | Description |
NIO | io.netty.channel.socket.nio | Uses the java.nio.channels package as a foundation—a selector-based approach. |
Epoll | io.netty.channel.epoll | Uses JNI for epoll() and non-blocking IO. This transport supports features available only on Linux, such as SO_REUSEPORT, and is faster than the NIO transport as well as fully non-blocking. |
OIO | io.netty.channel.socket.oio | Uses the java.net package as a foundation—uses blocking streams. |
Local | io.netty.channel.local | A local transport that can be used to communicate in the VM via pipes. |
Embedded | io.netty.channel.embedded | An embedded transport, which allows using ChannelHandlers without a true network-based transport. This can be quite useful for testing your ChannelHandler implementations. |
NIO
nio得状态大致分为以下几种,核心是register,多个channel注册到selector中,并注册感兴趣得事件监听,如果监听到则处理对应事件
A new Channel was accepted and is ready.
A Channel connection was completed.
A Channel has data that is ready for reading.
A Channel is available for writing data.
Name | Description |
OP_ACCEPT | Requests notification when a new connection is accepted, and a Channel is created. |
OP_CONNECT | Requests notification when a connection is established. |
OP_READ | Requests notification when data is ready to be read from the Channel. |
OP_WRITE | Requests notification when it is possible to write more data to the Channel. This handles cases when the socket buffer is completely filled, which usually happens when data is transmitted more rapidly than the remote peer can handle. |
Epoll
具有零拷贝得特性,可以避免从kernel space拷贝到user space来答复提升性能。为epoll和nio使用。
仅限linux
OIO
阻塞io,主要如一些老旧代码,再比如有一些逻辑得判断依赖等等,必须依赖阻塞io,o是old
使用场景总结
Application needs | Recommended transport |
Non-blocking code base or general starting point | NIO (or epoll on Linux) |
Blocking code base | OIO |
Communication within the same JVM | Local |
Testing ChannelHandler implementations | Embedded |