当前博客:05、桌面端TCP之文件传输分包组包

153 次浏览【转载需注明来源】

博客作者:【快码FOF编程】

个性签名:寒窗苦读十年一朝凤舞九天

在软件开发技术中,通常会遇见需要客户端向服务端传输数据文件的需求;想要实现这样的需求也可以使用TCP来实现。

首先各位同学可以创建最基础的TCP客户端及服务端代码,如下:

变量 服务端 = TCP服务端.创建TCP服务({ "端口": 3000, "地址": "0.0.0.0", "最大连接数": 1000, "是否组包": 真 }, (反馈信息) => {
	调试输出(反馈信息);
});
变量 客户端句柄 = TCP客户端.连接服务器({ "端口": 3000, "地址": "127.0.0.1" }, (反馈信息) => {
	调试输出(反馈信息);
});

接下来创建一个函数,这个函数用以向服务器发送文件,代码如下:

定义函数 发送文件给服务端() {
	局部变量 文件数据 = 文件操作类.读入文件("某个文件地址", "");
	TCP客户端.发送数据(客户端句柄, 文件数据);
}

知识点:文件操作类.读入文件("某个文件地址", "");这段代码 读入文件的第二个参数传入空文本的情况下会直接将文件读入成缓冲区数据,然后将缓冲区的数据发送给服务端。

知识点:如果文件比较大那么服务端收到的数据是一段一段的,这是因为TCP会自动进行大文件的切片,将数据切割成一段一段的小数据发送,这样就可以提高发送效率。

 

重点:如果想要服务端直接接收完整的数据可不可以呢?

答案是可以的,此时我们需要使用数据组包类,这个类就是专门用来处理网络交互时的数据组装

数据组包类常用的有五个命令,分别是:新建组包、新增包数据、获取包数据、清空包数据、是否组包完毕。

要理解这五个命令的使用方法,首先要理解一下数据组包类的原理,数据组包类的原理是当服务端收到数据时,使用新建组包命令在内存中开辟一段空间,然后本次传输任务的所有切片数据都可以使用新增包数据这个命令新增到开辟的空间中。

数据组包类确认本次所有的切片数据都接收完毕时,使用获取包数据这个命令就可以获取到完整的客户端本次传输的数据。

想要知道本次所有的切片数据是否都已经接收并组包完毕,可以使用是否组包完毕这个命令来进行判断,组包完毕返回真,否则返回假。

同时如果组包已经完毕,并且也获取了包数据后,可以使用清空包数据这个命令清空开辟空间中的数据,让程序的内存得到释放!

 

重点:实战文件传输

上面了解数据组包类的原理后,我们开始来实战编写数据组包的相关代码

首先是客户端向服务端发送文件数据的代码:

定义函数 发送文件给服务端() {
	局部变量 文件数据 = 文件操作类.读入文件("某个文件地址", "");
	文件数据 = 缓冲区类.缓冲区数据转数组(文件数据);
	文件数据.插入或删除成员(0, 0, 123, 124, 125);
	文件数据[文件数据.长度] = 123;
	文件数据[文件数据.长度] = 124;
	文件数据[文件数据.长度] = 125;
	文件数据 = 缓冲区类.数组转缓冲区数据(文件数据);
	TCP客户端.发送数据(客户端句柄, 文件数据);
}

知识点:读入文件后使用"缓冲区类.缓冲区数据转数组()"命令将文件数据转换成数组数据,然后在给数组的头部尾部分别加上标识;这个标识可以自定义,例如上方代码使用的标识时123,124,125这三个成员。

注意:成员标识的值不能超过255,需要控制在1-255之间

接下来实现服务端的数据接收并组包:

变量 服务端 = TCP服务端.创建TCP服务({ "端口": 3000, "地址": "0.0.0.0", "最大连接数": 1000, "是否组包": 真 }, (反馈信息) => {
	如果 (反馈信息["状态"] == "收到数据") {
		局部变量 收到数据 = 反馈信息["数据内容"];
		收到数据 = 缓冲区类.缓冲区数据转数组(收到数据);
		局部变量 客户标识 = 反馈信息["本地IP地址"] + 反馈信息["进程端口地址"];
		如果 (收到数据[0] == 123 && 收到数据[1] == 124 && 收到数据[2] == 125) {
			数据组包类.新建组包(客户标识, [123, 124, 125], [123, 124, 125]);
			数据组包类.新增包数据(客户标识, 收到数据);
		} 否则 {
			数据组包类.新增包数据(客户标识, 收到数据);
		}
		如果 (数据组包类.是否组包完毕(客户标识) == 真) {
			收到数据 = 数据组包类.获取包数据(客户标识);
			数据组包类.清空包数据(客户标识);
			文件操作类.写到文件("保存文件地址", 收到数据);
			返回 { "是否反馈": 真, "反馈数据": "你好客户端,已经收到你的文件", "是否断开": 假 };
		}
	}
});

知识点:首先使用【如果 (反馈信息["状态"] == "收到数据")】这个命令来判断任务是否是收到数据

知识点:然后将收到的切片数据取出来并赋值给一个局部变量,再使用"缓冲区类.缓冲区数据转数组()"这个命令将收到的切片数据转换成数组

知识点:通过服务端收到的信息,获取到客户端的IP和进程端口地址,用作"数据组包类.新建组包()"命令的标识

上面三个知识点的具体代码如下:

局部变量 收到数据 = 反馈信息["数据内容"];
收到数据 = 缓冲区类.缓冲区数据转数组(收到数据);
局部变量 客户标识 = 反馈信息["本地IP地址"] + 反馈信息["进程端口地址"];

知识点:接下来使用如果 (收到数据[0] == 123 && 收到数据[1] == 124 && 收到数据[2] == 125) 判断是否是数据头,这里大家还有印象么?客户端发送数据的时候定义了头部数据和尾部数据,这里就是用来进行头部数据的判断;当确认是头部数据后,就使用新建组包新增包数据命令进行空间的开辟和数据的新增。

如果 (收到数据[0] == 123 && 收到数据[1] == 124 && 收到数据[2] == 125) {
	数据组包类.新建组包(客户标识, [123, 124, 125], [123, 124, 125]);
	数据组包类.新增包数据(客户标识, 收到数据);
} 否则 {
	数据组包类.新增包数据(客户标识, 收到数据);
}

知识点:如果不是第一次的头部数据就会执行到否则区域,在否则区域中,就代表是头部后的切片数据,直接进行新增包数据命令添加即可。

其实代码写到这里,组包的初步代码就已经实现了,接下来只需要在组包的下面去判断是否组包完毕,组包完毕就通知客户端即可;代码如下:

如果 (数据组包类.是否组包完毕(客户标识) == 真) {
	收到数据 = 数据组包类.获取包数据(客户标识);
	数据组包类.清空包数据(客户标识);
	文件操作类.写到文件("保存文件地址", 收到数据);
	返回 { "是否反馈": 真, "反馈数据": "你好客户端,已经收到你的文件", "是否断开": 假 };
}

知识点:如果组包已经完毕,那么就代表本次传输的所有数据都已经完毕,然后使用获取包数据命令将本次组包的所有数据获取,并清空包数据释放内存,然后在进行相关的数据操作,例如写出文件等;到此时客户端向服务器发送文件等操作就已经彻底完成了。

 

本章节案例下载地址:案例下载地址

 

完整代码参考如下:

<!文档类型 网页类型>
<网页 语言代码="中文">
	<网页头部>
		<网页信息 文档编码="UTF8" />
		<网页信息 名称="页面视图" 关联数据="视图宽度=填充视图宽度,初始缩放值=1,最大缩放值=1,用户缩放状态=假" />
		<网页信息 关联HTTP="兼容模式" 关联数据="最高IE版本" />
		<网页标题>window窗口</网页标题>
	</网页头部>
	<网页主体>
		<!--这里编写网页代码-->
		<区块 id="窗口">
			<区块 类名="窗口标题">
				<区块 类名="标题" 行内样式="@窗口_允许拖动;">
					<标题1>FOFStudio</标题1>
				</区块>
				<区块 类名="工具">
					<区块 类名="最小化" 点击回调="窗口类.最小化()"></区块>
					<区块 类名="最大化" 点击回调="窗口类.最大化()"></区块>
					<区块 类名="关闭" 点击回调="窗口类.关闭窗口()"></区块>
				</区块>
			</区块>
			<区块 类名="窗口内容">
				<!--请在此编写窗口内容-->
				<按钮 点击回调="发送文件给服务端()">发送数据</按钮>
			</区块>
		</区块>
		<脚本>
			调试器窗口类.打开调试器窗口();
            变量 服务端 = TCP服务端.创建TCP服务({ "端口": 3000, "地址": "0.0.0.0", "最大连接数": 1000, "是否组包": 真 }, (反馈信息) => {
            	如果 (反馈信息["状态"] == "收到数据") {
            		局部变量 收到数据 = 反馈信息["数据内容"];
            		收到数据 = 缓冲区类.缓冲区数据转数组(收到数据);
            		局部变量 客户标识 = 反馈信息["本地IP地址"] + 反馈信息["进程端口地址"];
            		如果 (收到数据[0] == 123 && 收到数据[1] == 124 && 收到数据[2] == 125) {
            			数据组包类.新建组包(客户标识, [123, 124, 125], [123, 124, 125]);
            			数据组包类.新增包数据(客户标识, 收到数据);
            		} 否则 {
            			数据组包类.新增包数据(客户标识, 收到数据);
            		}
            		如果 (数据组包类.是否组包完毕(客户标识) == 真) {
            			收到数据 = 数据组包类.获取包数据(客户标识);
            			数据组包类.清空包数据(客户标识);
            			文件操作类.写到文件("保存文件地址", 收到数据);
            			返回 { "是否反馈": 真, "反馈数据": "你好客户端,已经收到你的文件", "是否断开": 假 };
            		}
            	}
            });
			变量 客户端句柄 = TCP客户端.连接服务器({ "端口": 3000, "地址": "127.0.0.1" }, (反馈信息) => {
				调试输出(反馈信息);
			});
            定义函数 发送文件给服务端() {
            	局部变量 文件数据 = 文件操作类.读入文件("某个文件地址", "");
            	文件数据 = 缓冲区类.缓冲区数据转数组(文件数据);
            	文件数据.插入或删除成员(0, 0, 123, 124, 125);
            	文件数据[文件数据.长度] = 123;
            	文件数据[文件数据.长度] = 124;
            	文件数据[文件数据.长度] = 125;
            	文件数据 = 缓冲区类.数组转缓冲区数据(文件数据);
            	TCP客户端.发送数据(客户端句柄, 文件数据);
            }
		</脚本>
	</网页主体>
</网页>
默认排序
Generic placeholder image
Generic placeholder image
快码FOF编程 Time: 2023-05-12 22:04:06

这篇文章太重要了,大家一定要认真学习


05、桌面端TCP之文件传输分包组包