假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况:

  1、服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包。

  2、服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包

  3、服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包

  4、服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包

  如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。  

那么在Netty中可使用LineBasedFrameDecoderStringDecoder

  LineBasedFrameDecoder的工作原理是一次遍历ByteBuf中的可读字节,判断看是否有"\n"或者"\r\n",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。

  StringDecoder将接收到的对象转换成字符串,然后继续调用后面的handler。

  利用LineBasedFrameDecoder解决TCP粘包问题:

 package netty;

 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.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class TimeServer { public void bind(int port) throws Exception{
//配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务器监听端口关闭
f.channel().closeFuture().sync();
}finally{
//优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel arg0) throws Exception{
arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));
42 arg0.pipeline().addLast(new StringDecoder());
arg0.pipeline().addLast(new TimeServerHandler());
}
} public static void main(String args[]) throws Exception{
int port = 10001;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
new TimeServer().bind(port);
}
}

TimeServerHandler, msg是删除回车换行符后的请求消息,不需要额外考虑处理半包问题,也不需要对请求消息进行编码:

 import java.io.IOException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; public class TimeServerHandler extends ChannelHandlerAdapter{ private int counter; public void channelRead(ChannelHandlerContext ctx,Object msg) throws IOException{ String body = (String)msg;
System.out.println("The time server receive order:" + body + "; the counter is :" + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)? new java.util.Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
} public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
ctx.close();
}
}

TimeClient,在TimeClientHandler之前新增lineBasedFrameDecoder和StringDecoder解码器:

 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.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class TimeClient { public void connect(int port,String host) throws Exception{
//创建客户端处理I/O读写的NioEventLoopGroup Group线程组
EventLoopGroup group = new NioEventLoopGroup();
try{
//创建客户端辅助启动类Bootstrap
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>(){
//将ChannelHandler设置到ChannelPipleline中,用于处理网络I/O事件
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
27 ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeClientHandler());
}
});
//发起异步连接操作,然后调用同步方法等待连接成功。
ChannelFuture f = b.connect(host,port).sync(); //等待客户端链路关闭
f.channel().closeFuture().sync();
}finally{
//优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception{
int port = 10001;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
new TimeClient().connect(port, "0.0.0.0");
} }

TimeClientHandler,拿到的msg已经是解码成字符串之后的应答消息:

 import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; public class TimeClientHandler extends ChannelHandlerAdapter{ private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName()); private int counter; private byte[]req; public TimeClientHandler(){
req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
} //当客户端与服务端TCP链路简历成功后,Netty的NIO线程会调用该方法,发送查询时间的指令给服务器
public void channelActive(ChannelHandlerContext ctx){
//将请求消息发送给服务端
ByteBuf message = null;
for(int i = 0;i<100;i++){
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
} //当服务器返回应答消息时,该方法被调用
public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
String body = (String)msg;
System.out.println("Now is:" + body + "; the counter is :" + ++counter);
} public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){ //释放资源
logger.warning("Unexpected exception from downstream :" + cause.getMessage());
ctx.close();
}
}

运行结果:

发现。。就木有粘包或拆包的问题啦~~~~

机缘巧合,同事也一起实现了Scala版~

clientHandler:

 package main.nettyscala

 import io.netty.buffer.{ByteBuf, Unpooled}
import io.netty.channel.{ChannelInboundHandlerAdapter, ChannelHandlerContext, ChannelHandlerAdapter} /**
* Created by root on 2016/11/18.
*/
class ClientHandler extends ChannelInboundHandlerAdapter {
override def channelActive(ctx: ChannelHandlerContext): Unit = {
println("channelActive")
//val content = "hello server"
val content = Console.readLine()
ctx.writeAndFlush(Unpooled.copiedBuffer(content.getBytes("UTF-8")))
//发送case class 不在发送字符串了,封装一个字符串
// ctx.writeAndFlush(RegisterMsg("hello server"))
} override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
println("channelRead")
val byteBuf = msg.asInstanceOf[ByteBuf]
val bytes = new Array[Byte](byteBuf.readableBytes())
byteBuf.readBytes(bytes)
val message = new String(bytes, "UTF-8")
println(message)
} override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
println("channeReadComplete")
ctx.flush()
}
//发送异常时关闭
override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
println("exceptionCaught")
ctx.close()
} }

NettyClient:

 package main.nettyscala

 import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.{NioSocketChannel, NioServerSocketChannel}
import io.netty.handler.codec.serialization.{ClassResolvers, ObjectDecoder, ObjectEncoder} object NettyClient {
def main(args: Array[String]) {
val host = args(0)
val port = args(1).toInt
val client = new NettyClient
client.connect(host, port)
}
} class NettyClient {
def connect(host: String, port: Int): Unit = {
//创建客户端NIO线程组
val eventGroup = new NioEventLoopGroup
//创建客户端辅助启动类
val bootstrap = new Bootstrap
try {
//将NIO线程组传入到Bootstrap
bootstrap.group(eventGroup)
//创建NioSocketChannel
.channel(classOf[NioSocketChannel])
//绑定I/O事件处理类
.handler(new ChannelInitializer[SocketChannel] {
override def initChannel(ch: SocketChannel): Unit = {
ch.pipeline().addLast(
// new ObjectEncoder,
// new ObjectDecoder(ClassResolvers.cacheDisabled(getClass.getClassLoader)),
new ClientHandler
)
}
})
//发起异步连接操作
val channelFuture = bootstrap.connect(host, port).sync()
//等待服务关闭
channelFuture.channel().closeFuture().sync()
} finally {
//优雅的退出,释放线程池资源
eventGroup.shutdownGracefully()
}
}
}

NettyServer:

 package main.nettyscala

 /**
* Created by root on 12/8/16.
*/
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.codec.serialization.{ClassResolvers, ClassResolver, ObjectDecoder, ObjectEncoder} object NettyServer {
def main(args: Array[String]) {
val host = args(0)
val port = args(1).toInt
val server = new NettyServer
server.bind(host, port)
}
}
class NettyServer {
def bind(host: String, port: Int): Unit = {
//配置服务端线程池组
//用于服务器接收客户端连接
val bossGroup = new NioEventLoopGroup()
//用户进行SocketChannel的网络读写
val workerGroup = new NioEventLoopGroup() try {
//是Netty用户启动NIO服务端的辅助启动类,降低服务端的开发复杂度
val bootstrap = new ServerBootstrap()
//将两个NIO线程组作为参数传入到ServerBootstrap
bootstrap.group(bossGroup, workerGroup)
//创建NioServerSocketChannel
.channel(classOf[NioServerSocketChannel])
//绑定I/O事件处理类
.childHandler(new ChannelInitializer[SocketChannel] {
override def initChannel(ch: SocketChannel): Unit = {
ch.pipeline().addLast(
// new ObjectEncoder,
// new ObjectDecoder(ClassResolvers.cacheDisabled(getClass.getClassLoader)),
new ServerHandler
)
}
})
//绑定端口,调用sync方法等待绑定操作完成
val channelFuture = bootstrap.bind(host, port).sync()
//等待服务关闭
channelFuture.channel().closeFuture().sync()
} finally {
//优雅的退出,释放线程池资源
bossGroup.shutdownGracefully()
workerGroup.shutdownGracefully()
}
}
}

ServerHandler:

 package main.nettyscala

 /**
* Created by root on 12/8/16.
*/
import io.netty.buffer.{Unpooled, ByteBuf}
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter} /**
* Created by root on 2016/11/18.
*/
class ServerHandler extends ChannelInboundHandlerAdapter {
/**
* 有客户端建立连接后调用
*/
override def channelActive(ctx: ChannelHandlerContext): Unit = {
println("channelActive invoked")
} /**
* 接受客户端发送来的消息
*/
override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
println("channelRead invoked")
val byteBuf = msg.asInstanceOf[ByteBuf]
val bytes = new Array[Byte](byteBuf.readableBytes())
byteBuf.readBytes(bytes)
val message = new String(bytes, "UTF-8")
println(message)
val back = "received message: " + message
val resp = Unpooled.copiedBuffer(back.getBytes("UTF-8"))
println(msg)
ctx.write(resp)
} /**
* 将消息对列中的数据写入到SocketChanne并发送给对方
*/
override def channelReadComplete(ctx: ChannelHandlerContext): Unit = {
println("channekReadComplete invoked")
ctx.flush()
} }

RegisterMsg:

 package main.nettyscala

 /**
* Created by root on 12/8/16.
*/
case class RegisterMsg(content: String) extends Serializable

运行结果:

Netty的TCP粘包/拆包(源码二)的更多相关文章

  1. netty之==TCP粘包/拆包问题解决之道(一)

    一.TCP粘包/拆包是什么 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在 ...

  2. 1. Netty解决Tcp粘包拆包

    一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...

  3. Netty TCP粘包/拆包问题《二》

    1.DelimiterBasedFrameDecoder:是以分隔符作为结束标志进行解决粘包/拆包问题 代码: EchoClient:客户端 /* * Copyright 2012 The Netty ...

  4. Netty解决TCP粘包/拆包问题 - 按行分隔字符串解码器

    服务端 package org.zln.netty.five.timer; import io.netty.bootstrap.ServerBootstrap; import io.netty.cha ...

  5. 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?

    前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...

  6. Netty(三)TCP粘包拆包处理

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...

  7. Netty(二)——TCP粘包/拆包

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...

  8. Netty使用LineBasedFrameDecoder解决TCP粘包/拆包

    TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...

  9. TCP粘包/拆包(Netty权威指南)

    无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个“流”协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片 ...

随机推荐

  1. KI的斐波那契_DFS

    Description KI十分喜欢美丽而优雅的斐波那契数列,最近他新认识了一种斐波那契字符串,定义如下 f (0) = b, f (1) = a, f (2) = f (1) + f (0) = a ...

  2. Maven学习笔记(1)之安装Maven

    此笔记是学习Maven时自己摸索+各种百度而来,并非全部原创,望与各位一同学习,勿拍~勿拍~ 安装步骤 1.下载Maven的最新版本,地址:http://maven.apache.org/downlo ...

  3. 百川sdk----自己的WebViewClient不被执行

    我在百川sdk的旺旺群中,追问这个问题N多次,一直没有人答复,哎,凡事都要靠自己..... 1.先查看下百川sdk中,是怎么处理咱们传递过去的 WebViewClient public class l ...

  4. C#拼接SQL语句,SQL Server 2005+,多行多列大数据量情况下,使用ROW_NUMBER实现的高效分页排序

    /// <summary>/// 单表(视图)获取分页SQL语句/// </summary>/// <param name="tableName"&g ...

  5. dsfgsdfg

    两融余额止跌回升,金融股回落飘绿,千股涨停续演,沪指收复4000点未果涨逾2% 相关报道 [今日收盘]灾后重建激情抢筹 大盘两日反弹500点 [今日收盘]沪指涨近6%重回3700点 未停牌个股九成涨停 ...

  6. 《深入浅出Node.js》第7章 网络编程

    @by Ruth92(转载请注明出处) 第7章 网络编程 Node 只需要几行代码即可构建服务器,无需额外的容器. Node 提供了以下4个模块(适用于服务器端和客户端): net -> TCP ...

  7. SQL---Chapter01 数据库和SQL

    数据库类型: 层次数据库(Hierarchical Database, HDB) 数据通过层次结构(树形结构)的方式表示出来. 关系型数据库(Relational Database, RDB) 使用专 ...

  8. 医院管理者必须知道的医院客户关系管理(CRM)

    客户关系管理(customer relationship management,CRM)是在二战之后首先由美国IBM.道氏.通用等大型企业提出并运用的一种以有效销售为目的的市场营销思想,其理论基础就是 ...

  9. icmp_ping学习笔记

    1.用字符串指针做为发送缓冲区和接收缓冲区的指针: 2.icmp报文类型结构体可自行定义,也可用<netinet/ip_icmp.h>中定义好的strcut icmp结构体: 3.ip_h ...

  10. jmeter 中的 Beanshell 使用

    一.操作变量:通过使内置对象vars可以对变量进行存取操作 a) vars.get("name"):从jmeter中获得变量值 b) vars.put("key" ...