Netty简单的服务器客户端通信
Netty是一个基于Java NIO的异常网络通信框架。Netty充分利用了Java NIO异常的特点,减少了线程资源的消耗,并且还对缓冲做了复用,大大节约了内存。无论是方便、轻量还是安全方面,Netty在Java网络编程框架中都是首屈一指的,基于Netty进行二次开发网络应用相当的方便,无数的个人企业应用已经证实了这一点。
下面是最经典的服务器客户端通信实例:
package study.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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;
public class NettyEchoServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
ChannelFuture future = bootstrap.bind(8888);
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class EchoChannelHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = (ByteBuf) msg;
byte[] bts = new byte[buffer.readableBytes()];
buffer.readBytes(bts);
String str = new String(bts, "utf-8");
System.out.println("server receive:" + str);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoChannelHandler());
}
}
}
package study.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
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 NettyEchoClient {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888);
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
private static class EchoClientHandler extends ChannelHandlerAdapter {
private ByteBuf msgBuf;
public EchoClientHandler() {
byte[] msgBts = "please connect".getBytes();
msgBuf = Unpooled.buffer(msgBts.length);
msgBuf.writeBytes(msgBts);
}
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(msgBuf);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
}
}
}
在NettyEchoServer的main方法中,首先创建了两个EventLoopGroup,然后创建一个服务器起动器ServerBootstrap,注册channel的类型是NioServerSocketChannel,NioServerSocketChannel就相当于NIO中的ServerSocketChannel,然后将ServerBootstrap绑定到端口8888,获取Channel的closeFuture,调用同步方法,这里主线程就阻塞在这里,而后台线程就开始监听连接。在前面还将ServerBootstrap的childHandler设置成一个ChildChannelHandler对象,ChildChannelHandler实际上就是一个Channel的初始化器,也就相当于如果有一个连接到来,就会创建一个SocketChannel,Channel初始化器就会对这个SocketChannel进行处理。在这个Channel初始化器的初始化方法中,调用了ch.pipeline().addLast(new EchoChannelHandler())方法,这里其实是给这个连接的SocketChannel的处理队列中添加一个处理器,也就是代码中的pipeLine,在这个pipeLine的末尾添加,这样,当SocketChannel有事件发生时,就可以回调处理器的响应方法了。这就是Netty基于事件回调的机制。然后EchoChannelHandler继承于ChannelHandlerAdapter,重写了channelRead,channelReadComplete以及exceptionCaught方法,channelRead就是SocketChannel的读事件回调,这样,就可以在读事件回调方法中写相应逻辑了,上面的代码中是直接把Buffer中的数据读取出来并打印到控制器。
客户端的代码也类似,创建一个EventLoopGroup,然后创建一个客户端的启动器,启动器注册NioSocketChannel,然后注册初始化器,这个初始化器就是当连接到服务器时就会产生一个SocketChannel,在初始化方法中,给这个SocketChannel的处理队列中添加处理器,这个处理器也相当于一个监听器,监听到SocketChannel的事件。在处理器中的channelActive方法中,这个方法表示连接完成,上面的代码是向代码器写了一个字符串,服务器就可以在SocketChannel的处理器的channelRead回调方法中得到这个字符串。
由于Netty把NIO封装得很方便,所以上面的代码看起来非常简洁,这也是为什么Netty这么受欢迎的一部分原因。方便简洁与高效是一个框架的根本。用Nety可以很方便的实现一些网络业务功能,可以用Netty二次开发一个通信协议,更加一步可以实现一个服务器程序。Netty把Java NIO封装得我们可以不去关注网络异步通信的低层实现,而去关心我们要实现的业务功能。