[Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发

来源:互联网 发布:paxos算法 视频 编辑:程序博客网 时间:2024/05/20 20:02

一.客户端

1.定义一个消息体,服务器和客户端通信的时候,传输的就是这样的信息。

using System.Collections;using System.Text;public class SocketMessage {    //大模块,例如登录注册模块,角色模块(行走、释放技能),购买模块    public int ModuleType { get; set; }    //进一步分类,例如登录注册模块中含有登录和注册两种类型    public int MessageType { get; set; }    //SocketMessage的核心,包含各种内容    public string Message { get; set; }    //信息的总字节数    public int Length { get; set; }    public SocketMessage(int moduleType, int messageType, string message)    {        ModuleType = moduleType;        MessageType = messageType;        Message = message;        //Length的字节数,ModuleType的字节数,MessageType的字节数,系统自动添加的存储字符串长度的字节,Message的字节数        Length = 4 + 4 + 4 + 1 + Encoding.UTF8.GetBytes(message).Length;    }}

2.因为传输的是二进制信息,所以要有一个类专门来读取和写入二进制

using System.Collections;using System.IO;using System;using System.Text;//对SocketMessage的读写public class ByteArray {    //为了节省传输的流量,所以传输的是二进制    //读与写操作都是对一个流来进行的,这里使用MemoryStream    private MemoryStream memoryStream;    private BinaryReader binaryReader;    private BinaryWriter binaryWriter;    private int readIndex = 0;    private int writeIndex = 0;    public ByteArray()    {        memoryStream = new MemoryStream();        binaryReader = new BinaryReader(memoryStream);        binaryWriter = new BinaryWriter(memoryStream);    }    public void Destroy()    {        binaryReader.Close();        binaryWriter.Close();        memoryStream.Close();        memoryStream.Dispose();    }    public int GetReadIndex()    {        return readIndex;    }    public int GetLength()    {        return (int)memoryStream.Length;    }    public int GetPosition()    {        //position是从0开始的        return (int)memoryStream.Position;    }    public byte[] GetByteArray()    {        return memoryStream.ToArray();    }    public void Seek(int offset, SeekOrigin seekOrigin)    {        //offset:相对于 SeekOrigin 所指定的位置的偏移量参数        memoryStream.Seek(offset, seekOrigin);    }    #region read    public bool ReadBoolean()    {        Seek(readIndex, SeekOrigin.Begin);        bool a = binaryReader.ReadBoolean();        readIndex += 1;        return a;    }    public short ReadInt16()    {        Seek(readIndex, SeekOrigin.Begin);        short a = binaryReader.ReadInt16();        readIndex += 2;        return a;    }    public int ReadInt32()    {        Seek(readIndex, SeekOrigin.Begin);        int a = binaryReader.ReadInt32();        readIndex += 4;        return a;    }    public float ReadSingle()    {        Seek(readIndex, SeekOrigin.Begin);        float a = binaryReader.ReadSingle();        readIndex += 4;        return a;    }    public double ReadDouble()    {        Seek(readIndex, SeekOrigin.Begin);        double a = binaryReader.ReadDouble();        readIndex += 8;        return a;    }    public string ReadString()    {        Seek(readIndex, SeekOrigin.Begin);        string a = binaryReader.ReadString();        //因为binaryWriter写字符串时会在字符串前面加一字节,存储字符串的长度        readIndex += Encoding.UTF8.GetBytes(a).Length + 1;        return a;    }    #endregion    #region write    public void Write(bool value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += 1;    }    public void Write(short value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += 2;    }    public void Write(int value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += 4;    }    public void Write(float value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += 4;    }    public void Write(double value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += 8;    }    public void Write(string value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        //因为binaryWriter写字符串时会在字符串前面加一字节,存储字符串的长度        writeIndex += Encoding.UTF8.GetBytes(value).Length + 1;    }    public void Write(byte[] value)    {        Seek(writeIndex, SeekOrigin.Begin);        binaryWriter.Write(value);        writeIndex += value.Length;    }    #endregion}

3.定义socket客户端,用来连接服务器,并收发信息

using System.Collections;using System.Net.Sockets;using System.Net;using System;using System.Text;using System.Threading;using UnityEngine;public class SocketClient {    private Socket socket;//当前套接字    private ByteArray byteArray = new ByteArray();//字节数组缓存    private Thread handleMessage;//处理消息的线程    public SocketClient()    {        handleMessage = new Thread(HandleMessage);        handleMessage.Start();    }    public SocketClient(Socket socket)    {        this.socket = socket;        handleMessage = new Thread(HandleMessage);        handleMessage.Start();    }    public Socket GetSocket()    {        return socket;    }    public void Destroy()    {        handleMessage.Abort();        socket.Close();        byteArray.Destroy();    }    /// <summary>    /// 异步连接服务器    /// </summary>    public void AsynConnect()    {        IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);        socket.BeginConnect(serverIp, asyncResult =>        {            socket.EndConnect(asyncResult);            Debug.Log("connect success!");                        AsynRecive();            AsynSend(new SocketMessage(19, 89, "你好,服务器"));            AsynSend(new SocketMessage(19, 89, "你好,服务器"));            AsynSend(new SocketMessage(19, 89, "你好,服务器"));        }, null);    }    /// <summary>    /// 异步接受信息    /// </summary>    public void AsynRecive()    {        byte[] data = new byte[1024];        socket.BeginReceive(data, 0, data.Length, SocketFlags.None,        asyncResult =>        {            int length = socket.EndReceive(asyncResult);            byte[] temp = new byte[length];            Debug.Log("接受到的字节数为" + length);            Array.Copy(data, 0, temp, 0, length);            byteArray.Write(temp);            AsynRecive();        }, null);    }    /// <summary>    /// 异步发送信息    /// </summary>    public void AsynSend(SocketMessage sm)    {        ByteArray ba = new ByteArray();        ba.Write(sm.Length);        ba.Write(sm.ModuleType);        ba.Write(sm.MessageType);        ba.Write(sm.Message);        byte[] data = ba.GetByteArray();        ba.Destroy();        socket.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>        {            int length = socket.EndSend(asyncResult);        }, null);    }    /// <summary>    /// 解析信息    /// </summary>    public void HandleMessage()    {        int tempLength = 0;//用来暂存信息的长度        bool hasGetMessageLength = false;//是否得到了消息的长度        while (true)        {            if (!hasGetMessageLength)            {                if (byteArray.GetLength() - byteArray.GetReadIndex() > 4)//消息的长度为int,占四个字节                {                    tempLength = byteArray.ReadInt32();//读取消息的长度                    hasGetMessageLength = true;                }            }            else            {                //根据长度就可以判断消息是否完整                //GetReadIndex()可以得到已读的字节                //注意上面的ReadInt32读取后,读的索引会加上4,要把多余的减去                if ((tempLength + byteArray.GetReadIndex() - 4) <= byteArray.GetLength())                {                    SocketMessage sm = new SocketMessage(byteArray.ReadInt32(), byteArray.ReadInt32(), byteArray.ReadString());                    //SocketServer.HandleMessage(this, sm);                    SocketSingletion.Instance.Send(sm);                    hasGetMessageLength = false;                }            }        }    }}

4.定义一个单例基类

using UnityEngine;using System.Collections;public class MonoSingletion<T> : MonoBehaviour {    private static T instance;    public static T Instance    {        get        {            return instance;        }    }    void Awake()    {        instance = GetComponent<T>();    }}

5.定义一个类,管理socke客户端的生命周期,并提供事件接口供其他类使用

using UnityEngine;using System.Collections;public class SocketSingletion : MonoSingletion<SocketSingletion> {    public SocketClient socketClient;    public delegate void SendDelegate(SocketMessage sm);    public event SendDelegate sendEvent = null;// Use this for initializationvoid Start ()     {        socketClient = new SocketClient();        socketClient.AsynConnect();}// Update is called once per framevoid Update () {}    public void Send(SocketMessage sm)    {        sendEvent(sm);    }    void OnDestroy()    {        print("Destroy socketClient");        socketClient.Destroy();    }}


二.服务器端

0.这里为了方便直接用c#写的服务器端,打开vs,直接把ByteArray、SocketClient、SocketMessage这三个类复制过来


1.

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net;using System.Net.Sockets;public class SocketServer {    private Socket socket;//当前套接字    public Dictionary<string, SocketClient> dictionary = new Dictionary<string, SocketClient>();//string为ip地址    public void Listen()    {        IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);        socket.Bind(serverIp);        socket.Listen(100);        Console.WriteLine("server ready.");        AsynAccept(socket);    }    /// <summary>    /// 异步连接客户端    /// </summary>    public void AsynAccept(Socket serverSocket)    {        serverSocket.BeginAccept(asyncResult =>        {            Socket client = serverSocket.EndAccept(asyncResult);            SocketClient socketClient = new SocketClient(client);                        string s = socketClient.GetSocket().RemoteEndPoint.ToString();            Console.WriteLine("连接的客户端为: " + s);            dictionary.Add(s, socketClient);            socketClient.AsynRecive();            socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端"));            socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端"));            socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端"));            AsynAccept(serverSocket);        }, null);    }    /// <summary>    /// 解析信息    /// </summary>    public static void HandleMessage(SocketClient sc, SocketMessage sm)    {        Console.WriteLine(sc.GetSocket().RemoteEndPoint.ToString() + "   " +             sm.Length + "   " + sm.ModuleType + "   " + sm.MessageType + "   " + sm.Message);    }}

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ConsoleApplication6{    class Program    {        static void Main(string[] args)        {            SocketServer socketServer = new SocketServer();            socketServer.Listen();            Console.ReadKey();        }    }}


三.测试

1.在unity中新建一个测试类

using UnityEngine;using System.Collections;public class ReceiveSocketMessage : MonoBehaviour {// Use this for initializationvoid Start ()     {        SocketSingletion.Instance.sendEvent += PrintInfo;}// Update is called once per framevoid Update ()     {}    public void PrintInfo(SocketMessage sm)    {        print("   " + sm.Length + "   " +             sm.ModuleType + "   " + sm.MessageType + "   " + sm.Message);    }}

2.运行程序

分析:服务器端向客户端发送了三条信息,而本人使用了两个gameobject来订阅接收的事件,所以打印了6条信息。同时,在客户端中只接受到两条信息,一条字节数为62,另一条字节数为31,说明出现了粘包问题了,这里本人使用的是为每条传递的消息体前加了4个字节(int型),用来记录消息的长度,这样就可以实现分包了。同样的,服务器端只接受了一条信息,也是粘包的体现。



2 0