socket应用之从电脑发送图片到手机(2)之发送和接收数据

本文介绍了在TCP通信中进行图片传输的方法,包括建立简单的通信协议来标识数据包,定义包头以包含数据名称和长度,以及发送和接收端的处理流程。发送端将图片转为Byte数组,附加包头信息,通过正则表达式识别包头。接收端则识别包头,拼接数据,确保数据完整。文章强调了批量发送大文件时通信协议的重要性,并指出文字和图片的编码差异。
摘要由CSDN通过智能技术生成

上一篇建立了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 发送流程

  1. 获得发送的图片
  2. 图片转换成byte数组
  3. 根据数组大小成成包头信息
  4. 发送包头

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. 识别包头,获得数据长度
  2. 接收剩余数据
  3. 拼合数据,并判断数据包长度,满足要求后,数据包接收结束
  4. 回到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

再次预祝大家 兔年 农历新年快乐!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值