netty短链接的几篇文章

 

netty4发送HttpRequest及HttpReponse太大的问题(半包)

 

 

 

 

netty是对socket的封装,底下走的TCP还是老一套,太大的HTTP包发送的时候是要被切分成好几段的,发送之后接收就变得很麻烦。网上有人提出多种解决方式,我自己试了试,结果如下:

1.对于HttpRequest,有人提出在头部协商消息体长度。不要轻易相信呐,骚年们,这玩意儿就是一个坑,写上之后那个不睁眼的TCP照样剁碎了给发出去!此处红色警戒!

2.不用TCP剁碎了,自己剁碎了传过去。此方是否对症,没试验。

3.定长消息。自我感觉不适合HttpRequest及HttpReponse这类东西,所以没试。

4.其他,如设置发送缓冲和接收缓冲等,均不管用

所以,从几个坑里面爬出来之后,我隆重推出netty的解决方案:用HttpObjectAggregator!!!

首先,在客户端和服务端的channel下的pipeline启动之前加上

 

 <span style="white-space:pre">	</span>ch.pipeline().addLast("aggregator", new HttpObjectAggregator(1024*1024*64));
        ch.pipeline().addLast("chunkedWriter", new ChunkedWriteHandler());  

接收的时候就这样:

 

 

/**
	 	 * 读取传过来的消息,read request message from channel
	 	 */
	    @Override
	    public void channelRead(ChannelHandlerContext ctx, Object msg)
	            throws Exception {
	        if (msg instanceof DefaultHttpRequest) {
	            request = (DefaultHttpRequest) msg;
	        }
	        if (msg instanceof HttpContent) {
	            HttpContent content = (HttpContent) msg;
	            ByteBuf buf = content.content();
	            messageOfClient = buf.toString(io.netty.util.CharsetUtil.UTF_8);
	            if(messageOfClient!=null&&!messageOfClient.contentEquals("")&&!messageOfClient.contains("ping")){
		            messageToSend+= messageOfClient;
	            }else{
	            	System.out.println("channel的数目:"+ServerDataSyncServer.channels.size()+" channelGroupSize");
	            	System.out.println(ctx.channel().remoteAddress().toString());
	            }
	            buf.release();
	            if(content instanceof LastHttpContent){
	            	//此处对其他客户端的心跳包不予处理,因为服务端掉线之后会客户端会循环侦测连接,客户端断掉之后将服务端将不打印输出信息
	            	if(messageToSend.length()>12&&messageToSend.substring(0, 2).contentEquals("DB")){

			            //消息长度符合一定条件,则是需要向其他客户端发送的数据库消息,调用方法转发
		            	System.out.println("Server已读取数据库持久化信息,将开始向所有客户端发送");
		            	ServerDataSyncServer.channels.remove(ctx.channel());
		            	System.out.println("messageToSend   "+messageToSend );
		            	String messageContent = messageToSend;
		            	ServerDataSyncServerSendGroup.sendToAllChannel(messageContent);
		            	messageToSend = "";
		            }
	            }
	        }
	    }


当然,我的发送客户端是短连接,

ServerDataSyncServer.channels.remove(ctx.channel());

长连接的对这句可以直接忽略。

 

这样就可以了,netty接收到分段的大TCP包之后会自动组装成完整的HTTPrequest或者HttpResponse,然后逐个读取分段的消息体,最后一个是LastHttpContent类型,读到这个就算是整个包读完了,再做后续处理即可。

完毕

 

---------------------------

 

 

Netty4中TCP短连接处理无法正确发送数据的问题探究

 

最近在使用Netty开发一个服务器,在一个短连接的业务场景中出现了

ChannelHandlerContext.flush()

ChannelHandlerContext.close()

后,数据没有正确发送出来的情况。

 

通过Wireshark抓本地回环包确认了有一小部分数据没有成功发送出来。

检查业务代码无误后发现在Netty配置中 有一个配置项

ChannelOption.SO_SNDBUF的值设置为了128

我发送数据的长度为144b

在客户端刚好也只能收到128字节 其余的16字节没有正确收到

 

尝试修改该配置为1024可解决问题。 但并不是根本的解决。

 

通过阅读Netty源码后发现,netty会异步的write剩余的数据,最后的解决方案为:

修改代码添加异步回调处理机制:

ChannelFuture channelFuture = ctx.writeAndFlush(responsePacket);

channelFuture.addListener(new ChannelFutureListener() {

/*

* @see io.netty.util.concurrent.GenericFutureListener#

* operationComplete(io.netty.util.concurrent.Future)

*/

@Override

publicvoid operationComplete(ChannelFuture future) throws Exception {

if (future.isSuccess()) {

ctx.close();

} else {

throw new Exception(“未成功发送数据给客户端”, future.cause());

}

}

});

 

经完整测试解决了问题

---------------------------------------------------

 

future.addListener(ChannelFutureListener.CLOSE)这句话的意思是,当请求处理完成之后,关闭链接。

 

 

基于netty4的TCP短连接测试 

 

 

 

本文中的代码做了一定优化,但是还不是很完全,欢迎指正

 

工程结构图如下:

基于netty4的TCP短连接测试 - 断鸿零雁 - 断鸿零雁的博客

 

TcpServer.java

 

 

package com.lin.netty4.tcp; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; 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.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import org.apache.log4j.Logger; public class TcpServer { private static final Logger logger = Logger.getLogger(TcpServer.class); private static final String IP = "127.0.0.1"; private static final int PORT = 9999; /**用于分配处理业务线程的线程组个数 */ protected static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors()*2; //默认 /** 业务出现线程大小*/ protected static final int BIZTHREADSIZE = 1000; private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE); private static final EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE); protected static void run() throws Exception { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup); b.channel(NioServerSocketChannel.class); b.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); pipeline.addLast(workerGroup,new TcpServerHandler()); } }); // b.childOption(ChannelOption.SO_KEEPALIVE,true); // b.option(ChannelOption.SO_BACKLOG, 10000); b.bind(IP, PORT).sync(); logger.info("TCP服务器已启动"); } protected static void shutdown() { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } public static void main(String[] args) throws Exception { logger.info("开始启动TCP服务器..."); TcpServer.run(); // TcpServer.shutdown(); } }


TcpServerHandler.java

 

package com.lin.netty4.tcp; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; import org.apache.log4j.Logger; public class TcpServerHandler extends ChannelInboundHandlerAdapter{ private static final Logger logger = Logger.getLogger(TcpServerHandler.class); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { ByteBuf buf = (ByteBuf) msg; byte[] dst = new byte[buf.capacity()]; buf.readBytes(dst); logger.info("SERVER接收到消息:" + new String(dst)); byte[] dest = (new String(dst)+". yes, server is accepted you ,nice !").getBytes(); ByteBuf destBuf = ctx.alloc().buffer(dest.length); destBuf.writeBytes(dest); ctx.channel().writeAndFlush(destBuf).addListener(ChannelFutureListener.CLOSE); ReferenceCountUtil.release(msg); } else { logger.warn("error object !"); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.warn("Unexpected exception from downstream.", cause); ctx.close(); } }

TcpClient.java

 

package com.lin.netty4.tcp; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import org.apache.log4j.Logger; public class TcpClient { private static final Logger logger = Logger.getLogger(TcpClient.class); public static String HOST = "127.0.0.1"; public static int PORT = 9999; public static Bootstrap bootstrap = getBootstrap(); /** * 初始化Bootstrap * @return */ public static final Bootstrap getBootstrap(){ EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class); b.handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); pipeline.addLast("handler", new TcpClientHandler()); } }); // b.option(ChannelOption.SO_KEEPALIVE, true); return b; } public static final Channel getChannel(String host,int port){ Channel channel = null; try { channel = bootstrap.connect(host, port).sync().channel(); } catch (Exception e) { logger.error(String.format("连接Server(IP[%s],PORT[%s])失败", host,port),e); return null; } return channel; } public static void sendMsg(Channel channel,Object msg) throws Exception { if(channel!=null){ channel.writeAndFlush(msg).sync(); }else{ logger.warn("消息发送失败,连接尚未建立!"); } } public static void main(String[] args) throws Exception { try { long t0 = System.nanoTime(); byte[] value = null; Channel channel = null; for (int i = 0; i < 50000; i++) { channel = getChannel(HOST, PORT); value = (i+",你好").getBytes(); ByteBufAllocator alloc = channel.alloc(); ByteBuf buf = alloc.buffer(value.length); buf.writeBytes(value); TcpClient.sendMsg(channel,buf); } long t1 = System.nanoTime(); System.out.println((t1-t0)/1000000.0); Thread.sleep(5000); System.exit(0); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

TcpClientHandler.java

 

package com.lin.netty4.tcp; import org.apache.log4j.Logger; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; public class TcpClientHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = Logger.getLogger(TcpClientHandler.class); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof ByteBuf){ ByteBuf buf = (ByteBuf)msg; byte[] dst = new byte[buf.capacity()]; buf.readBytes(dst); logger.info("client接收到服务器返回的消息:"+new String(dst)); ReferenceCountUtil.release(msg); }else{ logger.warn("error object"); } } }

log4j.properties

 

#控制台 log4j.rootLogger=DEBUG, CONSOLE,DAILY_ROLLING_FILE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=WARN log4j.appender.CONSOLE.Encoding=UTF-8 log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=[%t] %d [%p] - %l:%m%n #文件方式存储 log4j.appender.DAILY_ROLLING_FILE=org.apache.log4j.DailyRollingFileAppender log4j.appender.DAILY_ROLLING_FILE.Encoding=UTF-8 log4j.appender.DAILY_ROLLING_FILE.Threshold=WARN log4j.appender.DAILY_ROLLING_FILE.File=E\:\\log4j\\nettyDemo.log log4j.appender.DAILY_ROLLING_FILE.DatePattern='.'yyyy-MM-dd log4j.appender.DAILY_ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.DAILY_ROLLING_FILE.layout.ConversionPattern=[%t]%d [%p] - %l:%m%n log4j.logger.org.apache.commons = WARN log4j.logger.io.netty = WARN

 

当前情况下测试发送5W数据需要约37秒(37870.31592),Eclipse未做优化,PC配置4核3G内存

 

 

 

 

 

赠送视频如下:

 

或者一套redis视频

 

不定期送视频 

1700G it视频  等你来!

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值