【GameFramework框架内置模块】11、网络(Network)

推荐阅读

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、前言

【GameFramework框架】系列教程目录:
https://blog.csdn.net/q764424567/article/details/135831551

二、正文

2-1、介绍

网络(Network)模块,主要是提供了使用Socket长连接的功能,支持TCP协议,同时兼容Ipv4IPv6两个版本。

用户可以同时建立多个连接与多个服务器进行通信,比如连接了常规的服务器以外,还想连接语音聊天服务器。

如果想要接入ProtoBuf之类的协议库,只要派生自Packet类并实现自己的消息包类即可使用。

2-2、使用说明

这里以StarForce示例项目为例,这个项目演示了接入ProtoBuf协议库后,连接Socket、发送数据、接收数据的过程。

2-2-1、接入ProtoBuf协议库

1、生成Proto数据类

namespace StarForce
{
	//使用ProtoContract标签
    [Serializable, ProtoContract(Name = @"CSHeartBeat")]
    public class CSHeartBeat : CSPacketBase
    {
        public override int Id

        public override void Clear()
    }
}

2、序列化

//序列化
CSHeartBeat packet = ReferencePool.Acquire<CSHeartBeat >();
Serializer.Serialize(m_CachedStream, packet);

//反序列化
CSHeartBeat packet = Serializer.Deserialize<SCHeartBeat >(m_CachedStream);

3、解数据

//获取header包
SCPacketHeader scPacketHeader = packetHeader as SCPacketHeader;
//获取数据包类型
Type packetType = GetServerToClientPacketType(scPacketHeader.Id);
//解数据
Packet packet = (Packet)RuntimeTypeModel.Default.DeserializeWithLengthPrefix(source, ReferencePool.Acquire(packetType), packetType, PrefixStyle.Fixed32, 0);

接到数据后,要进行解包,包体为了防止TCP粘包问题,会对数据包的格式进行包装,比如说【事件id+数据长度+数据体】。

StarForce中,将事件id和数据长度封装到PacketHeader类中,在解包时就不用考虑字节的问题,通过Protobuf来操作即可。

2-2-2、使用网络(Network)模块

1、连接Socket

private const string ip = "127.0.0.1";
private const int port = 3563;

protected override void OnInit(object userData)
{
    var channel = GameEntry.Network.CreateNetworkChannel("Default", ServiceType.Tcp, new NetworkChannelHelper());
    channel.Connect(IPAddress.Parse(ip), port);
}

2、发送数据

public void OnStartButtonClick()
{
	CS_LoginVerify login = ReferencePool.Acquire<CS_LoginVerify>();
	login.account = GetAcount();
	
	var channel = GameEntry.Network.GetNetworkChannel("Default");
	channel .Send(login);
}

3、接收数据

public partial class SC_LoginVerifyHandler : PacketHandlerBase
{
    public override void Handle(object sender, Packet packet)
    {
        var data = (SC_LoginVerify)packet;
        //.....
    }
}

2-3、实现及代码分析

NetworkManager.cs

网络管理器,负责维护网络连接,每一个网络连接相当于一个网络频道,比如有多个网络连接,就建立多个网络频道,当一个网络连接断开后,直接再新建一个网络频道,就可以防止数据混乱。

namespace GameFramework.Network
{
	/// <summary>
	/// 网络管理器。
	/// </summary>
	public class NetworkManager
	{
	    /// <summary>
	    /// 网络频道。
	    /// </summary>
	private readonly Dictionary<string, NetworkChannelBase> m_NetworkChannels;
	
	    /// 用户自定义网络错误事件。
	    event EventHandler<NetworkCustomErrorEventArgs> NetworkCustomError;
	
	    /// 检查是否存在网络频道。
	    bool HasNetworkChannel(string name);
	
	    /// 获取网络频道。
	    INetworkChannel GetNetworkChannel(string name);
	
	    /// 获取所有网络频道。
	    INetworkChannel[] GetAllNetworkChannels();
	
	    /// 获取所有网络频道。
	    void GetAllNetworkChannels(List<INetworkChannel> results);
	
	    /// 创建网络频道。
	    INetworkChannel CreateNetworkChannel(string name, ServiceType serviceType, INetworkChannelHelper networkChannelHelper);
	
	    /// 销毁网络频道。
	    bool DestroyNetworkChannel(string name);
	}
}

NetworkChannel.cs

网络频道接口,承接上一个脚本的网络频道的概念,这个是对网络频道,也就是一个Socket的封装,处理Socket的连接和断开,以及数据的发送和接收。

namespace GameFramework.Network
{
    /// <summary>
    /// 网络频道接口。
    /// </summary>
    public interface INetworkChannel
    {
        /// 获取网络频道名称。
        string Name
        
        /// 获取网络地址类型。
        AddressFamily AddressFamily

        /// 获取网络频道所使用的 Socket。
        Socket Socket

        /// 获取网络服务类型。
        ServiceType ServiceType

        /// 注册网络消息包处理函数。
        void RegisterHandler(IPacketHandler handler);

        /// 连接到远程主机。
        void Connect(IPAddress ipAddress, int port);

        /// 关闭网络频道。
        void Close();

        /// 向远程主机发送消息包。
        void Send<T>(T packet) where T : Packet;
    }
}

NetworkManager.TcpNetworkChannel.cs

TcpNetworkChannel是框架基于TCP协议实现的网络频道,TCP也是我们常用的长连接方式。

namespace GameFramework.Network
{
    /// <summary>
    /// TCP 网络频道。
    /// </summary>
    public interface TcpNetworkChannel 
    {
		// 建立连接
		private void ConnectAsync(IPAddress ipAddress, int port, object userData)
		{
			m_Socket.BeginConnect(ipAddress, port, m_ConnectCallback, new ConnectState(m_Socket, userData));
		}
		
		// 数据发送
		private void SendAsync()
		{
			//序列化消息,添加包头
			serializeResult = m_NetworkChannelHelper.Serialize(packet, m_SendState.Stream);
			//发送
			m_Socket.BeginSend(m_SendState.Stream.GetBuffer(), (int)m_SendState.Stream.Position, (int)(m_SendState.Stream.Length - m_SendState.Stream.Position), SocketFlags.None, m_SendCallback, m_Socket);
		}

		// 数据接收
		private void ReceiveAsync()
		{
			//接收网络流
			m_Socket.BeginReceive(m_ReceiveState.Stream.GetBuffer(), (int)m_ReceiveState.Stream.Position, (int)(m_ReceiveState.Stream.Length - m_ReceiveState.Stream.Position), SocketFlags.None, m_ReceiveCallback, m_Socket);
			//解析消息头
			IPacketHeader packetHeader = m_NetworkChannelHelper.DeserializePacketHeader(m_ReceiveState.Stream, out customErrorData);
			m_ReceiveState.PrepareForPacket(packetHeader);
			//解析消息体
			Packet packet = m_NetworkChannelHelper.DeserializePacket(m_ReceiveState.PacketHeader, m_ReceiveState.Stream, out customErrorData);
		}
	}
}

NetworkChannel对数据的发送和接收处理,中间都会穿插一部NetworkChannelHelper的处理。

  • 发送数据时,有一个Helper的Serialize序列化数据的处理。
  • 接收数据时,有一个DeserializePacketHeader和DeserializePacket解析数据的处理。

这就是GF框架的精妙处,它把可定制的代码逻辑,都交给了Helper来处理,这样我们在扩展框架的同时,又完全不需要修改框架的核心结构。完全符合开闭原则。

NetworkChannelHelper.cs

网络频道辅助器接口,NetworkChannelHelper可以理解为把我们所需要发送 和接收的数据进行序列化和反序列化处理的工具。

NetworkChannelHelper的基本结构如下所示:

namespace GameFramework.Network
{
    /// <summary>
    /// 网络频道辅助器接口。
    /// </summary>
    public interface INetworkChannelHelper
    {
        /// 初始化网络频道辅助器。
        void Initialize(INetworkChannel networkChannel);

        /// 关闭并清理网络频道辅助器。
        void Shutdown();

        /// 准备进行连接。
        void PrepareForConnecting();

        /// 发送心跳消息包。
        bool SendHeartBeat();

        /// 序列化消息包。
        bool Serialize<T>(T packet, Stream destination) where T : Packet;

        /// 反序列化消息包头。
        IPacketHeader DeserializePacketHeader(Stream source, out object customErrorData);

        /// 反序列化消息包。
        Packet DeserializePacket(IPacketHeader packetHeader, Stream source, out object customErrorData);
    }
}

三、后记

如果觉得本篇文章有用别忘了点个关注,关注不迷路,持续分享更多Unity干货文章。


你的点赞就是对博主的支持,有问题记得留言:

博主主页有联系方式。

博主还有跟多宝藏文章等待你的发掘哦:

专栏 方向 简介
Unity3D开发小游戏 小游戏开发教程 分享一些使用Unity3D引擎开发的小游戏,分享一些制作小游戏的教程。
Unity3D从入门到进阶 入门 从自学Unity中获取灵感,总结从零开始学习Unity的路线,有C#和Unity的知识。
Unity3D之UGUI UGUI Unity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。
Unity3D之读取数据 文件读取 使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。
Unity3D之数据集合 数据集合 数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。
Unity3D之VR/AR(虚拟仿真)开发 虚拟仿真 总结博主工作常见的虚拟仿真需求进行案例讲解。
Unity3D之插件 插件 主要分享在Unity开发中用到的一些插件使用方法,插件介绍等
Unity3D之日常开发 日常记录 主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等
Unity3D之日常BUG 日常记录 记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-21 09:16:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-21 09:16:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-21 09:16:04       82 阅读
  4. Python语言-面向对象

    2024-03-21 09:16:04       91 阅读

热门阅读

  1. 在Ubuntu 12.04和CentOS 6上如何添加和删除用户

    2024-03-21 09:16:04       43 阅读
  2. SWIFT环境配置及大模型微调实践

    2024-03-21 09:16:04       42 阅读
  3. 汽车信息安全--密钥管理系统初探(1)

    2024-03-21 09:16:04       38 阅读
  4. HTML:浏览器CSS样式前缀

    2024-03-21 09:16:04       43 阅读
  5. vue html里面使用全局引入的scss变量

    2024-03-21 09:16:04       43 阅读
  6. 批量更新或者新增

    2024-03-21 09:16:04       43 阅读
  7. html面试题

    2024-03-21 09:16:04       41 阅读