Netty快速入门

什么是Netty

Netty是一个基于java nio 类库的异步通讯框架,它的架构特点是:异步非阻塞,基于事件驱动,高性能,高可靠性和高可定制性。

Netty应用场景

分布式开源框架中dubbo,zookeeper,rocketmq底层 rpc(远程过程调用协议)通讯使用就是netty

游戏开发中,底层使用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());
		}
	}
}

Netty5.0用法

//服务端

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<SocketChannel>() {

			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<SocketChannel>() {
			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拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是tcp的拆包和封包问题。

解决办法

消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。

sc.pipeline().addLast(new FixedLengthFrameDecoder(10));

包尾添加特殊分隔符,列如每条报文结束都添加回车换行符,或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。

扫描二维码关注公众号,回复: 5383077 查看本文章
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可以优先考虑。

猜你喜欢

转载自blog.csdn.net/qq_38065439/article/details/87973908