Unity网络服务器搭建【中高级】

前言
众所周知,网络游戏中,服务器的搭建尤为重要,无论是授权服务器,还是非授权服务器,它都承担着很大一部分的数据处理。今天,给大家分享一个中高级的小服务器搭建,当然也是在Unity上实现的,可以迁移为服务,外部程序等。其中加入了对象的序列化与反序列化,数据包的封包与拆包,细细品味,别有一番滋味。
服务器基础搭建

using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System;
using System.IO;

//实时消息处理委托
public delegate void NetEventHandler (string msg);

public class NetUtility
{
    //单例
    public static readonly NetUtility Instance = new NetUtility ();
    //消息回调
    private NetEventHandler ReceiveCallback;
    //服务器Tcp
    private TcpListener tcpServer;
    //客户端Tcp
    private TcpClient tcpClient;
    //缓冲区
    private byte[] buffer;
    //缓存数据组
    private List<byte> cache;
    //网络节点
    private IPEndPoint serverIPEndPoint;

    /// <summary>
    /// 设置网络节点
    /// </summary>
    /// <param name="ep">网络节点.</param>
    public void SetIpAddressAndPort (IPEndPoint ep)
    {
        //只写网络节点
        serverIPEndPoint = ep;
    }
    /// <summary>
    /// 设置委托
    /// </summary>
    /// <param name="handler">消息委托.</param>
    public void SetDelegate (NetEventHandler handler)
    {
        //只写赋值回调
        ReceiveCallback = handler;
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="NetUtility"/> class.
    /// </summary>
    private NetUtility ()
    {
        //服务器实例
        tcpServer = new TcpListener (IPAddress.Any, 23456);
        //客户端实例
        tcpClient = new TcpClient (AddressFamily.InterNetwork);
        //缓冲区初始化
        buffer = new byte[1024];
        //缓存数据组实例
        cache = new List<byte> ();
        //默认网络节点
        serverIPEndPoint = new IPEndPoint (IPAddress.Parse ("127.0.0.1"), 23456);
    }

服务器部分

#region Server Part:
/// <summary>
/// 开启服务器
/// </summary>
public void ServerStart ()
{
    //开启服务器
    tcpServer.Start (10);
    //服务器开启提示
    ReceiveCallback ("Server Has Init!");
    //开始异步接受客户端的连接请求
    tcpServer.BeginAcceptTcpClient (AsyncAccept, null);
}
/// <summary>
/// 异步连接回调
/// </summary>
/// <param name="ar">Ar.</param>
void AsyncAccept (System.IAsyncResult ar)
{
    //接受到客户端的异步连接请求
    tcpClient = tcpServer.EndAcceptTcpClient (ar);
    //有新的客户端连接提示
    ReceiveCallback ("Accept Client :" + tcpClient.Client.RemoteEndPoint.ToString ());
    //异步接收消息
    tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client);
    //异步接受客户端请求尾递归
    tcpServer.BeginAcceptTcpClient (AsyncAccept, null);
}
/// <summary>
/// 异步接收消息回调
/// </summary>
/// <param name="ar">Ar.</param>
void AsyncReceive (System.IAsyncResult ar)
{
    //获取消息套接字
    Socket workingClient = ar.AsyncState as Socket;
    //完成接收
    int msgLength = workingClient.EndReceive (ar);
    //如果接收到了数据
    if (msgLength > 0) {
        //消息接收提示
        ReceiveCallback ("ReceiveData : " + msgLength + "bytes");
        //临时缓冲区
        byte[] tempBuffer = new byte[msgLength];
        //拷贝数据到临时缓冲区
        Buffer.BlockCopy (buffer, 0, tempBuffer, 0, msgLength);
        //数据放到缓存数据组队尾
        cache.AddRange (tempBuffer); 
        //拆包解析
        byte[] result = LengthDecode (ref cache);
        //如果已经接收完全部数据
        if (result != null) {
            //开始反序列化数据
            NetModel resultModel = DeSerialize (result);
            //TODO:Object Processing!
            //数据对象结果提示
            ReceiveCallback ("Object Result IP : " + resultModel.senderIp);
            ReceiveCallback ("Object Result Content : " + resultModel.content);
            ReceiveCallback ("Object Result Time : " + resultModel.time);
        }
        //消息未接收全,继续接收
        tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client);
    }

}
#endregion

客户端部分

#region Client Part
/// <summary>
/// 客户端连接
/// </summary>
public void ClientConnnect ()
{
    //连接到服务器
    tcpClient.Connect (serverIPEndPoint);
    //连接到服务器提示
    ReceiveCallback ("Has Connect To Server : " + serverIPEndPoint.Address.ToString ());
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="model">Model.</param>
public void SendMsg (NetModel model)
{
    //将数据对象序列化
    buffer = Serialize (model);
    //将序列化后的数据加字节头
    buffer = LengthEncode (buffer);
    //拆分数据,多次发送
    for (int i = 0; i < buffer.Length/1024 + 1; i++) {
        //满发送,1KB
        int needSendBytes = 1024;
        //最后一次发送,剩余字节
        if (i == buffer.Length / 1024) {
            //计算剩余字节
            needSendBytes = buffer.Length - i * 1024;
        }
        //发送本次数据
        tcpClient.GetStream ().Write (buffer, i * 1024, needSendBytes);
    }
}
#endregion

公共方法

#region Public Function
/// <summary>
/// 数据加字节头操作
/// </summary>
/// <returns>数据结果.</returns>
/// <param name="data">源数据.</param>
byte[] LengthEncode (byte[] data)
{
    //内存流实例
    using (MemoryStream ms = new MemoryStream()) {
        //二进制流写操作实例
        using (BinaryWriter bw = new BinaryWriter(ms)) {
            //先写入字节长度
            bw.Write (data.Length);
            //再写入所有数据
            bw.Write (data);
            //临时结果
            byte[] result = new byte[ms.Length];
            //将写好的流数据放入临时结果
            Buffer.BlockCopy (ms.GetBuffer (), 0, result, 0, (int)ms.Length);
            //返回临时结果
            return result;
        }
    }
}
/// <summary>
/// 数据解析,拆解字节头,获取数据.
/// </summary>
/// <returns>源数据.</returns>
/// <param name="cache">缓存数据.</param>
byte[] LengthDecode (ref List<byte> cache)
{
    //如果字节数小于4,出现异常
    if (cache.Count < 4)
        return null;
    //内存流实例
    using (MemoryStream ms = new MemoryStream(cache.ToArray())) {
        //二进制流读操作实例
        using (BinaryReader br = new BinaryReader(ms)) {
            //先读取数据长度,一个int值
            int realMsgLength = br.ReadInt32 ();
            //如果未接收全数据,下次继续接收
            if (realMsgLength > ms.Length - ms.Position) {
                return null;
            }
            //接收完,读取所有数据
            byte[] result = br.ReadBytes (realMsgLength);
            //清空缓存
            cache.Clear ();
            //返回结果
            return result;
        }
    }
}
/// <summary>
/// 序列化数据.
/// </summary>
/// <param name="mod">数据对象.</param>
private byte[] Serialize (NetModel mod)
{
    try {
        //内存流实例
        using (MemoryStream ms = new MemoryStream()) {
            //ProtoBuf协议序列化数据对象
            ProtoBuf.Serializer.Serialize<NetModel> (ms, mod);
            //创建临时结果数组
            byte[] result = new byte[ms.Length];
            //调整游标位置为0
            ms.Position = 0;
            //开始读取,从0到尾
            ms.Read (result, 0, result.Length);
            //返回结果
            return result;
        }
    } catch (Exception ex) {

        Debug.Log ("Error:" + ex.ToString ());
        return null;
    }
}

/// <summary>
/// 反序列化数据.
/// </summary>
/// <returns>数据对象.</returns>
/// <param name="data">源数据.</param>
private NetModel DeSerialize (byte[] data)
{
    try {
        //内存流实例
        using (MemoryStream ms = new MemoryStream(data)) {
            //调整游标位置
            ms.Position = 0;
            //ProtoBuf协议反序列化数据
            NetModel mod = ProtoBuf.Serializer.Deserialize<NetModel> (ms);
            //返回数据对象
            return mod;

        }
    } catch (Exception ex) {
        Debug.Log ("Error: " + ex.ToString ());
        return null;
    }
}
#endregion

服务器实现

using UnityEngine;
using System.Collections;
using System.IO;
using System;

/// <summary>
/// 服务器实现.
/// </summary>
public class ServerDemo : MonoBehaviour
{
    //临时消息接收
    string currentMsg = "";
    Vector2 scrollViewPosition;

    void Start ()
    {
        scrollViewPosition = Vector2.zero;
        //消息委托
        NetUtility.Instance.SetDelegate ((string msg) => {
            Debug.Log (msg);
            currentMsg += msg + "\r\n";
        });
        //开启服务器
        NetUtility.Instance.ServerStart ();
    }

    void OnGUI ()
    {
        scrollViewPosition = GUILayout.BeginScrollView (scrollViewPosition, GUILayout.Width (300), GUILayout.Height (300));
        //消息展示
        GUILayout.Label (currentMsg);
        GUILayout.EndScrollView ();
    }
}

客户端实现

using UnityEngine;
using System.Collections;
using System.Text;
using System;
/// <summary>
/// 客户端实现
/// </summary>
public class ClientDemo : MonoBehaviour
{
    //待解析地址
    public string wwwAddress = "";

    void Start ()
    {
        //消息处理
        NetUtility.Instance.SetDelegate ((string msg) => {
            Debug.Log (msg + "\r\n");
        });
        //连接服务器
        NetUtility.Instance.ClientConnnect ();
        //开启协程
        StartCoroutine (ServerStart ());
    }

    IEnumerator ServerStart ()
    {
        //加载网页数据
        WWW www = new WWW (wwwAddress);
        yield return www;
        //编码获取内容
        string content = UTF8Encoding.UTF8.GetString (www.bytes);
        //内容测试
        Debug.Log (content);
        //待发送对象
        NetModel nm = new NetModel ();
        //消息体
        nm.senderIp = "127.0.0.1";
        nm.content = content;
        nm.time = DateTime.Now.ToString ();
        //发送数据对象
        NetUtility.Instance.SendMsg (nm);
    }
}

Demo下载:链接: http://pan.baidu.com/s/1mhgUALy 密码: u36k
结束语
文中的ProtoBuf是跨平台的,可以跨平台传输对象。本文使用的是传输层的协议,有兴趣的同学可以使用网络层协议Socket实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值