一:什么是Netty
Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。二:Netty应用场景
1.分布式开源框架中dubbo、Zookeeper,RocketMQ底层rpc通讯使用就是netty,而Netty的底层就是NIO。2.游戏开发中,底层使用netty通讯。
三:为什么选择netty
为什么不建议开发者直接使用JDK的NIO类库进行开发的原因: NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
四:Netty手写客户端与服务端
首先引入netty的maven依赖
<!--引入Netty框架依赖--> <dependency> <groupId>io.netty</groupId> <artifactId>netty</artifactId> <version>3.3.0.Final</version> </dependency>
服务端
package netty; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.*; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.codec.string.StringEncoder; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Netty服务端 * Created by lizhen on 2018/3/7. */ public class NettyServer { public static void main(String[] args) { //1.创建服务对象 ServerBootstrap serverBootstrap = new ServerBootstrap(); //2.创建两个线程池,一个是监控端口号,一个是监控NIO ExecutorService boos = Executors.newCachedThreadPool(); ExecutorService work = Executors.newCachedThreadPool(); //3.将线程池放入到工程当中 serverBootstrap.setFactory(new NioServerSocketChannelFactory(boos,work)); //4.设置管道工程 serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() { //设置管道 @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); //传输数据的时候直接就是String类型的 pipeline.addLast("decoder",new StringDecoder()); pipeline.addLast("encoder",new StringEncoder()); //设置事件监听类 pipeline.addLast("serverHandler",new ServerHandler()); return pipeline; } }); //5.绑定端口号 serverBootstrap.bind(new InetSocketAddress(8080)); System.out.println("服务端已经启动........."); } } class ServerHandler extends SimpleChannelHandler{ /** * 通道关闭的时候触发 */ @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { System.out.println("channelClosed"); } /** * 必须是连接已经建立,关闭通道的时候才会触发. */ @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelDisconnected(ctx, e); System.out.println("channelDisconnected"); } /** * 捕获异常 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { super.exceptionCaught(ctx, e); System.out.println("exceptionCaught"); } /** * 接受消息 */ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { super.messageReceived(ctx, e); System.out.println("服务器端收到客户端消息:"+e.getMessage()); //回复内容 ctx.getChannel().write("好不好只有你自己知道!!!"); } }
客户端
package netty; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.*; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.handler.codec.string.StringDecoder; import org.jboss.netty.handler.codec.string.StringEncoder; import java.net.InetSocketAddress; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Netty客户端 * Created by lizhen on 2018/3/7. */ public class NettyClient { public static void main(String[] args) { ClientBootstrap clientBootstrap = new ClientBootstrap(); ExecutorService boos = Executors.newCachedThreadPool(); ExecutorService work = Executors.newCachedThreadPool(); //3.将线程池放入到工程当中 clientBootstrap.setFactory(new NioClientSocketChannelFactory(boos,work)); //4.设置管道工程 clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() { //设置管道 @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); //传输数据的时候直接就是String类型的 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 ClientHandler extends SimpleChannelHandler{ /** * 通道关闭的时候触发 */ @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { System.out.println("channelClosed"); } /** * 必须是连接已经建立,关闭通道的时候才会触发. */ @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelDisconnected(ctx, e); System.out.println("channelDisconnected"); } /** * 捕获异常 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { super.exceptionCaught(ctx, e); System.out.println("exceptionCaught"); } /** * 接受消息 */ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { super.messageReceived(ctx, e); System.out.println("客户端收到服务端消息:"+e.getMessage()); //回复内容 // ctx.getChannel().write("我真的好吗?"); } }查看效果
五:Netty5.0创建客户端与服务端
1.引入netty5的相关依赖
<!-- https://mvnrepository.com/artifact/io.netty/netty-all --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling --> <dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling</artifactId> <version>1.3.19.GA</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling-serial --> <dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling-serial</artifactId> <version>1.3.18.GA</version> <scope>test</scope> </dependency>
2.利用netty5创建服务端
package netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; /** * Netty 5.0 使用方法 * Created by lizhen on 2018/3/7. */ public class Netty5Server { public static void main(String[] args) throws InterruptedException { System.out.println("服务器端已经启动...."); // 1.创建2个线程,一个负责接收客户端连接, 一个负责进行 传输数据 NioEventLoopGroup pGroup = new NioEventLoopGroup(); NioEventLoopGroup cGroup = new NioEventLoopGroup(); // 2. 创建服务器辅助类 ServerBootstrap b = new ServerBootstrap(); b.group(pGroup, cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024) // 3.设置缓冲区与发送区大小 .option(ChannelOption.SO_SNDBUF, 32 * 1024).option(ChannelOption.SO_RCVBUF, 32 * 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new Server5Handler()); } }); ChannelFuture cf = b.bind(8080).sync(); cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } } class Server5Handler extends ChannelHandlerAdapter { /** * 当通道被调用,执行该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收数据 String value = (String) msg; System.out.println("Server msg:" + value); // 回复给客户端 “您好!” String res = "好的..."; ctx.writeAndFlush(Unpooled.copiedBuffer(res.getBytes())); } }
3.利用netty5创建客户端
package netty; import io.netty.bootstrap.Bootstrap; 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.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; /** * 利用Netty5创建的客户端 * Created by lizhen on 2018/3/7. */ public class Netty5Client { public static void main(String[] args) throws InterruptedException { System.out.println("客户端已经启动...."); // 创建负责接收客户端连接 NioEventLoopGroup pGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new Client5Handler()); } }); ChannelFuture cf = b.connect("127.0.0.1", 8080).sync(); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard".getBytes())); // 等待客户端端口号关闭 cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); } } class Client5Handler extends ChannelHandlerAdapter { /** * 当通道被调用,执行该方法 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收数据 String value = (String) msg; System.out.println("client msg:" + value); } }
启动查看效果
六:TCP粘包、拆包问题解决方案
什么是粘包/拆包
一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题。
我们在客户端的这个位置修改一下代码,向服务端发送5次请求,看一下粘包的效果
ChannelFuture cf = b.connect("127.0.0.1", 8080).sync(); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard1".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard2".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard3".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard4".getBytes())); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("lizhe_goog_goog_stard5".getBytes())); // 等待客户端端口号关闭 cf.channel().closeFuture().sync(); pGroup.shutdownGracefully();
发送了五次接受了一次,这就叫粘包,是因为TCP协议对我们做了优化,在瞬间将多次请求粘包成了一次进行发送.
什么是拆包?
我们在客户端修改代码,以goog进行拆包,然后查看一下效果
加入这样两行代码,以_goog进行拆包,最后显示的结果...
ByteBuf buf = Unpooled.copiedBuffer("_goog".getBytes()); sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
七:序列化协议与自定义序列化协议
序列化定义
序列化(serialization)就是将对象序列化为二进制形式(字节数组),然后保存在硬盘上,也称为编码(Encode),主要用于网络传输、数据持久化等;反序列化(deserialization)则是将从网络、磁盘等读取的字节数组还原成原始对象,也称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。
比如dubbo是一定要做序列化的,是因为要把对象转换成二进制文件,然后通过netty进行传输.然后在反序列化成对象.其实Dubbo底层原理就是通过netty进行传输的.
序列化协议有哪些?
比较主流就是json和xml,其它几个记不住了.