Netty解决TCP粘包/拆包问题 - 按行分隔字符串解码器
服务端
package org.zln.netty.five.timer; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 时间服务器服务端
* Created by sherry on 16/11/5.
*/
public class TimerServer {
/**
* 服务端绑定端口号
*/
private int PORT; public TimerServer(int PORT) {
this.PORT = PORT;
} /**
* 日志
*/
private static Logger logger = LoggerFactory.getLogger(TimerServer.class); public void bind() {
/*
NioEventLoopGroup是线程池组
包含了一组NIO线程,专门用于网络事件的处理
bossGroup:服务端,接收客户端连接
workGroup:进行SocketChannel的网络读写
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
/*
ServerBootstrap:用于启动NIO服务的辅助类,目的是降低服务端的开发复杂度
*/
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)//配置TCP参数,能够设置很多,这里就只设置了backlog=1024,
.childHandler(new TimerServerInitializer());//绑定I/O事件处理类
logger.debug("绑定端口号:" + PORT + ",等待同步成功");
/*
bind:绑定端口
sync:同步阻塞方法,等待绑定完成,完成后返回 ChannelFuture ,主要用于通知回调
*/
ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();
logger.debug("等待服务端监听窗口关闭");
/*
closeFuture().sync():为了阻塞,服务端链路关闭后才退出.也是一个同步阻塞方法
*/
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
} finally {
logger.debug("优雅退出,释放线程池资源");
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
TimerServer
package org.zln.netty.five.timer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; /**
* Created by sherry on 16/11/5.
*/
public class TimerServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimerServerHandler()); }
}
TimerServerInitializer
package org.zln.netty.five.timer; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat;
import java.util.Date; /**
* Handler主要用于对网络事件进行读写操作,是真正的业务类
* 通常只需要关注 channelRead 和 exceptionCaught 方法
* Created by sherry on 16/11/5.
*/
public class TimerServerHandler extends ChannelHandlerAdapter { /**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(TimerServerHandler.class); private static int count = 0; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg;
logger.debug("第 "+(++count)+" 次收到请求 - "+body); String timeNow = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date())+System.lineSeparator(); //获取发送给客户端的数据
ByteBuf resBuf = Unpooled.copiedBuffer(timeNow.getBytes("UTF-8")); ctx.writeAndFlush(resBuf);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将消息发送队列中的消息写入到SocketChannel中发送给对方
logger.debug("channelReadComplete");
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发生异常时,关闭 ChannelHandlerContext,释放ChannelHandlerContext 相关的句柄等资源
logger.error(cause.getMessage(),cause);
ctx.close();
}
}
TimerServerHandler
客户端
package org.zln.netty.five.timer; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 时间服务器客户端
* Created by sherry on 16/11/5.
*/
public class TimerClient {
/**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(TimerServer.class); private String HOST;
private int PORT; public TimerClient(String HOST, int PORT) {
this.HOST = HOST;
this.PORT = PORT;
} public void connect(){
//配置客户端NIO线程组
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new TimerClientInitializer());
//发起异步连接操作
logger.debug("发起异步连接操作 - start");
ChannelFuture channelFuture = bootstrap.connect(HOST,PORT).sync();
logger.debug("发起异步连接操作 - end");
//等待客户端链路关闭
logger.debug("等待客户端链路关闭 - start");
channelFuture.channel().closeFuture().sync();
logger.debug("等待客户端链路关闭 - end");
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
}finally {
//优雅的关闭
eventLoopGroup.shutdownGracefully();
}
}
}
TimerClient
package org.zln.netty.five.timer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; /**
* Created by sherry on 16/11/5.
*/
public class TimerClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new TimerClientHandler());
}
}
TimerClientInitializer
package org.zln.netty.five.timer; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; /**
* Created by sherry on 16/11/5.
*/
public class TimerClientHandler extends ChannelHandlerAdapter { /**
* 日志
*/
private Logger logger = LoggerFactory.getLogger(TimerClientHandler.class); private static int count = 0; @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.debug("客户端连接上了服务端"); //发送请求
ByteBuf reqBuf = null;
for (int i = 0; i < 100; i++) {
reqBuf = getReq("GET TIME"+System.lineSeparator());
ctx.writeAndFlush(reqBuf);
} } /**
* 将字符串包装成ByteBuf
* @param s
* @return
*/
private ByteBuf getReq(String s) throws UnsupportedEncodingException {
byte[] data = s.getBytes("UTF-8");
ByteBuf reqBuf = Unpooled.buffer(data.length);
reqBuf.writeBytes(data);
return reqBuf;
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
logger.debug("这是收到的第 "+(++count)+" 笔响应 -- "+body);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
TimerClientHandler
这里主要使用 LineBasedFrameDecoder 和 StringDecoder 来实现解决粘包问题
原理如下:
LineBasedFrameDecoder 依次遍历 ByteBuf 中的可读字节,判断是否有 \n 或 \r\n,如果有,就作为结束位置。从可读索引到结束位置区间的字节组成一行。它是以换行符为结束标志的解码器。支持携带结束符或者不懈怠结束符两种解码方式。同时支持配置单行的最大长度。如果读取到了最大长度仍旧没有发现换行符,就会抛出异常,同时忽略掉之前读到的数据。
StringDecoder 的作用就是讲接收到的对象转化成字符串,然后继续调用handler。这样就不需要再handler中手动将对象转化成字符串了,直接强制转化就行。
LineBasedFrameDecoder+StringDecoder组合就是按行切割的文本解码器,用来解决TCP的粘包和拆包问题。
Netty解决TCP粘包/拆包问题 - 按行分隔字符串解码器的更多相关文章
- 1. Netty解决Tcp粘包拆包
一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...
- 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?
前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
- 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
一.粘包/拆包概念 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认 ...
- Netty的TCP粘包/拆包(源码二)
假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况: 1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包. 2.服 ...
- netty之==TCP粘包/拆包问题解决之道(一)
一.TCP粘包/拆包是什么 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在 ...
- netty 解决TCP粘包与拆包问题(二)
TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...
- netty 解决TCP粘包与拆包问题(一)
1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...
- 【转】Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
随机推荐
- Atitit. Atiposter 发帖机 新特性 poster new feature v7 q39
Atitit. Atiposter 发帖机 新特性 poster new feature v7 q39 V1 初步实现sina csdn cnblogs V2 实现qzone sohu 的发帖 ...
- arcgis安装msi安装包提示"在未标记为正在运行时,调用了RunScript”解决办法
安装msi安装包提示"在未标记为正在运行时,调用了RunScript”解决办法 windows/temp目录相关权限不对,右击temp文件夹,选择管理员获取所有权限.
- Oracle自动统计信息的收集原理及实验
[日期:2014-11-21]来源:Linux社区 作者:stevendbaguo[字体:大 中 小] 从Oracle Database 10g开始,Oracle在建库后就默认创建了一个名为GATH ...
- 【读书笔记】iOS-UIFont-动态下载系统提供的多种中文字体网址
苹果可使用的字体列表: https://support.apple.com/zh-cn/HT202599 动态下载字体的代码demo: https://developer.apple.com/libr ...
- iOS 利用长按手势移动 Table View Cells
本文译自:Cookbook: Moving Table View Cells with a Long Press Gesture 目录: 你需要什么? 如何做? 如何将其利用至UICollection ...
- AFNetworking使用方法
官网下载2.5版本:http://afnetworking.com/ 此文章是基于AFNetworking2.5版本的,需要看AFNetworking2.0版本的请看上一篇文章:AFNetworkin ...
- 原生JS获取各种高度宽度、浏览器窗口滚动条的位置、元素的几何尺寸名
1)关于 pageX, clienX,offsetX,layerX pageX:鼠标在页面上的位置,从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化 clientX:鼠标在页面上可视区域的位 ...
- CTF中那些脑洞大开的编码和加密
0x00 前言 正文开始之前先闲扯几句吧,玩CTF的小伙伴也许会遇到类似这样的问题:表哥,你知道这是什么加密吗?其实CTF中脑洞密码题(非现代加密方式)一般都是各种古典密码的变形,一般出题者会对密文进 ...
- selinux开启关闭
查看SELinux状态: 1./usr/sbin/sestatus -v ##如果SELinux status参数为enabled即为开启状态 SELinux status: ...
- 第k大的数,前k大的数
1.排序后去出前k个,o(n*log(n)) 如果k<log(n),可以考虑直接选择排序,因为只需要执行找到第k个就可以结束 o(n*k) 2.o(nlog(k))快排把数分为了两个部分, ...