Unity3D开发之Socket通信

    最近在研究网络同步,感觉很有意思。服务端与客户端都是用c#写的。很遗憾,现在的项目服务端都是用c++或者java写的。可能c#写出的效率不行?

    socket通信协议我们通过知道的两种。一种是TCP协议,一种是UDP协议。TCP是以传输数据稳定而夺得游戏开发者的垂爱。UDP因为容易掉数据导致对于精准数据的传输方面被坐冷板凳,但是UDP的优点是传输速度快,适用于工业展示类数据传输。丢几个数据无所谓,反正是要对整体数据走势分析。

    通常我们使用TCP协议。而TCP传输过程中会出现粘包分包问题。

粘包:

    当我们高频率传输一些小的数据时,TCP协议会自动将这些小的数据打包在一起发送出去,产生了所谓的粘包问题。这是TCP的内部优化。

分包:

    当我们一次发送大量数据时,TCP会自动将数据切分多块然后分批次发送。

解决方法:我们在每次发送数据时,都要加上数据头。通常我们设定数据头为int32类型,占四个字节。数据头的内容表示我们发送的数据占了多少字节。这样我们对于粘包的数据,可以根据数据头对数据进行一步一步的解析读取。

    下面是Socket通信的代码部分:

首先我们需要创建一个message类来处理接收到的数据:

数据处理帮助类:

public class Messgae
{
    //用来存储接收到消息的数据
    private byte[] data=new byte[1024];
    //开始往数组里添加数据的索引  因为可能存在分包的问题  所以startIndex不一定都是0
    private int startIndex = 0;

    public byte[] Data { get { return data; } }

    public int StartIndex { get { return startIndex; } }

    public int RemainSize { get { return data.Length - startIndex; } }

  public void ReadMessage(int newDataAmount,Action<ActionCode,string> ProcessCallback )
    {
        startIndex += newDataAmount;
        while (true)
        {
            if(startIndex<=4) return;
            int count = BitConverter.ToInt32(data, 0);
            //有可能存在粘包   需要根据数据头来解析数据
            if (startIndex - 4 >= count)
            {
                //TODO 解析数据
                ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 4);
                Debug.Log(actionCode);
                string s = Encoding.UTF8.GetString(data, 8, count - 4);
                Debug.Log(s);
                ProcessCallback(actionCode,s);//解析完一段数据  就去调用回调函数来解析我们获取到的数据
                Array.Copy(data,count+4,data,0,startIndex-4-count);//这里要将已经解析的数据从存储数组中去除 开始下一段数据的解析
                startIndex -= (count + 4);
            }
        }
    }

 客户端:

public class ClientManager : BaseManager
{
    private static string IP = "";
    private static int PORT = 8888;

    private Socket clientSocket;
    Messgae message=new Messgae();

    void StartClientSocket()
    {
        clientSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
        IPEndPoint ipEndPoint=new IPEndPoint(int.Parse(IP),PORT);
        try
        {
            clientSocket.Connect(ipEndPoint);
            StartReceieve();
        }
        catch (Exception e) 
        {
            Debug.Log("无法连接到服务器!"+e);
        }
    }

    void StartReceieve()
    {
        //开始异步接收服务端发送过来的消息  我们需要创建一个message类来处理我们读取到的数据
        clientSocket.BeginReceive(message.Data, message.StartIndex, message.RemainSize, SocketFlags.None,
            ReceieveCallBack, null);
    }

    private void ReceieveCallBack(IAsyncResult ar )
    {
        try
        {
            int count = clientSocket.EndReceive(ar);
            message.ReadMessage(count);//此处我们创建一个message类来处理我们读取到的数据
            StartReceieve();
        }
        catch (Exception e)
        {
            Debug.Log("客户端接收消息错误"+e);
        }
    }

    //发送数据给服务端
    private void Send(byte[] data)
    {
        clientSocket.Send(data);
    }


服务端:

class Server
    {
        private Socket serverSocket;
        private IPEndPoint ipEndPoint;

        private List<Client> clientList=new List<Client>();
        private ControllerManager controllerMgr;

        public Server() { }

        public Server(string ip, int port)
        {
            controllerMgr=new ControllerManager(this);
            ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);//0代表不限制连接服务器的客户端的数量
            serverSocket.BeginAccept(AcceptCallBack, null);//AcceptCallback代表接收到客户端来的消息后的回调函数
        }

        private void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar);
            Client client = new Client(clientSocket, this);//这里我们创建一个Client类来专门管理连接到服务器的客户端
            clientList.Add(client);
            client.Start();
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
    }

在服务器端用来管理连接服务器的客户端的Client类:

class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message message = new Message();

        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }

        public void Start()
        {
	    //这里用来中断客户端的断连  不然服务端要一直报错
            if(clientSocket==null||clientSocket.Connected==false) return;
            clientSocket.BeginReceive(message.Data, message.StartIndex, message.RemainSize, SocketFlags.None,
                ReceieveCallback, null);
        }

        private void ReceieveCallback(IAsyncResult ar)
        {
            try
            {
		//这里用来中断客户端的断连  不然服务端要一直报错
                if (clientSocket == null || clientSocket.Connected == false) return;
                int count = clientSocket.EndReceive(ar);
                if (count == 0)
                {
                    Close();
                }
                Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("解析客户端错误!" + e);
            }
        }

       

        public void Send(byte[] bytes)
        {
            clientSocket.Send(bytes);
        }

        private void Close()
        {
            if (clientSocket != null)
                clientSocket.Close();
        }
    }
在项目架构上,我们通常不会只发送头数据加字符串数据,根据项目需要我们也要加上我们的请求模块和响应模块。希望本博客能帮到你!


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值