MINA、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

拆解一个800G光模块:手把手带你认识里面的MT插芯和光纤阵列(FA) 本文详细拆解了800G光模块中的关键组件MT插芯和光纤阵列(FA),揭示了并技术如何实现高速数据传输。通过显微镜下的精密观察,展示了MT插芯的高精度构造和光纤阵列的连接奥秘,同时解析了高精度研磨工艺对光信号传输的重要性。这些微型组件是高速收模块实现800Gbps带宽的核心技术。 阅读详情

 

在TCP连接开始到结束连接,之间可能会多次传输数据,也就是服务器和客户端之间可能会在连接过程中互相传输多条消息。理想状况是一方每发送一条消息,另一方就立即接收到一条,也就是一次write对应一次read。但是,现实不总是按照剧本来走。

MINA官方文档节选:

TCP guarantess delivery of all packets in the correct order. But there is no guarantee that one write operation on the sender-side will result in one read event on the receiving side. One call of IoSession.write(Object message) by the sender can result in multiple messageReceived(IoSession session, Object message) events on the receiver; and multiple calls of IoSession.write(Object message) can lead to a single messageReceived event.

Netty官方文档节选:

In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer. Unfortunately, the buffer of a stream-based transport is not a queue of packets but a queue of bytes. It means, even if you sent two messages as two independent packets, an operating system will not treat them as two messages but as just a bunch of bytes. Therefore, there is no guarantee that what you read is exactly what your remote peer wrote.

上面两段话表达的意思相同:TCP是基于字节流的协议,它只能保证一方发送和另一方接收到的数据的字节顺序一致,但是,并不能保证一方每发送一条消息,另一方就能完整的接收到一条信息。有可能发送了两条对方将其合并成一条,也有可能发送了一条对方将其拆分成两条。所以在上一篇博文中的Demo,可以说是一个错误的示范。不过服务器和客户端在同一台机器上或者在局域网等网速很好的情况下,这种问题还是很难测试出来。

举个简单了例子(这个例子来源于Netty官方文档):

消息发送方发送了三个字符串:

消息发送方
但是接收方收到的可能是这样的:

消息接收方
那么问题就很严重了,接收方没法分开这三条信息了,也就没法解析了。

对此,MINA的官方文档提供了以下几种解决方案:

1、use fixed length messages

使用固定长度的消息。比如每个长度4字节,那么接收的时候按每条4字节拆分就可以了。

2、use a fixed length header that indicates the length of the body

使用固定长度的Header,Header中指定Body的长度(字节数),将信息的内容放在Body中。例如Header中指定的Body长度是100字节,那么Header之后的100字节就是Body,也就是信息的内容,100字节的Body后面就是下一条信息的Header了。

3、using a delimiter; for example many text-based protocols append a newline (or CR LF pair) after every message

使用分隔符。例如许多文本内容的协议会在每条消息后面加上换行符(CR LF,即”\r\n”),也就是一行一条消息。当然也可以用其他特殊符号作为分隔符,例如逗号、分号等等。

当然除了上面说到的3种方案,还有其他方案。有的协议也可能会同时用到上面多种方案。例如HTTP协议,Header部分用的是CR LF换行来区分每一条Header,而Header中用Content-Length来指定Body字节数。

下面,分别用MINA、Netty、Twisted自带的相关API实现按换行符CR LF来分割消息。

MINA

MINA可以使用ProtocolCodecFilter来对发送和接收的二进制数据进行加工,如何加工取决于ProtocolCodecFactory或ProtocolEncoder、ProtocolDecoder,加工后在IoHandler中messageReceived事件函数获取的message就不再是IoBuffer了,而是你想要的其他类型,可以是字符串,Java对象。这里可以使用TextLineCodecFactory(ProtocolCodecFactory的一个实现类)实现CR LF分割消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class TcpServer {

	public static void main(String[] args) throws IOException {
		IoAcceptor acceptor = new NioSocketAcceptor();

		// 添加一个Filter,用于接收、发送的内容按照"\r\n"分割
		acceptor.getFilterChain().addLast("codec",
				new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), "\r\n", "\r\n")));

		acceptor.setHandler(new TcpServerHandle());
		acceptor.bind(new InetSocketAddress(8080));
	}

}

class TcpServerHandle extends IoHandlerAdapter {

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		cause.printStackTrace();
	}

	// 接收到新的数据
	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {

		// 接收客户端的数据,这里接收到的不再是IoBuffer类型,而是字符串
		String line = (String) message;
		System.out.println("messageReceived:" + line);

	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		System.out.println("sessionCreated");
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		System.out.println("sessionClosed");
	}
}

 

Netty

Netty设计上和MINA类似,需要在ChannelPipeline加上一些ChannelHandler用来对原始数据进行处理。这里用LineBasedFrameDecoder将接收到的数据按行分割,StringDecoder再将数据由字节码转成字符串。同样,接收到的数据进过加工后,在channelRead事件函数中,msg参数不再是ByteBuf而是String。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class TcpServer {

	public static void main(String[] args) throws InterruptedException {
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup)
					.channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						public void initChannel(SocketChannel ch)
								throws Exception {
							ChannelPipeline pipeline = ch.pipeline();

							// LineBasedFrameDecoder按行分割消息
							pipeline.addLast(new LineBasedFrameDecoder(80));
							// 再按UTF-8编码转成字符串
							pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));

							pipeline.addLast(new TcpServerHandler());
						}
					});
			ChannelFuture f = b.bind(8080).sync();
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

}

class TcpServerHandler extends ChannelInboundHandlerAdapter {

	// 接收到新的数据
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {

		// msg经过StringDecoder后类型不再是ByteBuf而是String
		String line = (String) msg;
		System.out.println("channelRead:" + line);
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) {
		System.out.println("channelActive");
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) {
		System.out.println("channelInactive");
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

 

Twisted

Twisted的设计和上面两者的设计不太一样,所以实现消息分割也不太一样。处理事件的类TcpServerHandle不再继承Protocol,而是继承Protocol的子类LineOnlyReceiver。接收到新数据的事件方法也不再是dataReceived,而是LineOnlyReceiver提供的lineReceived。看Twisted源码的话可以发现LineOnlyReceiver的内部实际上自己实现了dataReceived,然后将其按行分割,有新的一行数据就调用lineReceived。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding:utf-8 –*-

from twisted.protocols.basic import LineOnlyReceiver
from twisted.internet.protocol import Factory
from twisted.internet import reactor

class TcpServerHandle(LineOnlyReceiver):

    # 新的连接建立
    def connectionMade(self):
        print 'connectionMade'

    # 连接断开
    def connectionLost(self, reason):
        print 'connectionLost'

    # 接收到新的一行数据
    def lineReceived(self, data):
        print 'lineReceived:' + data

factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()

 

下面用一个Java客户端对三个服务器进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TcpClient {

	public static void main(String[] args) throws IOException {

		Socket socket = null;
		OutputStream out = null;

		try {

			socket = new Socket("localhost", 8080);
			out = socket.getOutputStream();

			// 请求服务器
			String lines = "床前明月光\r\n疑是地上霜\r\n举头望明月\r\n低头思故乡\r\n";
			byte[] outputBytes = lines.getBytes("UTF-8");
			out.write(outputBytes);
			out.flush();

		} finally {
			// 关闭连接
			out.close();
			socket.close();
		}

	}

}

 

MINA服务器输出结果:

sessionCreated
messageReceived:床前明月光
messageReceived:疑是地上霜
messageReceived:举头望明月
messageReceived:低头思故乡
sessionClosed

Netty服务器输出结果:

channelActive
channelRead:床前明月光
channelRead:疑是地上霜
channelRead:举头望明月
channelRead:低头思故乡
channelInactive

Twisted服务器输出结果:

connectionMade
lineReceived:床前明月光
lineReceived:疑是地上霜
lineReceived:举头望明月
lineReceived:低头思故乡
connectionLost

当然,测试的时候也可以将发送的数据模拟成不按规则分割的情况,下面用一个更变态的客户端来测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class TcpClient {

	public static void main(String[] args) throws IOException, InterruptedException {


		Socket socket = null;
		OutputStream out = null;

		try{

			socket = new Socket("localhost", 8080);  
			out = socket.getOutputStream();

			String lines = "床前";
			byte[] outputBytes = lines.getBytes("UTF-8");
			out.write(outputBytes);
			out.flush();

			Thread.sleep(1000);

			lines = "明月";
			outputBytes = lines.getBytes("UTF-8");
			out.write(outputBytes);
			out.flush();

			Thread.sleep(1000);

			lines = "光\r\n疑是地上霜\r\n举头";
			outputBytes = lines.getBytes("UTF-8");
			out.write(outputBytes);
			out.flush();

			Thread.sleep(1000);

			lines = "望明月\r\n低头思故乡\r\n";
			outputBytes = lines.getBytes("UTF-8");
			out.write(outputBytes);
			out.flush();

		} finally {
			// 关闭连接
			out.close();
			socket.close();
		}

	}

}

 

再次分别测试上面三个服务器,结果和上面的输出结果一样,没有任何问题。

地址:  http://xxgblog.com/2014/08/21/mina-netty-twisted-2/

RKNN3588——YOLOv8的PT模型转RKNN模型 1. 首先克隆rknn修改后的ultralytics版本项目到本地主要是修改了源码的ultralytics/nn/modules/head.py和ultralytics/engine/exporter.py两个文件。2. 使用修改后的ultralytics对pt模型进模型转换,此处的format=rknn代表支持rknn后续的转换,而不是用onnx,一定要注意!!! 阅读详情

相关推荐

Centos 7 Linux 离线安装使用 MySQL 5.7:下载、安装、配置、使用

本文详细介绍了在 CentOS 7 系统下离线安装 MySQL 5.7 的完整流程,包括环境准备、安装包下载、用户与目录配置、数据库初始化、服务启动与自启设置、root 密码修改、远程访问开启及防火墙配置等关键步骤,并提供了本地与远程客户端(如 Navicat、DBeaver)的连接使用方法,适合无网络环境下的数据库部署参考。

寒水馨 · 轻量级技术文档 · hanshuixin.org 6070

MinaNettyTwisted一起):TCP消息边界问题及按分割消息

TCP连接开始到结束连接,之间可能会多次传输数据,也就是服务器和客户端之间可能会在连接过程中互相传输多条消息。理想状况是一方每一条消息,另一方就立即接收到一条,也就是一次write对应一次read。但是,现实不总是按照剧本来走。

叉叉哥的BLOG 5428

基于Simulink的电力电子负载特性模拟仿真实例

电阻性负载是最简单的负载形式,其阻抗为纯电阻,电流和电压之间的关系符合欧姆定律。电阻性负载的特点是消耗能量,不会存储能量。本示例将以一个典型的直流电源供电系统为例,展示如何利用Simulink进不同负载特性的仿真模拟。直流电源:提供固定电压输入。逆变器(可选):根据需要产生PWM调制信号,驱动负载。负载模块:模拟不同类型的负载特性,如电阻性负载、电感性负载和容性负载。测量模块:用于观察负载上的电压和电流波形。系统架构视图plaintext深色版本v| || |

xiaoheshang_123的博客 413

Netty篇:拆包与粘包

TCP粘包/拆包 问题描述        TCP是个“流”协议,所谓流,就是没有界限的一串数据。一个业务上完整的包可能会被TCP拆分过个包进送,也有可能把多个小的包封装一个大的数据包送,这就是所谓的TCP粘包和拆包问题。 解决策略 (1)消息定长,每个报文大小固定,不够则空位补齐 (2)在包尾增加回车换...

走在路上 260

使用DelimiterBasedFrameDecoder进消息分隔

在使用NettyTCP消息传输时,为了上层协议能够对消息正确区分,避免粘包和拆包导致的问题,一般可以通过消息定长、将回车换符作为消息结束符、将特殊的分隔符作为消息的结束标志或者在消息头中定义长度字段来标识消息的总长度。其中常用的通过分隔符作为消息的结束标志就涉及到Netty的DelimiterBasedFrameDecoder类,服务端如下: import io.netty.bootst...

Terisadeng的博客 3234

批量消息消息列表分割

批量消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。 送批量消息 public class BatchProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("batch_.

学而不思则忘 765

MinaNettyTwisted一起(三):TCP消息固定大小的前缀(Header)

在上一篇博文中,有介绍到用换分割消息的方法。但是这种方法有个小问题,如果消息中本身就包含换符,那将会将这条消息分割两条,结果就不对了。 本文介绍另外一种消息分割方式,即上一篇博文中讲的第2条:use a fixed length header that indicates the length of the body,用一个固定字节数的Header前缀来指定Body的字节数,以此来分割消息

叉叉哥的BLOG 4648

MinaNettyTwisted一起(五):整合protobuf

protobuf是谷歌的Protocol Buffers的简称,用于结构化数据和字节码之间互相转换(序列化、反序列化),一般应用于网络传输,可支持多种编程语言。protobuf如何使用这里不再介绍,本文主要介绍在MINANettyTwisted中如何使用protobuf,不了解protobuf的同可以去参考我的另一篇博文。在前面的一篇博文中,有介绍到一种用一个固定为4字节的前缀Header来

叉叉哥的BLOG 7208

MinaNettyTwisted一起(四):定制自己的协议

在前面的博文中,介绍一些消息分割的方案,以及MINANettyTwisted针对这些方案提供的相关API。例如MINA的TextLineCodecFactory、PrefixedStringCodecFactory,Netty的LineBasedFrameDecoder、LengthFieldBasedFrameDecoder,Twisted的LineOnlyReceiver、Int32StringReceiver。 除了这些方案,还有很多其他方案,当然也可以自己定义。在这里,我们定制一个自己的方案,并

叉叉哥的BLOG 4628

MINANettyTwisted一起(四):定制自己的协议

在前面的博文中,介绍一些消息分割的方案,以及MINANettyTwisted针对这些方案提供的相关API。例如MINA的TextLineCodecFactory、PrefixedStringCodecFactory,Netty的LineBasedFrameDecoder、LengthFieldBasedFrameDecoder,Twisted的LineOnlyReceiver、Int32Str...

KHOST的博客 315

NettyMINATwisted一起系列04:定制自己的协议

文章已获得作者授权,原文地址:xxgblog.com/2014/08/25/mina-netty-twisted-4/在前面的文章中,介绍一些消息分割的方案,以及 MINANettyTwisted 针对这些方案提供的相关AP...

cjx93224的博客 211

消息分割

  短消息分割 1.在送短信时,一般是一条短信70汉字,当超过70个汉字时后面的并不能显示出来,这就用到了短信分割。代码如下(分割函数)    Var SplitStr: TStrings;//在全局中声明    procedure TFrmMain.Split(aStr: WideString;aPos: Integer);begin    SplitStr := TS

matail的专栏 858

通信:帧与解析

帧与解析 当然,将数据转换在线路上传输的格式只完了一半工作,在接收端还必须将接收到的字节序列还原原始信息。应用程序协议通常处理的是由一组字段组的离散的信息。帧(Framing)技术则解决了接收端如何定位消息的首尾位置的问题。无论信息是编码了文本、多字节进制数、或是两者的结合,应用程序协议必须指定消息的接收者如何确定何时消息已完整接收。 如果一条完整的消息负载在一个D...

爱.NET 626

Python Twisted 框架中 socket通信

转载:http://blog.csdn.net/jackyyen/archive/2009/04/13/4069887.aspx // 部分一 Twisted使用了更多的基于事件的方式。要写一个基本的服务器,你要实现事件处理器,它处理诸如一个新的客户端连接、新的数据到达和客户端连接中断等情况。在Twisted中,你的事件处理器定义在一个protocol中;你也需要一个factory,当一个新的

1581

MinaNettyTwisted一起(一):实现简单的TCP服务器

MINANettyTwisted为什么放在一起习?它们的共同特点:event-driven以及asynchronous。它们都是事件驱动、异步的网络编程框架。由此可见,它们之间的共同点还是很明显的。所以我这里将这三个框架放在一起,实现相同的功能,不但可以用少量的精力三样东西,而且还可以对它们之间进各方面的对比。

叉叉哥的BLOG 9930

Python Twisted介绍

原文链接:http://www.aosabook.org/en/twisted.html 作者:Jessica McKellar Twisted是用Python实现的基于事件驱动的网络引擎框架。Twisted诞生于2000年初,在当时的网络游戏开者看来,无论他们使用哪种语言,手中都鲜有可兼顾扩展性及跨平台的网络库。Twisted的作者试图在当时现有的环境下开游戏,这一步走的非常...

weixin_30381793的博客 1608

php 字符串的四种表达方式-两种定界符(界定符)

完全转载:http://php.net/manual/zh/language.types.string.php String 字符串 一个字符串 string 就是由一系列的字符组,其中每个字符等同于一个字节。这意味着 PHP 只能支持 256 的字符集,因此不支持 Unicode 。详见字符串类型详解。 Note: string 最大可以达到 2GB。

zhengwish的专栏 1万+

上市公司注册地所在地数据(1991-2023年).rar

该数据集涵盖1991年至2023年中国A股上市公司的注册地地理位置信息,覆盖沪深北三大证券交易所的主板、中小板、创业板、科创板及北证板块的全部上市公司。数据内容包含证券代码、股票简称、上市日期、注册地址及办公地址的省份、城市、区县三级政区划信息,部分数据版本还提供注册地经纬度坐标(基于BD-90坐标系)及地址前后期补全记录[citation:4][citation:5][citation:6]。数据样本量超过5300家企业,包含7.3万个观测值,覆盖全国31个省市自治区,其中广东、浙江、江苏等省份的上市公司数量位居前列[citation:5]。数据来源基于上市公司年报、公告及监管部门公开信息整理,部分版本还标注了企业是否属于ST/PT状态、股权性质及业分类代码[citation:6][citation:8]。该数据集可用于分析区域经济展与资本市场关联性,支持城市竞争力评估、企业地理分布研究及政策效果追踪等社科领域研究,亦为投资者评估企业区位优势提供基础数据支撑[citation:5][citation:8]。

UPS的性能分类与标准化UPS系统结构.pdf

UPS的性能分类与标准化UPS系统结构.pdf

上一篇: MINA、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)
下一篇: MINA、Netty、Twisted一起学(一):实现简单的TCP服务器
K'illCode
博客等级 码龄9年 763粉丝 838原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值