上一篇建立了TCP通信,其核心部分是解决不知道服务器的IP地址等等信息,通过广播的方式先进行沟通(握手),然后获得建立TCP的必要信息(IP地址,端口)。这篇文章,介绍图片传送的部分。
2 简单通信协议和数据发送
note:如果发送小文件,或者人工控制发送,只需要发送接收基本课可以解决问题,但是如果批量发送,文件比较大的情况下,建立一个简单的通信协议就非常重要了。
通信协议主要解决如下问题:
2.1 发送数据的包头定义和识别
给发送数据一个名称,不但可以表达清晰的数据属性,例如图片名称,还有助于调试;
发送数据的大小是方便在批量发送时,接收端判断数据的归属,将同一个数据包拼在一起。
笔者制定的简单协议如下:
2.1.1 发送数据包的包头定义
采用一个json格式字符串,如下
{
“NAME”:“SVR_IP”,
“LEN”:24
}
包括名称和长度两部分
2.1.2 包头的识别正则表达式
采用正则表达式对包头进行识别
其 pattern 字符串如下
static const m_MSPHeadRegPattern = '{"NAME":"(?<name>[A-z_]+)","LEN":(?<len>[0-9]+)}';
note:该定义主要是在客户端使用,服务端仅仅使用包头定义。
2.2 发送端发送流程
2.2.1 发送流程
- 获得发送的图片
- 图片转换成byte数组
- 根据数组大小成成包头信息
- 发送包头
2.2.2 发送代码
note:省略获取图片的代码
// data 图片数据, cmd 上面提到的发送命令, socket, 建立好的socket
private static void SendJsonDataToRmt(byte[] data, string cmd, Socket socket)
{
//byte[] headArr = new byte[13];
var datlen = data.Length;
//appcfg 下面的变量是协议的一部分, 下面语句生成包头, 编码格式是utf8,这样可以发送汉字。
var sendStr = string.Format(appCfg.m_CMD_SEND_FORMAT, cmd, datlen);
sendStr = sendStr.PadRight(appCfg.m_CMD_STR_LEN);
byte[] cmdBytes = System.Text.Encoding.UTF8.GetBytes(sendStr);
// 发送包头部分
socket.Send(cmdBytes);// the id of the data ,
// 发送数据部分,编码格式ANSI 格式,接收处理时要做相应处理。
socket.Send(data);
}
其中,图片转换为byte数组的函数如下:
public static byte[] ImageToBytes(Image image)
{
ImageFormat format = image.RawFormat;
using (MemoryStream ms = new MemoryStream())
{
//image.Save(ms, ImageFormat.MemoryBmp);
if (format.Equals(ImageFormat.Jpeg))
{
image.Save(ms, ImageFormat.Jpeg);
}
else if (format.Equals(ImageFormat.Png))
{
image.Save(ms, ImageFormat.Png);
}
else if (format.Equals(ImageFormat.Bmp))
{
image.Save(ms, ImageFormat.Bmp);
}
else if (format.Equals(ImageFormat.Gif))
{
image.Save(ms, ImageFormat.Gif);
}
else if (format.Equals(ImageFormat.Icon))
{
image.Save(ms, ImageFormat.Icon);
}
else if (format.Equals(ImageFormat.MemoryBmp))
{
image.Save(ms, ImageFormat.Jpeg);
}
byte[] buffer = new byte[ms.Length];
//Image.Save()会改变MemoryStream的Position,需要重新Seek到Begin
ms.Seek(0, SeekOrigin.Begin);
ms.Read(buffer, 0, buffer.Length);
return buffer;
}
}
3 接收处理
3.1 接收处理流程
- 识别包头,获得数据长度
- 接收剩余数据
- 拼合数据,并判断数据包长度,满足要求后,数据包接收结束
- 回到1 ,等待下一个数据包
3.2 接收代码
note:主要是拼合数据逻辑的处理
另外,上一篇文章中已经包含了接收数据的回调监听代码
, 这里再写一次
Future<void> setupConnection() async {
if (m_isConnect) return;
//await getSvrIPViaUdp();
while (AppCfg.m_TcpSvrIP == "") {
await getSvrIPViaUdp();
await Future.delayed(const Duration(milliseconds: 100), () {
print("Waiting for getting m_srvip.....\r\n");
});
}
if (AppCfg.m_TcpSvrIP != "" && !m_isConnect) {
m_socket = await Socket.connect(AppCfg.m_TcpSvrIP, AppCfg.m_TcpSvrPort);
m_isConnect = true;
//await for the first image sent from the server
// 监听建立在这里!!!!!!!
// _receivedMsgHandler 是接收数据的回调函数
m_socket.listen(_receivedMsgHandler,
onError: _errorHandler, onDone: _doneHandler, cancelOnError: false);
}
}
_receivedMsgHandler 代码如下,其中包括数据拼合处理逻辑
// receive the stream data from the server via TCP
void _receivedMsgHandler(List<int> event) {
var data = Uint8List.fromList(event);
String message = String.fromCharCodes(data);
String cmdName = "";
String rcvDataStr = "";
//1# accumulate the strinng to ensure an
// entire strging of command has been got.
m_RcvMsgTtl += message;
RegExp rx = RegExp(r'' + AppCfg.m_MSPHeadRegPattern);
while (m_RcvMsgTtl.length > AppCfg.m_CMD_STR_LEN) {
//2# check if the front part of the string contains the command
//RegExp rx = RegExp(r'Sv-loon\s*([\s\S]*?)\s*Datum:');
//var cmdStr = m_RcvMsgTtl.substring(0, AppCfg.m_CMD_STR_LEN);
RegExpMatch? match = rx.firstMatch(m_RcvMsgTtl);
if (match != null) {
cmdName = match.namedGroup("name")!;
m_DataLen = int.parse(match.namedGroup("len")!);
// modify the data
m_RcvMsgTtl = m_RcvMsgTtl.substring(match.start);
}
// find the data
if (m_RcvMsgTtl.length >= m_DataLen + AppCfg.m_CMD_STR_LEN) {
// Get the data based on the data len
var from = AppCfg.m_CMD_STR_LEN;
var to = from + m_DataLen;
rcvDataStr = m_RcvMsgTtl.substring(from, to);
RunSvrAcion(cmdName, rcvDataStr);
m_RcvMsgTtl = m_RcvMsgTtl.substring(to);
} else {
break;
}
}
}
至此,一个完整的首发流程和主要核心过程已经实现。需要注意的是,文字处理和图片处理的encoding 是不一样的,笔者尝试都采用统一成 utf8 ,但是没有成功。后来就退而求其次,分开编码算了,这样还节省流量。
MaraSun BJFWDQ
再次预祝大家 兔年 农历新年快乐!