Netty是一个基于java nio 类库的异步通讯框架,它的架构特点是:异步非阻塞,基于事件驱动,高性能,高可靠性和高可定制性。
分布式开源框架中dubbo,zookeeper,rocketmq底层 rpc(远程过程调用协议)通讯使用就是netty
游戏开发中,底层使用netty通讯
NIO的类库和api繁杂,使用麻烦,你需要熟练掌握Selector,ServerSocketChannel,SocketChannel,ByteBuffer等。
需要具备其他的额外技能做铺垫,列如熟悉java多线程编程,因为nio编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写高质量的NIO程序。
可靠性能力补齐,工作量和难度都非常大,列如客户端面临断连重连,网络闪断,半包读写,失败缓存,网络拥塞和异常码流的处理,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大。
JDK NIO的bug,列如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CUP 100% 官方申称jdk1.6版本修护了这个问题,但是jdk1.7版本该问题依然存在,只不过该bug发生概率降低了一些而已。
//服务器端代码
class ServerHandler extends SimpleChannelHandler {
//通道关闭的时候触发
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
super.channelClosed(ctx, e);
System.out.println("服务器端通道关闭");
}
//必须是连接已经建立,关闭通道的时候才会触发
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
super.channelDisconnected(ctx, e);
System.out.println("连接。。服务器端通道关闭。。");
}
//捕获异常
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
super.exceptionCaught(ctx, e);
System.out.println("服务器端异常。。。");
}
//接受消息
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
super.messageReceived(ctx, e);
System.out.println("请求数据" + e.getMessage());
ctx.getChannel().write("收到" + e.getMessage());
}
}
public class NettyServer {
public static void main(String[] args) throws Exception {
//创建服务类对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
//创建两个线程池,分别监听端口,nio监听
ExecutorService e1 = Executors.newCachedThreadPool();
ExecutorService e2 = Executors.newCachedThreadPool();
//设置工程,并把两个线程池加入中
serverBootstrap.setFactory(new NioServerSocketChannelFactory(e1, e2));
//设置管道工厂
serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("serverHandler", new ServerHandler());
return pipeline;
}
});
serverBootstrap.bind(new InetSocketAddress(8080));
System.out.println("服务器端启动。。。。。。。。。。");
}
}
//客户端代码
class ClientHandler extends SimpleChannelHandler {
//通道关闭时候触发
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
super.channelClosed(ctx, e);
System.out.println("客户端通道关闭了。。。。。。");
}
//必须连接已经建立,关闭通道的时候才会触发
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
super.channelDisconnected(ctx, e);
System.out.println("连接。。客户端通道关闭了。。。。。。");
}
//捕获异常
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
super.exceptionCaught(ctx, e);
System.out.println("异常了。。。。。。");
}
//接受消息
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
super.messageReceived(ctx, e);
System.out.println("响应数据" + e.getMessage());
}
}
public class NettyClient {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动。。。。。。。。");
ClientBootstrap clientBootStrap = new ClientBootstrap();
ExecutorService e1 = Executors.newCachedThreadPool();
ExecutorService e2 = Executors.newCachedThreadPool();
clientBootStrap.setFactory(new NioClientSocketChannelFactory(e1, e2));
clientBootStrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("clientHandler", new ClientHandler());
return pipeline;
}
});
//连接服务器
ChannelFuture connect = clientBootStrap.connect(new InetSocketAddress("127.0.0.1",8080));
Channel channel = connect.getChannel();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("请输入内容。。。。。。");
channel.write(scanner.next());
}
}
}
//服务端
class ServerHandler extends ChannelHandlerAdapter {
//通道被调用执行此方法
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
String s = (String) msg;
System.out.println("请求数据:" + s);
ctx.writeAndFlush(Unpooled.copiedBuffer("响应数据--文龙".getBytes()));
}
}
public class NettyServer {
public static void main(String[] args) throws Exception {
System.out.println("服务器已经启动。。。。");
//创建2个线程 一个负责接收客户端连接 一个负责进行传输数据
NioEventLoopGroup n1 = new NioEventLoopGroup();
NioEventLoopGroup n2 = new NioEventLoopGroup();
//创建服务器辅助类
ServerBootstrap sb = new ServerBootstrap();
sb.group(n1, n2).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
//设置缓冲区与发送区大小
.option(ChannelOption.SO_SNDBUF, 10*1024).option(ChannelOption.SO_RCVBUF, 10*1024)
.childHandler(new ChannelInitializer() {
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture cf = sb.bind(8080).sync();
cf.channel().closeFuture().sync();
n1.shutdownGracefully();
n2.shutdownGracefully();
}
}
//客户端
class ClientHanlder extends ChannelHandlerAdapter {
//通道被调用执行该方法
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
String s = (String) msg;
System.out.println(s);
}
}
public class NettyClient {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动。。。。。。。");
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bs = new Bootstrap();
bs.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ClientHanlder());
}
});
ChannelFuture cf = bs.connect("127.0.0.1",8080).sync();
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("a1-shuwenlong".getBytes()));
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("a1-shuwenlong".getBytes()));
//等待客户端口关闭
cf.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是tcp的拆包和封包问题。
解决办法
消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
sc.pipeline().addLast(new FixedLengthFrameDecoder(10));
包尾添加特殊分隔符,列如每条报文结束都添加回车换行符,或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
ByteBuff b = Unpooled.copiedBuffer("_a1".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,b))
序列化就是将对象序列化为二进制形式,一般也将序列化叫做编码,主要用于网络传输,数据持久化等。
反序列化则是将从网络磁盘等读取的字节数组还原成原始对象,以便后续的业务进行。
一帮也将反序列化称为解码,主要用于网络传输对象的解码,以便完成远程调用。
几种序列化协议
xml,json,fastjson,thrift,avro,protobuf
xml序列化无论在性能和简洁性上比较差
Thrift与Protobuf相比在时空开销方面都有一定的劣势
Protobuf和Avro在两方面表现都非常优越
不同的场景适用的序列化协议
对于公司间的系统调用,如果性能要求在100ms以上的服务,基于XML的SOAP协议是一个值得考虑的方案。
基于Web browser的Ajax,以及Mobile app与服务端之间的通讯,JSON协议是首选。对于性能要求不太高,或者以动态类型语言为主,或者传输数据载荷很小的的运用场景,JSON也是非常不错的选择。
对于调试环境比较恶劣的场景,采用JSON或XML能够极大的提高调试效率,降低系统开发成本。
当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具有一定的竞争关系。
对于T级别的数据的持久化应用场景,Protobuf和Avro是首要选择。如果持久化后的数据存储在Hadoop子项目里,Avro会是更好的选择。
由于Avro的设计理念偏向于动态类型语言,对于动态语言为主的应用场景,Avro是更好的选择。
对于持久层非Hadoop项目,以静态类型语言为主的应用场景,Protobuf会更符合静态类型语言工程师的开发习惯。
如果需要提供一个完整的RPC解决方案,Thrift是一个好的选择。
如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。