C#与php自定义数据流传输

发布时间:2023年12月26日

介绍

如果不了解Unity与web如何通讯的可以看我之前的文章。
无论传输什么类型的数据,如int、float、string等,他们都被保存在文本中,接下来我们从字符串中解析这些数据。

一、客户端与服务器数据传输流程图

客户端发送数据给服务器:

在这里插入图片描述

服务器返回数据给客户端:

在这里插入图片描述

二、自定义数据流

C#版本数据流

我们要创建一个C#版本的数据流类,它的主要功能是将各种不同类型的数据压入一个单独的字符创中,或将从服务器读回的字节数组解析成响应的数据,这里要清楚不同类型数据所占字节长度,如32位int即占用4个字节,短整型short占2个字节等,代码如下:

using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;


public class PostStream {

    public Dictionary<string,string> Headers = new Dictionary<string, string>();
    const int HASHSIZE = 16;        //末尾16个字节保存md5数字签名
    const int BYTE_LEN = 1;         //byte占一个字节
    const int SHORT16_LEN = 2;      //short占2个字节
    const int INT32_LEN = 4;        //int占4个字节
    const int FLOAT_LEN = 4;        //float占4个字节

    private int m_index = 0;
    public int Length
    {
        get
        {
            return m_index;
        }
    }

    //秘密密码,用于数字签名
    private string m_secretKey = "123456";

    //存储Post信息
    private string[,] m_field;

    /// <summary>
    /// 最大传输数量
    /// </summary>
    private const int MAX_POST = 128;

    /// <summary>
    /// 单位Post信息所存储的信息量
    /// </summary>
    private const int PAIR = 2;

    /// <summary>
    /// 信息头索引
    /// </summary>
    private const int HEAD = 0;

    /// <summary>
    /// 信息内容索引
    /// </summary>
    private const int CONTENT = 1;

    /// <summary>
    /// 收到的字节数组
    /// </summary>
    private byte[] m_bytes = null;
    public byte[] BYTES { get { return m_bytes; } }

    /// <summary>
    /// 发送的字符串
    /// </summary>
    private string m_content = "";

    /// <summary>
    /// 读取是否出现错误
    /// </summary>
    private bool m_errorRead = false;

    /// <summary>
    /// 是否进行数字签名
    /// </summary>
    private bool m_sum = true;

    /// <summary>
    /// 构造函数初始化
    /// </summary>
    public PostStream()
    {
        Headers = new Dictionary<string,string>();
        m_index = 0;
        m_bytes = null;
        m_content = "";
        m_errorRead = false;
    }

    //这个类的第一部分是将不同类型的数据按POST格式压入到m_content字符串和二位字符串数组m_field中。m_content中的数据时实际发送的数据,m_field中的数据用于MD5数字签名。
    #region 写入数据

    /// <summary>
    /// 开始压数据,issum参数用来标识是否进行MD5数字签名
    /// </summary>
    public void BeginWrite(bool issum)
    {
        m_index = 0;
        m_sum = issum;
        m_field = new string[MAX_POST, PAIR];
        Headers.Add("Content-Type", "application/x-www-form-urlencoded");
    }

    /// <summary>
    /// head表示POST的名字,content是实际的数据内容
    /// </summary>
    /// <param name="head"></param>
    /// <param name="content"></param>
    public void Write(string head, string content)
    {
        if (m_index >= MAX_POST) return;

        m_field[m_index, HEAD] = head;
        m_field[m_index, CONTENT] = content;

        m_index++;
        if (m_content.Length == 0)
            m_content += (head + "=" + content);
        else
            m_content += ("&" + head + "=" + content);
        Debug.LogError(m_content);
    }

    /// <summary>
    /// 使用MD5对字符串进行数字签名
    /// </summary>
    public void EndWrite()
    {
        if (m_sum)
        {
            string hasstring = "";
            for (int i = 0; i < MAX_POST; i++)
                hasstring += m_field[i, CONTENT];
            hasstring += m_secretKey; //数据: content1content2...m_secretKey
            Debug.LogError("hasstring=" + hasstring);
            m_content += "&key=" + Md5Sum(hasstring);//数据: head1=content1&head2=content2...&key=(hasstring的MD5值)
            Debug.LogError("m_content=" + m_content);
        }
        m_bytes = Encoding.UTF8.GetBytes(m_content);
    }

    #endregion

    //第二部分是读取从服务器返回的数据。从服务器返回的数据时一个单独的字节数组,我们将这个数组解析为相应的数据,这个过程用到了最多的是BitConverter函数,它可以将相应长度的字节转为对应的数据
    #region 读取数据

    /// <summary>
    /// 读取数据
    /// </summary>
    /// <param name="www"></param>
    /// <param name="issum"></param>
    /// <returns></returns>
    public bool BeginRead(WWW www,bool issum)
    {
        m_bytes = www.bytes;
        m_content = www.text;

        m_sum = issum;

        //错误
        if (m_bytes == null)
        {
            m_errorRead = true;
            return false;
        }

        //读取前2个字节,获得字符串长度
        short length = 0;
        this.ReadShort(ref length);//服务器这里做了处理,在写入数据时先写入一个short类型的数据代表数据长度
        if (length != m_bytes.Length)
        {
            m_index = length;
            m_errorRead = true;
            return false;
        }

        //比较本地与服务器数字签名是否一致
        if (m_sum)
        {
            byte[] localhash = GetLocalHash(m_bytes, m_secretKey);
            byte[] hashbytes = GetCurrentHash(m_bytes);
            if (!ByteEquals(localhash,hashbytes))
            {
                m_errorRead = true;
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// 忽略一个字节
    /// </summary>
    public void IgnoreByte()
    {
        if (m_errorRead) return;
        m_index += BYTE_LEN;
    }

    /// <summary>
    /// 读取一个字节
    /// </summary>
    public void ReadByte(ref byte bts)
    {
        if (m_errorRead) return;

        bts = m_bytes[m_index];
        m_index += BYTE_LEN;
    }

    /// <summary>
    /// 读取一个short
    /// </summary>
    /// <param name="number"></param>
    public void ReadShort(ref short number)
    {
        if (m_errorRead) return;
        number = System.BitConverter.ToInt16(m_bytes,m_index);
        m_index += SHORT16_LEN;
    }

    /// <summary>
    /// 读取一个int
    /// </summary>
    public void ReadInt(ref int number)
    {
        if (m_errorRead) return;
        number = System.BitConverter.ToInt32(m_bytes,m_index);
        m_index += INT32_LEN;
    }

    /// <summary>
    /// 读取一个float
    /// </summary>
    public void ReadFloat(ref float number)
    {
        if (m_errorRead) return;
        number = System.BitConverter.ToSingle(m_bytes, m_index);
        m_index += FLOAT_LEN;
    }

    /// <summary>
    /// 读取一个字符串
    /// </summary>
    public void ReadString(ref string str)
    {
        if (m_errorRead) return;

        short num = 0;
        ReadShort(ref num);

        str = Encoding.UTF8.GetString(m_bytes,m_index,(int)num);

        m_index += num;
    }

    /// <summary>
    /// 读取一个bytes数组
    /// </summary>
    /// <param name="bytes"></param>
    public void ReadBytes(ref byte[] bytes)
    {
        if (m_errorRead) return;
        short len = 0;
        ReadShort(ref len);

        //字节流
        bytes = new byte[len];
        for (int i = m_index; i < m_index + len; i++)
        {
            bytes[i - m_index] = m_bytes[i];
        }

        m_index += len;
    }

    /// <summary>
    /// 结束读取
    /// </summary>
    /// <returns></returns>
    public bool EndRead()
    {
        if (m_errorRead) return false;
        else return true;
    }

    #endregion

    /// <summary>
    /// 去掉服务器返回的数字签名,使用本地秘钥重新计算数字签名
    /// </summary>
    /// <returns></returns>
    public static byte[] GetLocalHash(byte[] bytes,string key)
    {
        //hash bytes
        byte[] hashbytes = null;

        int n = bytes.Length - HASHSIZE;

        if (n < 0) return hashbytes;

        //获得key的bytes
        byte[] keybytes = System.Text.ASCIIEncoding.ASCII.GetBytes(key);

        //创建用于hash的bytes
        byte[] getbytes = new byte[n + keybytes.Length];

        for (int i = 0; i < n; i++)
        {
            getbytes[i] = bytes[i];
        }

        keybytes.CopyTo(getbytes,n);

        System.Security.Cryptography.MD5 md5;
        md5 = System.Security.Cryptography.MD5CryptoServiceProvider.Create();

        return md5.ComputeHash(getbytes);
    }

    /// <summary>
    /// 获得从服务器返回的数字签名
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    public static byte[] GetCurrentHash(byte[] bytes)
    {
        byte[] hashbytes = null;
        if (bytes.Length < HASHSIZE) return hashbytes;

        hashbytes = new byte[HASHSIZE];

        for (int i = bytes.Length - HASHSIZE; i < bytes.Length; i++)
        {
            hashbytes[i - (bytes.Length - HASHSIZE)] = bytes[i];
        }
        return hashbytes;
    }

    #region 比较两个bytes数组是否相等

    /// <summary>
    /// 比较两个bytes数组是否相等
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    public static bool ByteEquals(byte[] a,byte[] b)
    {
        if (a == null || b == null || a.Length != b.Length) return false;

        for (int i = 0; i < a.Length; i++)
        {
            if (a[i] != b[i]) return false;
        }

        return true;
    }

    #endregion

    #region 取字符串md5值

    /// <summary>
    /// md5值
    /// </summary>
    /// <param name="strToEncrypt">//数据: head1content1head2content2...m_secretKey</param>
    /// <returns></returns>
    public static string Md5Sum(string strToEncrypt)
    {
        byte[] bs = UTF8Encoding.UTF8.GetBytes(strToEncrypt);

        System.Security.Cryptography.MD5 md5;
        md5 = System.Security.Cryptography.MD5CryptoServiceProvider.Create();

        byte[] hashBytes = md5.ComputeHash(bs);

        string hashString = "";
        for (int i = 0; i < hashBytes.Length; i++)
        {
            hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2,'0');
        }

        return hashString.PadLeft(32,'0');
    }

    #endregion
}


PHP版本数据流

PHP版本的代码与C#版本如出一辙,只是换成了PHP的语法:

<?php 
//PHPStream.php
define("BYTE",1);
define("SHORT",2);
define("INT",4);
define("FLOAT",4);
define("HASHSIZE",16);
define("PKEY",123456);


class PHPStream
{
    private $Key = "";
    public $bytes = "";
    public $Content = "";
    public $index = 0;
    public $ErrorRead = false;
    
    //开始写数据
    function  BeginWrite( $Key )
    {
        $this->index=0;
        $this->bytes="";
        $this->Content="";
        $this->ErrorRead=false;
        
        //total bytes length
        $this->WriteShort(0);//服务器这里在发送数据的时候会先去写入一个short,代表数据长度
        if ( strlen($Key) )
        {
            $this->Key=$Key;
        }
    }
    
    //写一个byte
    function WriteByte( $byte )
    {
        //$this->bytes.=pack('c',$byte);
        $this->bytes.=$byte;
        $this->index+=BYTE;
    }
    
    //写一个short
    function WriteShort( $number )
    {
        $this->bytes.=pack("v",$number);
        $this->index+=SHORT;
    }
    
    //写一个32位int
    function WriteInt( $number )
    {
        $this->bytes.=pack("V",$number);
        $this->index+=INT;
    }
    
    //写一个float
    function WriteFLOAT( $number )
    {
        $this->bytes.=pack("f",$number);
        $this->index+=FLOAT;
    }
    
    //写一个字符串
    function WriteString( $str )
    {
        $len=strlen($str);
        $this->WriteShort($len);
        
        $this->bytes.=$str;
        $this->index+=$len;
    }
    
    //写一组byte
    function WriteBytes( $bytes )
    {
        $len=strlen($bytes);
        $this->WriteShort($len);
        
        $this->bytes.=$bytes;
        $this->index+=$len;
    }
    
    function EndWrite()
    {
        //数字签名
        if ( strlen($this->Key)>0 )
        {
            $len=$this->index+HASHSIZE;
            $str=pack("v",$len);
            
            //猜测这里的bytes内部对应是0-1 2-3 4-5 6-7
            //猜测内部为键值对 $str[0] = $str[1]
            $this->bytes[0]=$str[0];//猜测为key值
            $this->bytes[1]=$str[1];//猜测为key值对应的value
            
            //获取md5值
            $hashbytes=md5($this->bytes.$this->Key,true);
            
            $this->bytes.=$hashbytes;
            
        }
        else 
        {
            $str=pack("v",$this->index);
            $this->bytes[0]=$str[0];
            $this->bytes[1]=$str[1];
        }
    }
    
    //开始读入数据
    function BeginRead( $Key )
    {
        $this->index=0;
        $this->bytes="";
        $this->Content="";
        $this->ErrorRead=false;
        
        if ( strlen($Key)>0 )//strlen检测字符串长度
        {
            $this->Key=$Key;
        }
    }
    
    //读取POST信息
    function Read( $head )
    {
        if( isset($_POST[$head]) )
        {
            $this->Content.=$_POST[$head];
            return $_POST[$head];
        }
        else
        {
            $this->ErrorRead=true;
        }
    }
    
    //结束读取
    function EndRead()
    {
        if ($this->ErrorRead) return false;
        
        if (strlen($this->Key)<1) return true;//如果不需要签名验证则将原本的PKEY改为空字符串
        
        //取得数字签名
        $hashkey="";
        if ( isset($_POST["key"]) ) $hashkey=$_POST["key"];
        else 
        {
            $this->ErrorRead=true;
            return false;
        }
        
        //重新计算数字签名
        $localhash=md5($this->Content.$this->Key);
        
        //比较数字签名
        if (strcmp($hashkey,$localhash)==0) return true;//strcmp检测两个字符串是否一致
        else
        {
            $this->ErrorRead=true;
            return false;
        }
        
    }
}
?>

三、数据传输测试

1.在Unity中创建一个C#脚本NetWorkManager.cs

在脚本中创建一个WWW实例,分别发送int、float、short和string类型的数据至服务器,服务器收到后再将这些数据返回给Unity,下面是C#代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NetworkManager : MonoBehaviour {

    public const string URL = "http://192.168.1.5:8088/StreamTest.php";

    private void Start()
    {
        StartCoroutine(Test());
    }

    IEnumerator Test()
    {
        PostStream poststream = new PostStream();

        int integer = 1000;
        float number = 8.99f;
        short small = 30;
        string txt = "编程其乐无穷";

        //开始写入数据并指定需要签名认证
        poststream.BeginWrite(true);

        //写入数据Content: integer=1000
        poststream.Write("integer",integer.ToString());

        //写入数据Content: integer=1000&number=8.99
        poststream.Write("number",number.ToString());

        //写入数据Content: integer=1000&number=8.99&short=30
        poststream.Write("short",small.ToString());

        //写入数据Content: integer=1000&number=8.99&short=30&string=编程其乐无穷
        poststream.Write("string",txt);

        //1.最终签名认证的数据Content: integer=1000&number=8.99&short=30&string=编程其乐无穷&key=c344b95687a03452d4bf479a89affb94 
        //解释: c344b95687a03452d4bf479a89affb94为“10008.9930编程其乐无穷123456”字符串的MD5值  
        //123456为用于签名的密码 其组成是由写入的数组+密码组成
        //2.最终非签名认证的数据Content: integer=1000&number=8.99&short=30&string=编程其乐无穷
        poststream.EndWrite();
                              
        //服务器Post请求
        WWW www = new WWW(URL,poststream.BYTES,poststream.Headers);

        yield return www;

        //无错误
        if (www.error != null)
        {
            Debug.LogError(www.error);
        }
        else//读取返回值
        {
            poststream = new PostStream();
            poststream.BeginRead(www, true);
            poststream.ReadInt(ref integer);
            poststream.ReadFloat(ref number);
            poststream.ReadShort(ref small);
            poststream.ReadString(ref txt);

            bool ok = poststream.EndRead();
            if (ok)
            {
                Debug.LogError(integer);
                Debug.LogError(number);
                Debug.LogError(small);
                Debug.LogError(txt);
            }
            else
            {
                Debug.LogError("error");
            }
        }
    }
}

2.服务器www目录创建StreamTest.php脚本代码如下:

<?php 
//StreamTest.php
header('Content-Type:text/html; charset=utf-8');
require_once("PHPStream.php");//引用PHPStream.php文件

//read
$stream=new PHPStream();
$stream->BeginRead(PKEY);//与客户端对应的数字签名密码
$integer=$stream->Read("integer");//从传入的数据中找到Key值为integer的Value
$number=$stream->Read("number");//从传入的数据中找到Key值为number的Value
$short=$stream->Read("short");//从传入的数据中找到Key值为short的Value
$str=$stream->Read("string");//从传入的数据中找到Key值为string的Value
$ok=$stream->EndRead();

if ($ok)
{
    //开始写入一个short: bytes=pack("v",0)
    //开始的index: index = 0+2
    $stream->BeginWrite(PKEY);

    //写入一个Int: bytes=pack("v",0)+pack("V",$integer)
    //当前index: index = 0 + 2 + 4
    $stream->WriteInt($integer);
    
    //写入一个Float: bytes=pack("v",0)+pack("V",$integer)+pack("f",$number)
    //当前index: index = 0 + 2 + 4 + 4
    $stream->WriteFloat($number);

    //写入一个Float: bytes=pack("v",0)+pack("V",$integer)+pack("f",$number)+pack("v",$short)
    //当前index: index = 0 + 2 + 4 + 4 + 2
    $stream->WriteShort($short);

    //写入一个String: bytes=pack("v",0)+pack("V",$integer)+pack("f",$number)+pack("v",$short)+[pack("v",strlen($str))+$str]
    //当前index: index = 0 + 2 + 4 + 4 + 2 + (2 + strlen($str))
    $stream->WriteString($str);

    //带有签名bytes 末尾加md5(bytes=pack("v",0)+pack("V",$integer)+pack("f",$number)+pack("v",$short)+[pack("v",strlen($str))+$str]) (无签名则不加)
    //带有签名index: index = 0 + 2 + 4 + 4 + 2 + (2 + strlen($str)) + 16 (无签名就去掉+16)
    $stream->EndWrite();
    
    echo $stream->bytes;
}
else
{
    echo "error";
}
?>

结果如下:

在这里插入图片描述

这里需要注意一个问题,自定义数据类写入过程和读取过程顺序必须一致,否则无法获取数据。

PHP中的pack与unpack的方法将数据转换为二进制的方法最好了解下。

文章来源:https://blog.csdn.net/qq_42194657/article/details/135192569
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。