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

一.客户端

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 initialization
	void Start () 
    {
        socketClient = new SocketClient();
        socketClient.AsynConnect();
	}
	
	// Update is called once per frame
	void 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 initialization
	void Start () 
    {
        SocketSingletion.Instance.sendEvent += PrintInfo;
	}
	
	// Update is called once per frame
	void Update () 
    {
	
	}

    public void PrintInfo(SocketMessage sm)
    {
        print("   " + sm.Length + "   " + 
            sm.ModuleType + "   " + sm.MessageType + "   " + sm.Message);
    }
}

2.运行程序

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



  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值