网络游戏的前后端通讯(二)

【旧博客转移 - 2015年9月20日 22:48】

 

1.Socket的粘包拆包

tcp协议是有缓冲区的,如果发送的数据太小,会被放置缓冲区里,累积更多的数据再一起发送,发送后不会立马清除缓冲区,等待TCP应答消息到了,才会清除缓冲区。没应答就会继续重发,造成粘包的原因有几种。

 

  1. 发送端的多个数据包在缓冲区里一起发送
  2. 接收端由于网络原因阻塞,然后一次性接受多个包
另外数据包大小比缓冲区大小大的话,会被分成多个片分开发送。

 

解决以上问题,可以在包的结构设计上处理,一般都采用 包头(4字节)+数据 的结构来封包,包头写入包的长度,下面请看解析包代码

 

/**
 * 封包
 * **/
private void send(Message vo){
    if(vo == null)return;
    MemoryStream buff = new MemoryStream();
    byte[] voByte = Message.encode(vo);
    byte[] pLen = ByteArrayUtil.writeInt(voByte.Length);
    buff.Write(pLen, 0, 4);//写包头
    buff.Write(voByte, 0, voByte.Length);
    _socket.Send(buff.ToArray());
}
//开一个子线程接受数据
class SocketThread
{
    public Connection conn;

    private int len = 0;
    public void run(){
        while(true){
            if(conn.state != Connection.CONNECTED)continue;
            if(len == 0){
                if(conn._socket.Available < 4)continue;//不足4字节读包长
                byte[] lenB = new byte[4];
                conn._socket.Receive(lenB, 4, SocketFlags.None);
                len = ByteArrayUtil.readInt(lenB);//读出包长
            }
            if(conn._socket.Available < len) continue;//不足一个包

            byte[] voB = new byte[len];
            conn._socket.Receive(voB, len, SocketFlags.None);//把包读出来

            len = 0;

            MemoryStream ms = new MemoryStream(voB);
            Message vo = Message.decode(ms);//解码包
            ms.Close();
            Debug.Log("receive:"+vo.getClassName());
            conn.resultConnectionHandler(vo);
        }
    }
}

 

2.协议加密校验

很多游戏的数据都是明文传输的,别人使用一些抓包工具如Wireshark、WPE等等,就可以轻易修改网络包数据。为了防止数据被修改,我们可以做一下校验,这里介绍一种简单的加key校验方法,服务端跟客户端都用同一种校验算法,根据key生成一个校验值,然后在数据中把这个校验值传输给前端,前端再做校验判断
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test
{
    class Program
    {

        private byte[] datas = new byte[100];

        public Program() {

            //随机生成100个字节的测试数据
            Random random = new Random();
            for (int i = 0; i < datas.Length; i++)
            {
                datas[i] = (byte)random.Next(1, 255);
            }

            ArrayToString(datas);//打印数组

            //校验密钥
            int key = 109;

            int checkVal = CheckBytes(datas, key);//生成校验值
            Console.WriteLine("校验值:" + checkVal);

            datas[3] = 2;//模拟抓包工具修改数值

            Console.WriteLine("模拟抓包工具修改数值:");

            ArrayToString(datas);//打印数组

            int checkVal2 = CheckBytes(datas, key);//再次生成校验值,此时数据已经在上面被模拟修改了
            Console.WriteLine("校验值:" + checkVal2);

            if (checkVal != checkVal2)
            {
                Console.WriteLine("检测到被修改的数据包!");
            }

        }

        ///
     /// <summary>
        /// 根据key校验字节数组
        /// </summary>

        public int CheckBytes(byte[] datas, int key)
        {
            int sumCalc = 0;
            int i = datas.Length;
            //每一个字节都会影响校验值
            while(--i > -1)
            {
                sumCalc += (int)(datas[i] ^ key >> datas[i]);
            }
            return sumCalc;
        }

        public void ArrayToString(byte[] datas)
        {
            string str = "";
            for (int i = 0; i < datas.Length; i++)
            {
                str += datas[i] + ",";
            }
            Console.WriteLine(str.Substring(0, str.Length-1));
        }

        static void Main(string[] args)
        {
            new Program();

            Console.ReadKey();
        }

    }
}

 

运行结果:

 

不过如果你的客户端代码被别人反编译了,别人知道了怎么获取key以及校验算法后,就能把值修改成能校验通过的数据,所以怎么隐藏key很重要,客户端代码加密混淆也很重要。

posted @ 2017-05-16 14:40  李嘉的博客  阅读(932)  评论(0编辑  收藏  举报