netty初探(2)
上一篇 netty(1)
一、TCP/IP 流式传输
在上文演示了2进制流式传输引起的TCP拆包问题,这里继续演示文本型的传输问题,文本型的可以有以下几种策略
1.1 以特殊字符表示结尾
HTTP协议中以\r\n\r\n表示请求首部结束,这里也以\r\n\r\n表示特殊字符,非常容易理解,没有碰到\r\n\r\n就继续写入缓冲,碰到了表明是一个完整的逻辑数据,可以处理了。
Server端代码
public class Server { public static final String SERVER_DELIMITER = "\r\n\r\n"; public static void main(String[] args) throws Exception { //1 创建2个线程,一个是负责接收客户端的连接。一个是负责进行数据传输的 EventLoopGroup pGroup = new NioEventLoopGroup(); EventLoopGroup cGroup = new NioEventLoopGroup(); //2 创建服务器辅助类 ServerBootstrap b = new ServerBootstrap(); b.group(pGroup, cGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.SO_SNDBUF, 32 * 1024) .option(ChannelOption.SO_RCVBUF, 32 * 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { //设置特殊分隔符 ByteBuf buf = Unpooled.copiedBuffer(SERVER_DELIMITER.getBytes()); sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf)); //设置字符串形式的解码 sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new ServerHandler()); } }); //4 绑定连接 ChannelFuture cf = b.bind(8765).sync(); //等待服务器监听端口关闭 cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } }
public class ServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(" server channel active... "); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String request = (String)msg; System.out.println("Server :" + msg); String response = "服务器响应:" + msg + Server.SERVER_DELIMITER; ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes())); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) throws Exception { ctx.close(); } }
客户端代码:
public class Client { public static final String CLIENT_DELIMITER = "\r\n\r\n"; public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { // ByteBuf buf = Unpooled.copiedBuffer(CLIENT_DELIMITER.getBytes()); sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf)); sc.pipeline().addLast(new StringDecoder()); sc.pipeline().addLast(new ClientHandler()); } }); ChannelFuture cf = b.connect("127.0.0.1", 8765).sync(); StringBuffer sb = new StringBuffer("GET " + "/index.jsp" + " HTTP/1.1\r\n"); sb.append("Host: www.javathinker.org\r\n"); sb.append("Accept: */*\r\n"); sb.append("Accept-Language: zh-cn\r\n"); sb.append("Accept-Encoding: gzip, deflate\r\n"); sb.append("User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)\r\n"); sb.append("Connection: Keep-Alive\r\n\r\n"); cf.channel().writeAndFlush(Unpooled.wrappedBuffer(sb.toString().getBytes())); // Thread.sleep(1000); // cf.addListener(ChannelFutureListener.CLOSE); //等待客户端端口关闭 cf.channel().closeFuture().sync(); group.shutdownGracefully(); } }
public class ClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client channel active... "); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { String response = (String)msg; System.out.println("Client: " + response); } finally { ReferenceCountUtil.release(msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
1.2 以FixedLength的方式
跟上述代码类似,只需要修改下Decoder即可
b.group(group) .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 ClientHandler()); } });
二、使用Java对象传输
官网Object demo:http://netty.io/4.1/xref/io/netty/example/objectecho/package-summary.html
使用Java对象传输,只需要配置对应的encoder: Object->byte[] 和对应的decoder: byte[]->Object即可
2.1 使用java searilizable
先写个工具类,提供GZIP压缩功能,以及发送请求和处理请求的通用方法
package netty; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import netty.marshalling.Req; import netty.marshalling.Resp; import java.io.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * Created by carl.yu on 2016/11/7. */ public class Utils { public static byte[] gzip(byte[] data) throws Exception{ ByteArrayOutputStream bos = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(bos); gzip.write(data); gzip.finish(); gzip.close(); byte[] ret = bos.toByteArray(); bos.close(); return ret; } public static byte[] ungzip(byte[] data) throws Exception{ ByteArrayInputStream bis = new ByteArrayInputStream(data); GZIPInputStream gzip = new GZIPInputStream(bis); byte[] buf = new byte[1024]; int num = -1; ByteArrayOutputStream bos = new ByteArrayOutputStream(); while((num = gzip.read(buf, 0 , buf.length)) != -1 ){ bos.write(buf, 0, num); } gzip.close(); bis.close(); byte[] ret = bos.toByteArray(); bos.flush(); bos.close(); return ret; } public static void send(ChannelFuture cf) throws Exception{ for (int i = 1; i <= 2; i++) { Req req = new Req(); req.setId("" + i); req.setName("pro" + i); req.setRequestMessage("数据信息" + i); String path = "G:\\projects-helloworld\\lucene\\src\\main\\resources\\in\\" + i + ".jpg"; String fileName = i + ".jpg"; req.setFileName(fileName); File file = new File(path); FileInputStream in = new FileInputStream(file); byte[] data = new byte[in.available()]; in.read(data); in.close(); req.setAttachment(Utils.gzip(data)); cf.channel().writeAndFlush(req); } } public static void recv(ChannelHandlerContext ctx, Object msg) throws Exception{ Req req = (Req) msg; System.out.println("req:"+req); System.out.println("Server : " + req.getId() + ", " + req.getName() + ", " + req.getRequestMessage()); byte[] attachment = Utils.ungzip(req.getAttachment()); String fileName = req.getFileName(); String path = "G:\\projects-helloworld\\lucene\\src\\main\\resources\\out\\" + fileName; FileOutputStream fos = new FileOutputStream(path); fos.write(attachment); fos.close(); Resp resp = new Resp(); resp.setId(req.getId()); resp.setName("resp" + req.getId()); resp.setResponseMessage("响应内容" + req.getId()); ctx.writeAndFlush(resp);//.addListener(ChannelFutureListener.CLOSE); } public static void main(String[] args) throws Exception{ //读取文件 String readPath = System.getProperty("user.dir") + File.separatorChar + "sources" + File.separatorChar + "006.jpg"; File file = new File(readPath); FileInputStream in = new FileInputStream(file); byte[] data = new byte[in.available()]; in.read(data); in.close(); System.out.println("文件原始大小:" + data.length); //测试压缩 byte[] ret1 = gzip(data); System.out.println("压缩之后大小:" + ret1.length); byte[] ret2 = ungzip(ret1); System.out.println("还原之后大小:" + ret2.length); //写出文件 String writePath = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "006.jpg"; FileOutputStream fos = new FileOutputStream(writePath); fos.write(ret2); fos.close(); } }
Req:
import java.io.Serializable; public class Req implements Serializable { private static final long SerialVersionUID = 1L; private String id; private String name; private String requestMessage; private byte[] attachment; private String fileName; 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; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } @Override public String toString() { return "Req{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", requestMessage='" + requestMessage + '\'' + ", fileName='" + fileName + '\'' + '}'; } }
Resp:
import java.io.Serializable; public class Resp implements Serializable{ private static final long serialVersionUID = 1L; private String id = "1"; private String name ="aaa"; private String responseMessage ="this is demo"; 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() { return "Resp{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", responseMessage='" + responseMessage + '\'' + '}'; } }
Server端代码:
public class ObjectEchoServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } p.addLast( new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new ObjectEchoServerHandler()); } }); // Bind and start to accept incoming connections. b.bind(PORT).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
public class ObjectEchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // Echo back the received object to the client. Utils.recv(ctx, msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
client端代码:
public final class ObjectEchoClient { static final boolean SSL = System.getProperty("ssl") != null; static final String HOST = System.getProperty("host", "127.0.0.1"); static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { sslCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); } else { sslCtx = null; } EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT)); } p.addLast( new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new ObjectEchoClientHandler()); } }); // Start the connection attempt. ChannelFuture cf = b.connect(HOST, PORT).sync(); Utils.send(cf); cf.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } }
public class ObjectEchoClientHandler extends ChannelInboundHandlerAdapter { private final List<Integer> firstMessage; /** * Creates a client-side handler. */ public ObjectEchoClientHandler() { firstMessage = new ArrayList<Integer>(ObjectEchoClient.SIZE); for (int i = 0; i < ObjectEchoClient.SIZE; i++) { firstMessage.add(Integer.valueOf(i)); } } @Override public void channelActive(ChannelHandlerContext ctx) { } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { Resp resp = (Resp)msg; System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage()); } finally { ReferenceCountUtil.release(msg); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
2.2 使用jboss marshalling序列化
<dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling-serial</artifactId> <version>2.0.0.Beta2</version> </dependency>
jboss marshalling自动支持netty的codec
public final class MarshallingCodeCFactory { /** * 创建Jboss Marshalling解码器MarshallingDecoder * @return MarshallingDecoder */ public static MarshallingDecoder buildMarshallingDecoder() { //首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象。 final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); //创建了MarshallingConfiguration对象,配置了版本号为5 final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); //根据marshallerFactory和configuration创建provider UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration); //构建Netty的MarshallingDecoder对象,俩个参数分别为provider和单个消息序列化后的最大长度 MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1); return decoder; } /** * 创建Jboss Marshalling编码器MarshallingEncoder * @return MarshallingEncoder */ public static MarshallingEncoder buildMarshallingEncoder() { final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial"); final MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration); //构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组 MarshallingEncoder encoder = new MarshallingEncoder(provider); return encoder; } }
直接在server中配置codec即可,其他都类似
b.group(pGroup, cGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) //设置日志 .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder()); sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder()); sc.pipeline().addLast(new ServerHandler()); } });
b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder()); sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder()); sc.pipeline().addLast(new ClientHandler()); } });
2.3 其他序列化框架
其他序列化框架都类似,只要有对应的encoder和decoder,比如Kryo和Google protocol Buf
三、其他
3.1 read_time_out和write_time_out
在用netty进行socket通信时,通常也会遇到read time out和write time out的设置问题,netty也是通过handler来实现的,netty默认提供了2个类
// The connection is closed when there is no inbound traffic // for 30 seconds. public class MyChannelInitializer extends ChannelInitializer<Channel> { public void initChannel(Channel channel) { channel.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(30); channel.pipeline().addLast("myHandler", new MyHandler()); } } // Handler should handle the ReadTimeoutException. public class MyHandler extends ChannelDuplexHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof ReadTimeoutException) { // do something } else { super.exceptionCaught(ctx, cause); } } } ServerBootstrap bootstrap = ...; ... bootstrap.childHandler(new MyChannelInitializer()); ...
当读超时,可以捕获该异常,也可以丢弃此连接,防止占用服务器资源。
同样的
// The connection is closed when a write operation cannot finish in 30 seconds. public class MyChannelInitializer extends ChannelInitializer<Channel> { public void initChannel(Channel channel) { channel.pipeline().addLast("writeTimeoutHandler", new WriteTimeoutHandler(30); channel.pipeline().addLast("myHandler", new MyHandler()); } } // Handler should handle the WriteTimeoutException. public class MyHandler extends ChannelDuplexHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof WriteTimeoutException) { // do something } else { super.exceptionCaught(ctx, cause); } } } ServerBootstrap bootstrap = ...; ... bootstrap.childHandler(new MyChannelInitializer()); ...