三、Socket网络通信编程–Netty
Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议。Netty大大简化了网络程序的开发过程比如TCP和UDP的 Socket的开发。
“快速和简单”并不意味着应用程序会有难维护和性能低的问题,Netty是一个精心设计的框架,它从许多协议的实现中吸收了很多的经验比如FTP、SMTP、HTTP、许多二进制和基于文本的传统协议,Netty在不降低开发效率、性能、稳定性、灵活性情况下,成功地找到了解决方案。
有一些用户可能已经发现其他的一些网络框架也声称自己有同样的优势,所以你可能会问是Netty和它们的不同之处。答案就是Netty的哲学设计理念。Netty从第一天开始就为用户提供了用户体验最好的API以及实现设计。正是因为Netty的设计理念,才让我们得以轻松地阅读本指南并使用Netty。
用户指南:http://ifeve.com/netty5-user-guide/
1、Netty简单应用
Server.java
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; public class Server { private int port; public Server(int port) { // TODO Auto-generated constructor stub this.port = port; } public void run(){ //用来接收连接事件组 EventLoopGroup bossGroup = new NioEventLoopGroup(); //用来处理接收到的连接的事件处理组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //server配置辅助类 ServerBootstrap serverBootstrap = new ServerBootstrap(); //将连接接收组与事件处理组连接,当server的bossGroup接收到连接之后就会交给workerGroup进行处理 serverBootstrap.group(bossGroup, workerGroup) //指定接收的Channel类型 .channel(NioServerSocketChannel.class) //handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel) throws Exception { // TODO Auto-generated method stub channel.pipeline().addLast(new ServerHandler()); } }) //设置TCP缓冲区的大小 .option(ChannelOption.SO_BACKLOG, 128) //设置发送缓冲大小 .option(ChannelOption.SO_SNDBUF, 32*1024) //设置接收缓冲大小 .option(ChannelOption.SO_RCVBUF, 32*1024) //设置是否保持连接 .childOption(ChannelOption.SO_KEEPALIVE, true); //注意,此处option()是提供给NioServerSocketChannel用来接收进来的连接,也就是boss线程。 //childOption()是提供给由父管道ServerChannel接收到的连接,也就是worker线程,在这个例子中也是NioServerSocketChannel。 //异步绑定端口,可以绑定多个端口 ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync(); ChannelFuture channelFuture2 = serverBootstrap.bind(8766).sync(); System.out.println("Server服务已经启动."); //异步检查管道是否关闭 channelFuture.channel().closeFuture().sync(); channelFuture2.channel().closeFuture().sync(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) { Server server = new Server(8765); server.run(); } }
ServerHandler.java
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.PromiseAggregator; public class ServerHandler extends ChannelHandlerAdapter{ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Active..."); super.channelActive(ctx); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Read..."); try{ ByteBuf buf = (ByteBuf)msg; byte[] msgByte = new byte[buf.readableBytes()]; buf.readBytes(msgByte); System.out.println("Server Handler received message : " + new String(msgByte , "utf-8")); ChannelFuture writeFlush = ctx.writeAndFlush(Unpooled.copiedBuffer(("hi, client.").getBytes())); //给writeFlush添加监听,当数据发送完毕之后,调用该方法 writeFlush.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture arg0) throws Exception { // TODO Auto-generated method stub System.out.println(arg0); ctx.close(); } }); writeFlush.addListener(ChannelFutureListener.CLOSE); }finally{ ReferenceCountUtil.release(msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Read Complete..."); super.channelReadComplete(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub System.out.println("Exception Caught..."); super.exceptionCaught(ctx, cause); } }
Client.java
import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; 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 Client { private String ip; private int port; public Client(String ip, int port) { // TODO Auto-generated constructor stub this.ip = ip; this.port = port; } public void run(){ //客户端用来连接服务端的连接组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sChannel) throws Exception { // TODO Auto-generated method stub sChannel.pipeline().addLast(new ClientHandler()); } }) .option(ChannelOption.SO_KEEPALIVE, true); //可以进多个端口同时连接 ChannelFuture future = bootstrap.connect(this.ip, this.port).sync(); ChannelFuture future2 = bootstrap.connect(this.ip, 8766).sync(); future.channel().writeAndFlush(Unpooled.copiedBuffer(("Hello Server.").getBytes())); future2.channel().writeAndFlush(Unpooled.copiedBuffer(("Hello Server8766.").getBytes())); future.channel().closeFuture().sync(); future2.channel().closeFuture().sync(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ workerGroup.shutdownGracefully(); } } public static void main(String[] args) { Client client = new Client("127.0.0.1", 8765); client.run(); } }
ClinentHandler.java
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class ClientHandler extends ChannelHandlerAdapter{ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub try{ ByteBuf buf = (ByteBuf)msg; byte[] msgByte = new byte[buf.readableBytes()]; buf.readBytes(msgByte); System.out.println("ClientHandler received message : " + new String(msgByte, "utf-8")); }finally{ } } }
建立Netty通信服务的四个步骤:
- 1 创建2个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信读写
- 2 创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等等。
- 3 创建一个实际处理数据的类Channellnitializer,进行初始化的准备工作,比如设置接收或传出数据的字符集、格式、以及实际处理数据的接口
- 4 绑定端口,执行同步阻塞方法等待服务器端启动即可。
注意:在Netty中,默认传输都是以ByteBuf进行传输的,如果要使用字符串或者其他的格式,需要在配置ChannelInitializer的时候,需要配置相应的编码器和解码器,例如如果是要直接传输String类型,那么则需要使用String类型的编码器和解码器(Netty API已经提供)。注意,配置的时候客户端和服务器端最好都配置,否则没有配置的一方获取的仍然还是ByteBuf类型的结果
//3 创建一个辅助类Bootstrap,就是对我们的Server进行一系列的配置
ServerBootstrap b = new ServerBootstrap();
//把俩个工作线程组加入进来
b.group(bossGroup, workerGroup)
//我要指定使用NioServerSocketChannel这种类型的通道
.channel(NioServerSocketChannel.class)
//一定要使用 childHandler 去绑定具体的 事件处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//设置自定义分隔符
ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
//配置分割符解析器,并设置最大帧的长度与自定义分割符
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
//设置字符串形式的解析器
sc.pipeline().addLast(new StringDecoder());
//设置字符串形式的编码器
sc.pipeline().addLast(new StringEncoder());
sc.pipeline().addLast(new ServerHandler());
}
});
2、Netty拆包粘包
在基于流的传输里比如TCP/IP,接收到的数据会先被存储到一个socket接收缓冲里。不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列。即使你发送了2个独立的数据包,操作系统也不会作为2个消息处理而仅仅是作为一连串的字节而言。因此这是不能保证你远程写入的数据就会准确地读取。
参考资料:http://ifeve.com/netty5-user-guide
常用的拆包粘包主要有3种方式:
- 1、消息定长,例如每个报文的大小固定为200个字节,如果不够,空位补空格。
- 2、在包尾部增加特殊字符串进行分割,例如加回车等
- 3、 将消息分文消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务逻辑的处理
(1) 在包尾部增加特殊字符串进行分割
Server.java
import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; 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; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; public class Server { public static void main(String[] args) throws Exception { //1 第一个线程组 是用于接收Client端连接的 EventLoopGroup bossGroup = new NioEventLoopGroup(); //2 第二个线程组 是用于实际的业务处理操作的 EventLoopGroup workerGroup = new NioEventLoopGroup(); //3 创建一个辅助类Bootstrap,就是对我们的Server进行一系列的配置 ServerBootstrap b = new ServerBootstrap(); //把俩个工作线程组加入进来 b.group(bossGroup, workerGroup) //我要指定使用NioServerSocketChannel这种类型的通道 .channel(NioServerSocketChannel.class) //一定要使用 childHandler 去绑定具体的 事件处理器 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { //设置自定义分隔符 ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes()); //配置分割符解析器,并设置最大帧的长度与自定义分割符 sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf)); //设置字符串形式的解析器 sc.pipeline().addLast(new StringDecoder()); //设置字符串形式的编码器 sc.pipeline().addLast(new StringEncoder()); sc.pipeline().addLast(new ServerHandler()); } }) //设置TCP缓冲区 .option(ChannelOption.SO_BACKLOG, 128) //设置发送缓冲大小 .option(ChannelOption.SO_SNDBUF, 32*1024) //设置接收缓冲大小 .option(ChannelOption.SO_RCVBUF, 32*1024) //保持连接 .option(ChannelOption.SO_KEEPALIVE, true); //绑定指定的端口 进行监听 ChannelFuture f = b.bind(8765).sync(); //Thread.sleep(1000000); //异步监听管道关闭 f.channel().closeFuture().sync(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
ServerHandler.java
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.GenericFutureListener; public class ServerHandler extends ChannelHandlerAdapter { /** * @param ctx 连接上下文 * @msg 传输的消息对象 */ @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { try { //do something msg String request = (String)msg; System.out.println("Server: " + request); //写给客户端 String response = "我是响应消息$_"; //需要注意,在netty中默认是使用ByteBuf进行传输的,所以在回写消息的时候必须要转成ByteBuf //调用write方法的时候,netty会自动释放msg,所以下面的ReferenceCountUtil.release可以省略 ChannelFuture future = ctx.writeAndFlush(response); // ChannelFuture future = ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes())); //添加监听,当数据回写完毕之后,调用该监听方法 future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture arg0) throws Exception { // TODO Auto-generated method stub System.out.println("Server消息会送完毕,回调该方法。"); //关闭连接 // ctx.close(); } }); //当数据回写完毕之后,关闭与客户端的连接 // future.addListener(ChannelFutureListener.CLOSE); } catch (Exception e) { // TODO: handle exception }finally{ // ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
Client.java
import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; public class Client { public static void main(String[] args) throws Exception { EventLoopGroup workgroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(workgroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes()); //配置分割符解析器,并设置最大帧的长度与自定义分割符 sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf)); //设置字符串形式的解析器 sc.pipeline().addLast(new StringDecoder()); //设置字符串形式的编码器 sc.pipeline().addLast(new StringEncoder()); sc.pipeline().addLast(new ClientHandler()); } }); ChannelFuture cf1 = b.connect("127.0.0.1", 8765).sync(); // cf1.channel().writeAndFlush(Unpooled.copiedBuffer("我是消息1;$_".getBytes())); // cf1.channel().writeAndFlush(Unpooled.copiedBuffer("我是消息2;$_".getBytes())); // cf1.channel().writeAndFlush(Unpooled.copiedBuffer("我是消息3;$_".getBytes())); cf1.channel().writeAndFlush("我是消息1;$_"); cf1.channel().writeAndFlush("我是消息2;$_"); cf1.channel().writeAndFlush("我是消息3;$_"); cf1.channel().closeFuture().sync(); workgroup.shutdownGracefully(); } }
ClientHandle.java
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; public class ClientHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { //do something msg String response = (String)msg; System.out.println("Client:" + response); } finally { ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
(2) 消息定长
只要修改消息解析器就好
Server.java
//3 创建一个辅助类Bootstrap,就是对我们的Server进行一系列的配置 ServerBootstrap b = new ServerBootstrap(); //把俩个工作线程组加入进来 b.group(bossGroup, workerGroup) //我要指定使用NioServerSocketChannel这种类型的通道 .channel(NioServerSocketChannel.class) //一定要使用 childHandler 去绑定具体的 事件处理器 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { //配置固定长度解析器 sc.pipeline().addLast(new FixedLengthFrameDecoder(5)); //设置字符串形式的解析器 sc.pipeline().addLast(new StringDecoder()); //设置字符串形式的编码器 sc.pipeline().addLast(new StringEncoder()); sc.pipeline().addLast(new ServerHandler()); } }) //设置TCP缓冲区 .option(ChannelOption.SO_BACKLOG, 128) //保持连接 .option(ChannelOption.SO_KEEPALIVE, true);
Client.java
Bootstrap b = new Bootstrap(); b.group(workgroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { //配置固定长度解析器 sc.pipeline().addLast(new FixedLengthFrameDecoder(5)); //设置字符串形式的解析器 sc.pipeline().addLast(new StringDecoder()); //设置字符串形式的编码器 sc.pipeline().addLast(new StringEncoder()); sc.pipeline().addLast(new ClientHandler()); } }); //上面设置了定长为5个字节,那么如果不足5个字节消息不会发送,如果超过五个直接,那么会将前面的5个字节当做一个消息发出,剩下的部分如果不足5个,那么则不会发送,如果够5个就当做一个新的消息发出 cf1.channel().writeAndFlush(Unpooled.copiedBuffer("aaaaa".getBytes())); cf1.channel().writeAndFlush(Unpooled.copiedBuffer("bbbbb".getBytes())); cf1.channel().writeAndFlush(Unpooled.copiedBuffer("ccccccc ".getBytes())); cf1.channel().writeAndFlush(Unpooled.copiedBuffer("ddd".getBytes()));
3、Netty服务部署
常用的部署方式有2中,一种是耦合在Web应用中(以Tomcat为例),使其伴随Tomcat的启动而启动,伴随Tomcat的关闭而关闭。另外一种则是将Netty独立打包部署,然后由单独的进程启动运行(可以使用shell或其他脚本进行启动),然后以数据库或者其他缓存为承接点,实现数据交互。Netty与其他程序进行交互,然后将获取到的数据进行处理插入数据库或者缓存,然后其他服务从中获取。获取在Netty中调用web应用的一些对外接口。
4、Netty编解码技术
编解码技术,说白了就是java序列化技术,序列化目的就两个,第一个进行网络传输,第二对象持久化。虽然我们可以使用java进行对象序列化,netty去传输,但是java序列化的硬伤太多,比如:java序列化没法跨语言、序列化后码流太大、序列化性能太低等等。。
主流的编解码框架:
- JBoss的Marshalling包
- google的Protobuf
- 基于Protobuf的Kyro (性能高于Protobuf,可以与Marshalling媲美)
- MessagePack框架
(1)Netty结合JBoss Marshalling
JBoss Marshalling是一个java对象序列化包,对JDK默认的序列化框架进行了优化,但又保持跟java.io.Seriallzable接口兼容,同时增加了一些可调的参数和附加特性。
类库:jboss-marshalling1.3.0、jboss-marshalling-serial-1.3.0
下载地址:https://www.jboss.org/jbossmarshalling/downloads/
应用实例:
1、构造Marshalling的编解码工厂类
MarshallingCodeFactory.java
import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; import io.netty.handler.codec.marshalling.DefaultMarshallerProvider; import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider; import io.netty.handler.codec.marshalling.MarshallerProvider; import io.netty.handler.codec.marshalling.MarshallingDecoder; import io.netty.handler.codec.marshalling.MarshallingEncoder; import io.netty.handler.codec.marshalling.UnmarshallerProvider; public class MarshallingCodeFactory { /** *解码器 */ public static MarshallingDecoder buildMarshallingDecode(){ //首先通过Marshlling工具类的方法获取Marshalling实例对象,参数serial标识创建的是java序列化工厂对象 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); //创建MarshallingConfiguration对象 final MarshallingConfiguration configuration = new MarshallingConfiguration(); //设置Marshalling的版本号 configuration.setVersion(5); //根据MarshallerFactory和configuration创建provider(用于编解码操作) UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); //构建Netty的MarshallingDecoder对象,两个参数分别为provider和单个消息序列化后的最大长度,大于该长度的消息会被拒绝处理 MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024); return decoder; } /** *编码器 */ public static MarshallingEncoder buildMarshallingEncoder(){ //首先通过Marshlling工具类的方法获取Marshalling实例对象,参数serial标识创建的是java序列化工厂对象 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); //创建MarshallingConfiguration对象 final MarshallingConfiguration configuration = new MarshallingConfiguration(); //设置Marshalling的版本号 configuration.setVersion(5); //根据MarshallerFactory和configuration创建provider MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); MarshallingEncoder encoder = new MarshallingEncoder(provider); return encoder; } }
Server.java
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; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class Server { private int port; public Server(int port) { // TODO Auto-generated constructor stub this.port = port; } public void run(){ //用来接收连接事件组 EventLoopGroup bossGroup = new NioEventLoopGroup(); //用来处理接收到的连接的事件处理组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //server配置辅助类 ServerBootstrap serverBootstrap = new ServerBootstrap(); //将连接接收组与事件处理组连接,当server的bossGroup接收到连接之后就会交给workerGroup进行处理 serverBootstrap.group(bossGroup, workerGroup) //指定接收的Channel类型 .channel(NioServerSocketChannel.class) //handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。 .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel) throws Exception { // TODO Auto-generated method stub channel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingEncoder()); channel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingDecode()); channel.pipeline().addLast(new ServerHandler()); } }) //设置TCP缓冲区的大小 .option(ChannelOption.SO_BACKLOG, 128) //设置是否保持连接 .childOption(ChannelOption.SO_KEEPALIVE, true); //注意,此处option()是提供给NioServerSocketChannel用来接收进来的连接,也就是boss线程。 //childOption()是提供给由父管道ServerChannel接收到的连接,也就是worker线程,在这个例子中也是NioServerSocketChannel。 //异步绑定端口,可以多次调用绑定多个端口 ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync(); // ChannelFuture channelFuture2 = serverBootstrap.bind(8764).sync(); System.out.println("Server服务已经启动."); //异步检查管道是否关闭 channelFuture.channel().closeFuture().sync(); // channelFuture2.channel().closeFuture().sync(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) { Server server = new Server(8765); server.run(); } }
ServerHandler.java
import java.io.File; import java.io.FileOutputStream; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; public class ServerHandler extends ChannelHandlerAdapter{ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Active..."); super.channelActive(ctx); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Read..."); try{ Request request = (Request)msg; byte[] attachment = request.getAttachment(); byte[] unGzipData = GzipUtils.unGzip(attachment); String outPath = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "006_" + request.getId() + ".jpg"; FileOutputStream out = new FileOutputStream(outPath); out.write(unGzipData); out.flush(); out.close(); System.out.println("Server Handler received message : " + request.toString() ); Response response = new Response(); response.setId(request.getId()); response.setName("response_name_" + request.getId()); response.setResponseMessage("响应内容:" + request.getId()); ctx.writeAndFlush(response); // writeFlush.addListener(ChannelFutureListener.CLOSE); }finally{ ReferenceCountUtil.release(msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { // TODO Auto-generated method stub System.out.println("Channel Read Complete..."); super.channelReadComplete(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub System.out.println("Exception Caught..."); super.exceptionCaught(ctx, cause); } }
Client.java
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; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.io.File; import java.io.FileInputStream; public class Client { private String ip; private int port; public Client(String ip, int port) { // TODO Auto-generated constructor stub this.ip = ip; this.port = port; } public void run() throws Exception{ //客户端用来连接服务端的连接组 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sChannel) throws Exception { // TODO Auto-generated method stub sChannel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingEncoder()); sChannel.pipeline().addLast(MarshallingCodeFactory.buildMarshallingDecode()); sChannel.pipeline().addLast(new ClientHandler()); } }) .option(ChannelOption.SO_KEEPALIVE, true); //可以多次调用绑定多个端口 ChannelFuture channelFuture = bootstrap.connect(this.ip, this.port).sync(); for(int i = 0; i < 2; i++){ Request req = new Request(); req.setId("id_" + i); req.setName("name_" + i); req.setRequestMessage("数据消息_" + i); String path = System.getProperty("user.dir") + File.separatorChar + "source" + File.separatorChar + "006.jpg"; File file = new File(path); if( file.exists() ){ FileInputStream in = new FileInputStream(file); byte[] fileData = new byte[in.available()]; in.read(fileData, 0, fileData.length); req.setAttachment(GzipUtils.gzip(fileData)); } channelFuture.channel().writeAndFlush(req); } channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ workerGroup.shutdownGracefully(); } } public static void main(String[] args) { Client client = new Client("127.0.0.1", 8765); try { client.run(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
ClientHandler.java
import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; public class ClientHandler extends ChannelHandlerAdapter{ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub try{ Response response = (Response)msg; System.out.println("ClientHandler received message : " + response.toString() ); }finally{ } } }
Request.java
import java.io.Serializable; /** * 请求实体,注意需要实现Serializable接口,因为Marshalling相当于对Serializable的一个补充,在传输的时候仍然是利用Serializable进行序列化的 * @author jliu10 * */ public class Request implements Serializable{ private static final long serialVersionUID = 5538517398618869423L; private String id; private String name; private String requestMessage; private byte[] attachment; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRequestMessage() { return requestMessage; } public void setRequestMessage(String requestMessage) { this.requestMessage = requestMessage; } public byte[] getAttachment() { return attachment; } public void setAttachment(byte[] attachment) { this.attachment = attachment; } @Override public String toString() { // TODO Auto-generated method stub return " id : " + this.id + "; name : " + this.name + "; requestMessage : " + this.requestMessage; } }
Response.java
import java.io.Serializable; /** * 请求实体,注意需要实现Serializable接口,因为Marshalling相当于对Serializable的一个补充,在传输的时候仍然是利用Serializable进行序列化的 * @author jliu10 * */ public class Response implements Serializable{ private static final long serialVersionUID = 4157472697937211837L; private String id; private String name; private String responseMessage; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; } @Override public String toString() { // TODO Auto-generated method stub return " id : " + this.id + "; name : " + this.name + "; responseMessage : " + this.responseMessage; } }
一般我们可能会需要传输附件文件,但是原始附件文件可能会很大或者是文件夹之类的,所以我们通常会选择先将附件进行压缩,然后再进行传输。JDK已经给我们提供了一个压缩类GZIP
GzipUtils.java
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public class GzipUtils { public static byte[] gzip(byte[] data) throws Exception{ //构造一个byte输出流,用来缓存数据,然后提取成byte[](将数据从流中提取到内存中) ByteArrayOutputStream bos = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(bos); gzip.write(data); gzip.finish(); gzip.close(); byte[] zipData = bos.toByteArray(); bos.flush(); bos.close(); return zipData; } public static byte[] unGzip(byte[] data) throws Exception{ //构造一个byte输入流,将传入的byte[]数据转成输入流(将数据从内存中写入到流中) ByteArrayInputStream bis = new ByteArrayInputStream(data); GZIPInputStream gzip = new GZIPInputStream(bis); byte[] readBuf = new byte[1024]; int num = -1; //构造一个byte输出流,用来缓存数据,然后提取成byte[] ByteArrayOutputStream bos = new ByteArrayOutputStream(); while((num = gzip.read(readBuf, 0, readBuf.length)) != -1){ bos.write(readBuf, 0, num); } gzip.close(); bis.close(); byte[] unZipData = bos.toByteArray(); bos.flush(); bos.close(); return unZipData; } public static void main(String[] args) throws Exception { String path = System.getProperty("user.dir") + File.separatorChar + "source" + File.separatorChar + "20160107001.mp4"; File file = new File(path); if( !file.exists() ){ System.out.println("This file + (" + path + ") not exist!"); return; } FileInputStream in = new FileInputStream(file); byte[] fileData = new byte[in.available()]; in.read(fileData, 0, fileData.length); in.close(); System.out.println("原数据长度为:" + fileData.length); byte[] gZipData = GzipUtils.gzip(fileData); System.out.println("压缩后的数据长度为:" + gZipData.length); byte[] unGzipData = GzipUtils.unGzip(gZipData); System.out.println("解压后的数据长度为:" + unGzipData.length); String outPath1 = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "20160107001.zip"; FileOutputStream out1 = new FileOutputStream(outPath1); out1.write(gZipData); out1.flush(); out1.close(); String outPath2 = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "20160107001.mp4"; FileOutputStream out2 = new FileOutputStream(outPath2); out2.write(unGzipData); out2.flush(); out2.close(); } }
(2) Netty实现UDP协议开发
http://blog.csdn.net/mffandxx/article/details/53264172