第四章 TCP粘包/拆包问题的解决之道---4.2--- 未考虑TCP粘包导致功能异常案例

时间:2022-09-15 18:01:31

    4.2 未考虑TCP粘包导致功能异常案例

      如果代码没有考虑粘包/拆包问题,往往会出现解码错位或者错误,导致程序不能正常工作。

      4.2.1 TimeServer 的改造

        Class : TimeServer

package com.phei.netty.chap4;

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 TimeServer { public void bind(int port) throws Exception{
// 配置服务端的NIO线程组
// 创建两个NioEventLoopGroup实例。
// NioEventLoopGroup是个线程组,包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组。
// 一个用于服务端接收客户端的连接,一个用于进行SocketChannel的网络读写。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
// 创建ServerBootstrap对象,是Netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度。
ServerBootstrap b = new ServerBootstrap();
// ServerBootstrap的group方法将两个NIO线程组当作入参传递到ServerBootstrap中。、
// channel方法设置创建的Channel为NioServerSocketChannel,功能对应于JDK NIO类库中的ServerSocketChannel类。
// option方法配置NioServerSocketChannel的TCP参数,此处将它的backlog设置为1024。
// childHandler方法绑定I/O事件的处理类ChildChannelHandler,它的作用类似于Reactor模式中的Handler类,主要用于处理网络I/P事件,例如记录日志、对消息进行编解码等。
b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
// 服务端启动辅助类配置完成之后,调用bind方法绑定监听端口。
// 同步阻塞方法sync等待绑定操作完成,并返回一个ChannelFuture。
// ChannelFuture功能类似于JDK的java.util.concurrent.Future,主要用于异步操作的通知回调。
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
// 进行阻塞,等待服务端链路关闭之后main函数才退出。
f.channel().closeFuture().sync();
}finally{
// 优雅退出,释放线程资源
// 调用NIO线程组的shutdownGraceFully进行优雅退出,它会释放跟shutdownGracefully相关联的资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) throws Exception{
int port = 8080;
if(null != args && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
// 采用默认值
port = 8080;
}
}
new TimeServer().bind(port);
}
}

      Class : TimeServerHandler

package com.phei.netty.chap4;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import java.util.Date; public class TimeServerHandler extends ChannelHandlerAdapter { private int counter; /* 每读到一条消息后,就计一次数,然后发送应答消息给客户端。
* 按照设计,服务端接到的消息总数应该跟客户端发送的消息总数相同,
* 而且请求消息删除回车换行符后应该为"QUERY TIME ORDER"。*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8").substring(0,req.length - System.getProperty("line.separator").length());
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
} }

      4.2.2 TimeClient 的改造

        Class : TimeClient

package com.phei.netty.chap4;

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; public class TimeClient { public void connect(int port,String host) throws Exception{
// 配置客户端NIO线程组
// 创建客户端处理I/O读写的NioEventLoopGroup线程组
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>(){ /* initChannel方法:当创建NioSocketChannel成功之后,进行初始化时
将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件*/
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
// 发起异步连接操作
// 调用connect方法发起异步连接,然后调用同步方法等待连接成功
ChannelFuture f = b.connect(host,port).sync();
// 等待客户端链路关闭
// 当客户端连接关闭之后,客户端主函数退出,退出之前释放NIO线程组的资源。
f.channel().closeFuture().sync();
}finally{
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception{
int port = 8080;
if(null != args && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
// 采用默认值
}
}
new TimeClient().connect(port, "127.0.0.1");
}
}

        Class : TimeClientHandler

package com.phei.netty.chap4;

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();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
/* 客户端跟服务端链路建立成功之后,循环发100条消息,
* 每发送一条就刷新一次,保证每条消息都会被写入Channel中。*/
for(int i = 0;i < 100;i++){
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
/*客户端每接收到服务端一条应答消息之后,就打印一次计数器。*/
System.out.println("Now is : " + body + " ; the counter is : " + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
// 释放资源
logger.warning("Unexpected exception from downstream : " + cause.getMessage());
ctx.close();
} }

        Console : 服务端

The time server receive order : QUERY TIME ORDER
此处省略55行QUERY TIME ORDER......
QUERY TIME ORD ; the counter is : 1
The time server receive order :
此处省略42行QUERY TIME ORDER......
QUERY TIME ORDER ; the counter is : 2

        服务端运行结果表明它只接收到了两条消息,第一条包含57条”QUERY TIME ORDER“指令,第二条包含了43条”QUERY TIME ORDER”指令,总数正好是100条。我们期待的是收到100条消息,每条包含一条“QUERY TIME ORDER”指令。这说明发生了TCP粘包。

        Console : 客户端

Now is : BAD ORDER
BAD ORDER
; the counter is : 1

        按照设计初衷,客户端应该收到100条当前系统时间的消息,但实际上只收到了一条。这不难理解,因为服务端只收到了2条消息,所以实际服务端只发送了2条应答,由于请求消息不满足查询条件,所以返回了2条“BAD ORDER”应答消息。但实际上客户端只收到了一条包含2条“BAD ORDER”指令的消息,说明服务端返回的应答消息也发生了粘包。

        可以通过Netty的LineBasedFrameDecoder和StringDecoder来解决TCP粘包问题。

啦啦啦

啦啦啦

第四章 TCP粘包/拆包问题的解决之道---4.2--- 未考虑TCP粘包导致功能异常案例的更多相关文章

  1. 第四章 TCP粘包&sol;拆包问题的解决之道---4&period;1---

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

  2. TCP粘包&sol;拆包问题的解决

    TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...

  3. (入门篇 NettyNIO开发指南)第四章-TIP黏包&sol;拆包问题解决之道

    熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...

  4. Netty(二)——TCP粘包&sol;拆包

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

  5. TCP粘包&sol;拆包问题

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

  6. TCP粘包&sol;拆包(Netty权威指南)

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

  7. &lt&semi;Netty&gt&semi;(入门篇)TIP黏包&sol;拆包问题原因及换行的初步解决之道

    熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...

  8. TCP 粘包拆包

    一.什么是粘包拆包? 粘包拆包是TCP协议传输中一种现象概念.TCP是传输层协议,他传输的是“流”式数据,TCP并不知道传输是哪种业务数据,或者说,并不关心.它只是根据缓冲区状况将数据进行包划分,然后 ...

  9. Netty2:粘包&sol;拆包问题与使用LineBasedFrameDecoder的解决方案

    什么是粘包.拆包 粘包.拆包是Socket编程中最常遇见的一个问题,本文来研究一下Netty是如何解决粘包.拆包的,首先我们从什么是粘包.拆包开始说起: TCP是个"流"协议,所谓 ...

随机推荐

  1. 【POJ 1151】Atlantis

    离散化后扫描线扫一遍. 夏令营时gty学长就讲过扫描线,可惜当时too naive,知道现在才写出模板题. 当时也不会线段树啊233 #include<cstdio> #include&l ...

  2. Python之调用函数

    Python之调用函数 Python内置了很多有用的函数,我们可以直接调用. 要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数 abs,它接收一个参数. 可以直接从Python的官方网站查 ...

  3. php中base64&lowbar;decode与base64&lowbar;encode加密解密函数

    php中base64_decode与base64_encode加密解密函数,实例分析了base64加密解密函数的具体用法,具有一定的实用价值,需要的朋友可以参考下 本文实例讲述了php中base64_ ...

  4. Goods transportation

    Goods transportation time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  5. Oracle子查询中any、some、all之间的区别

    用some,any和all对子查询中返回的多行结果进行处理. 下面我们来简单介一下这几个关键词的含义. * Some在此表示满足其中一个的意义,是用or串起来的比较从句. * Any也表示满足其中一个 ...

  6. 侧滑面板(对viewGroup的自定义)

    额,好吧,最近一直在做侧滑的事情,到目前为止一共是学了三种方法了,一个是直接加第三方开源框架SlidingMenu,第二给是用DrawerLayout,今天这个是用谷歌官方提供的在新的support- ...

  7. Nginx从听说到学会

    第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的"同行"Apache吧!Nginx同Apache一样都是一种WEB服务器.基于REST架构风格,以统一 ...

  8. Django REST framework基础:分页

    DRF分页组件 为什么要使用分页 我们数据表中可能会有成千上万条数据,当我们访问某张表的所有数据时,我们不太可能需要一次把所有的数据都展示出来,因为数据量很大,对服务端的内存压力比较大还有就是网络传输 ...

  9. &period;NET Threadpool的一点认识

    说到.NET Threadpool我想大家都知道,只是平时比较零散,顾现在整理一下: 一码阻塞,万码等待:ASP.NET Core 同步方法调用异步方法“死锁”的真相 .NET Threadpool ...

  10. Wireshark按照域名过滤

    HTTP协议 http.host == "http://baidu.net" DNS协议 dns.qry.name=="www.baidu.com"