C#网络模块

1数据包
在传输网络数据的时候,接收方一次收到的数据长度可能是不确定的,比如客户端发送了100个字节给服务器,服务器有可能一次收到100个字节,也可能先收到20个,再收到80个。为了知道到底一个数据的长度是多少,我们将首先创建一个类,用于管理序列化的数据流,序列化、反序列化对象。
NetPacket这个类提供的功能主要包括两部分:一部分是将序列化的数据写入,并加入4个字节作为数据的“头”;另一部分是从byte数组的前4个字节解析数据长度,再读取相应长度的数据。这里是把protobuf的序列化处理。
1.1protobuf序列化
1.protobuf序列化为byte数组

       /// <summary>
        /// protobuf序列化成byte
        /// </summary>
        /// <typeparam name="T">protobuf的类</typeparam>
        /// <param name="t">protobuf值</param>
        /// <returns>protobuf序列化后的byte数组</returns>
        public byte[] ProtoRuntimeSerialize<T>(T t)
        {
            byte[] bs;
            using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
            {
                ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.RuntimeTypeModel.Create();
                model.Serialize(stream, t);
                bs = stream.ToArray();
            }
            return bs;
        }

2.byte数组写入数据流

       // 直接写入一个proto序列化的对象
        public void WriteProtoObject<T>(T t)
        {
            byte[] bs = ProtoRuntimeSerialize<T>(t);
            WriteStream(bs);
        }
           /// <summary>
		/// 写入byte数组
	    /// </summary>
	    public void WriteStream(byte[] bs)
	    {
	        WriteInt(bs.Length);
	        // 压入数据流
			bs.CopyTo(bytes, m_headLen + m_bodyLen);
			m_bodyLen += bs.Length;
	    }

整型数据转换为byte

byte[] bs = System.BitConverter.GetBytes(number);

1.2protobuf反序列化
把byte转换为对应的protobuf

       // 反序列化
        public T ProtoRuntimeDeserialize<T>(byte[] bs)
        {
            T t = default(T);
            using (System.IO.MemoryStream stream = new System.IO.MemoryStream(bs))
            {
                ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.RuntimeTypeModel.Create();// ChatSerializer.Create();
                t = (T)model.Deserialize(stream, null, typeof(T));
            }
            return t;
        }
        //读出proto
        public T ReadProtoObject<T>()
        {
            byte[] bs = ReadStream();
            if (bs == null)
            {
                return default(T);
            }
            return ProtoRuntimeDeserialize<T>(bs);
        }

2网络功能
TCPPeer类封装TCP/IP协议,比如接收和发送数据
2.1发送数据
开始向流异步写入。每次发送的是byte数组。

        public void Send( Socket sk, NetPacket packet  )
        {
            NetworkStream ns;
            lock (sk)
            {
                ns = new NetworkStream(sk);
                if (ns.CanWrite)
                {
                    try
                    {
                        ns.BeginWrite( packet.bytes, 0, packet.Length, new System.AsyncCallback(SendCallback), ns);
                    }

发送回调

      private void SendCallback(System.IAsyncResult ar)
        {
            NetworkStream ns = (NetworkStream)ar.AsyncState;
            try
            {
                ns.EndWrite(ar);
                ns.Flush();
                ns.Close();
            }

2.2接收数据
1.当连接建立好时,开始从连接的Socket中异步接收数据。每次读取4个字节长度(代表消息的实际长度)。

client.BeginReceive(packet.bytes, 0, NetPacket.m_headLen, SocketFlags.None, new System.AsyncCallback(ReceiveHeader), packet);

2.接收消息头。

void ReceiveHeader(System.IAsyncResult ar)
        {
            NetPacket packet = (NetPacket)ar.AsyncState;
            try
            {
                // 返回网络上接收的数据长度
                int read = packet.socket.EndReceive(ar);
                // 已断开连接
                if (read < 1)
                {
                    // 通知丢失连接
                    //AddInternalPacket("OnLost", packet.socket);
                    networkMgr.OnLost(packet);
                    return;
                }

                packet.readLength += read;
                // 消息头必须读满4个字节
                if (packet.readLength < NetPacket.m_headLen)
                {
                    packet.socket.BeginReceive(packet.bytes,
                        packet.readLength,                          // 存储偏移已读入的长度
                        NetPacket.m_headLen - packet.readLength, // 这次只读入剩余的数据
                        SocketFlags.None,
                        new System.AsyncCallback(ReceiveHeader),
                        packet);
                }
                else
                {
                    // 获得消息体长度
                    packet.DecodeHeader();

                    packet.readLength = 0;
                    // 开始读取消息体
                    packet.socket.BeginReceive(packet.bytes,
                        NetPacket.m_headLen,
                        packet.m_bodyLen,
                        SocketFlags.None,
                        new System.AsyncCallback(ReceiveBody),
                        packet);
                }

说明:
1.消息头是4个字节,如果没读满4个字节做偏移量读4-已读的字节,再调用异步接收函数,读取剩下的。
2.当这条消息读满4个字节,解析出前4个字节代表的消息体的长度,再调用异步接收函数,读取消息体的信息。
3.接收消息体

void ReceiveBody(System.IAsyncResult ar)
        {
            NetPacket packet = (NetPacket)ar.AsyncState;

            try
            {
                // 返回网络上接收的数据长度
                int read = packet.socket.EndReceive(ar);
                // 已断开连接
                if (read < 1)
                {
                    // 通知丢失连接
                    //AddInternalPacket("OnLost", packet.socket);
                    networkMgr.OnLost(packet);
                    return;
                }
                packet.readLength += read;

                // 消息体必须读满指定的长度
                if ( packet.readLength < packet.m_bodyLen )
                {
                    packet.socket.BeginReceive(packet.bytes,
                        NetPacket.m_headLen + packet.readLength,
                        packet.m_bodyLen - packet.readLength,
                        SocketFlags.None,
                        new System.AsyncCallback(ReceiveBody),
                        packet);
                }
                else
                {
                    // 将消息传入到逻辑处理队列
                    networkMgr.AddPacket(packet);

                    // 下一个读取
                    packet.Reset();
                    packet.socket.BeginReceive(packet.bytes,
                        0,
                        NetPacket.m_headLen,
                        SocketFlags.None,
                        new System.AsyncCallback(ReceiveHeader),
                        packet);
                }
            }

说明:
1.没读满消息体的长度,做偏移,从(头4个字节偏移+已读长度)开始,偏移量为(消息体长度-已读长度),接着调用ReceiveBody,读取剩下的消息体。
2.当读满消息体长度时,将消息传入到逻辑处理队列,接着ReceiveHeader,读取下条消息。
3网络管理类
负责把网络接收到的消息,分发到各个消息处理函数。
3.1数据包入队

       // 数据包入队
        public void AddPacket( NetPacket packet )
        {
            lock (Packets)
            {
                Packets.Enqueue(packet);
            }
        }

3.2消息委托

public delegate void OnReceive( NetPacket packet );
        // 注册消息
        public void AddHandler(string msgid, OnReceive handler)
        {
            handlers.Add(msgid, handler);
        }

说明:1个string为key,委托函数为value,外部函数注册好消息的处理。
3.2处理更新

public void Update()
        {
            NetPacket packet = null;
            for (packet = GetPacket(); packet != null; )
            {
                string msg = "";
                // 获得消息标识符
                packet.BeginRead(out msg);

                OnReceive handler = null;
                if (handlers.TryGetValue(msg, out handler))
                {
                    // 根据消息标识符找到相应的OnReceive代理函数
                    if (handler != null)
                        handler(packet);
                }

说明:
1.从消息队列中取队头的
2.读取消息开始时的string为消息id
消息id找到相应的OnReceive委托函数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四夕立羽

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值