[Unity] Realize Tcp communication in Unity (2) - server

The previous article realized the Tcp communication of the Unity client, and this article also implements the Tcp of the server, and enables the client and server to perform joint debugging.

For the client, one application (one device) corresponds to one Socket.

But the server is different. A server needs to process many client requests. Every time a client successfully establishes a connection with the server, a new socket object needs to be created. This also reflects the one-to-one communication in the Tcp protocol. a feature.

The creation process of the server-side Socket is similar to that of the client, and still requires three parameters

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

If you don’t understand what each parameter means, you can read my previous client blog, which has a detailed introduction.

Let's start the workflow of the server socket (here, just create a console application using .NetFramework)


connecting part

1. Call the Bind method to bind an ip and port, that is to say, the client can only receive it if the client initiates a request to this ip and port

2. After the Bind is successful, call the Listen method to set the maximum number of listeners. When the connected client exceeds this value, no new connection will be established.

static void Main(string[] args)
{
    m_Socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    m_AcceptThread = new Thread(OnAccept);//另起一个线程进入阻塞状态等待客户端连接
    m_Clients = new List<RoleClient>();//所有连接的客户端
    m_Socket.Bind(new IPEndPoint(IPAddress.Parse(m_IP), m_Port));//绑定ip端口
    m_Socket.Listen(50);//最大监听数50
    m_AcceptThread.Start();//进入等待连接状态
    AppDomain.CurrentDomain.ProcessExit += OnApplicatonQuit;
    Console.WriteLine("服务器启动成功!");
    Console.WriteLine("监听IP:" + m_IP + ",端口:" + m_Port);
    //广播代码
    while(true)
    {
        string str = Console.ReadLine();


        if (string.IsNullOrEmpty(str)) continue;

        if (str.Equals("close all"))
        {
            for (int i = m_Clients.Count - 1; i >= 0 ; i--)
            {
                m_Clients[i].Close(true);
            }
        }
        else
        {
            for (int i = 0; i < m_Clients.Count; i++)
            {
                m_Clients[i].Send(1, Encoding.UTF8.GetBytes(str));
            }

        }
    }
}

 

3. Start another thread and enter the blocking state, waiting for the client to connect

4. If a client successfully connects, create a message processing instance corresponding to the client, and add the client to a table for broadcasting

static void OnAccept()
{
    while(true)
    {
        try
        {
            Socket client = m_Socket.Accept();//尝试接收一个客户端的连接
            IPEndPoint clientPoint = client.RemoteEndPoint as IPEndPoint;
            m_Clients.Add(new RoleClient(client, m_Clients));//为客户端建立一个请求处理实例,并加入到表中
            Console.WriteLine("客户端:"+ clientPoint.Address.ToString() +"已经连接!");
        }
        catch
        {
            continue;
        }
    }
}

5. The server program closes and closes all client connections at the same time

private static void OnApplicatonQuit(object sender, EventArgs e)
{
    for (int i = m_Clients.Count - 1; i > -1 ; i--)
    {
        m_Clients[i].Close();
    }

    m_AcceptThread.Abort();
    m_Clients.Clear();
}

At this point, the client requests a connection, and the server receives the connection and creates the corresponding request instance. The function has been implemented. Let’s start joint debugging and try

Start the server first, as shown in the figure, successfully listened to the local ip of 127.0.0.1 and port 8888

Then the client makes a test interface, hangs SocketMgr on an empty object, and writes the corresponding test code

public Button button;
public InputField inputField;

void Start () 
{
    button.onClick.AddListener(onClick);
	inputField.gameObject.SetActive(false);

	SocketMgr.Instance.OnConnectSuccess = delegate () 
	{
        inputField.gameObject.SetActive(true);
		button.transform.Find("Text").GetComponent<Text>().text = "发送";
	};

    SocketMgr.Instance.OnDisConnect = delegate ()
    {
        inputField.gameObject.SetActive(false);
		button.transform.Find("Text").GetComponent<Text>().text = "连接";
	};

	SocketMgr.Instance.onReceive = OnReceive;
}

private void OnReceive(ushort arg1, byte[] arg2)
{
    inputField.text = arg1 + "," + Encoding.UTF8.GetString(arg2);
}

private void onClick()
{
    if(!SocketMgr.Instance.IsConnected)
	{
        SocketMgr.Instance.Connect("127.0.0.1", 8888);
		return;
	}

	SocketMgr.Instance.Send(1, Encoding.UTF8.GetBytes(inputField.text));
}

SocketMgr is the Socket communication framework encapsulated in the client part of my last article. The specific codes have all been pasted in the blog. If you want to try it yourself, you can copy it directly and use it.

 

Next, click to run, and the moment to witness the miracle is coming

Clicking the connect button will send a connection establishment request to the server through the connect method. The connection is successfully established here, and there should be thunderous applause here.


Unpacking and sending part

The unpacking and sending of packages on the server side are basically similar to those on the client side. The difference is that there is no distinction between the main thread and the non-main thread on the server side. After unpacking, the data can be distributed directly in the callback of BeginReceive. Because the client part has been elaborated in detail, I won’t explain more here, just upload the code directly

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    public class RoleClient
    {
        Timer timer;
        public RoleClient(Socket socket, List<RoleClient> otherClients)
        {
            m_OtherClients = otherClients;
            m_ReceiveBuffer = new byte[1024 * 512];
            m_ReceiveStream = new MemoryStream();
            m_ReceiveQueue = new Queue<byte[]>();
            m_SendQueue = new Queue<byte[]>();
            m_Socket = socket;
            m_IsConnected = true;
            //m_ReceiveThread = new Thread(CheckReceive);
            timer = new Timer(CheckReceiveBuffer, 0, 0,200);
            //m_ReceiveThread.Start();
            StartReceive();
        }

        public void Send(ushort msgCode, byte[] buffer)
        {
            byte[] sendMsgBuffer = null;

            using (MemoryStream ms = new MemoryStream())
            {
                int msgLen = buffer.Length;
                byte[] lenBuffer = BitConverter.GetBytes((ushort)msgLen);
                byte[] msgCodeBuffer = BitConverter.GetBytes(msgCode);
                ms.Write(lenBuffer, 0, lenBuffer.Length);
                ms.Write(msgCodeBuffer, 0, msgCodeBuffer.Length);
                ms.Write(buffer, 0, msgLen);
                sendMsgBuffer = ms.ToArray();
            }

            lock (m_SendQueue)
            {
                m_SendQueue.Enqueue(sendMsgBuffer);
                CheckSendBuffer();
            }
        }

        public void Close(bool isForce = false)
        {
            try { m_Socket.Shutdown(SocketShutdown.Both); }
            catch { }

            if (isForce)
            {
                IPEndPoint endPoint = m_Socket.RemoteEndPoint as IPEndPoint;
                Console.WriteLine("强制关闭与客户端:" + endPoint.Address.ToString() + "的连接");
            }

            m_IsConnected = false;
            m_Socket.Close();
            m_ReceiveStream.SetLength(0);
            m_ReceiveQueue.Clear();
            m_SendQueue.Clear();
            timer.Dispose();

            if (m_OtherClients != null)
            {
                m_OtherClients.Remove(this);
            }

            timer = null;
            m_SendQueue = null;
            m_ReceiveQueue = null;
            m_ReceiveStream = null;
            m_ReceiveBuffer = null;
            m_OtherClients = null;
        }

        private void StartReceive()
        {
            if (!m_IsConnected) return;
            m_Socket.BeginReceive(m_ReceiveBuffer, 0, m_ReceiveBuffer.Length, SocketFlags.None, OnReceive, m_Socket);
        }

        private void OnReceive(IAsyncResult ir)
        {
            if (!m_IsConnected) return;
            try
            {
                int length = m_Socket.EndReceive(ir);
                if(length < 1)
                {
                    IPEndPoint endPoint = m_Socket.RemoteEndPoint as IPEndPoint;
                    Console.WriteLine("客户端:" + endPoint.Address.ToString() + "已断开连接");
                    Close();
                    return;
                }

                m_ReceiveStream.Position = m_ReceiveStream.Length;
                m_ReceiveStream.Write(m_ReceiveBuffer, 0, length);

                if (m_ReceiveStream.Length < 3)
                {
                    StartReceive();
                    return;
                }

                while (true)
                {
                    m_ReceiveStream.Position = 0;
                    byte[] msgLenBuffer = new byte[2];
                    m_ReceiveStream.Read(msgLenBuffer, 0, 2);
                    int msgLen = BitConverter.ToUInt16(msgLenBuffer, 0) + 2;
                    int fullLen = 2 + msgLen;

                    if (m_ReceiveStream.Length < fullLen)
                    {
                        break;
                    }

                    byte[] msgBuffer = new byte[msgLen];
                    m_ReceiveStream.Position = 2;
                    m_ReceiveStream.Read(msgBuffer, 0, msgLen);

                    lock (m_ReceiveQueue)
                    {
                        m_ReceiveQueue.Enqueue(msgBuffer);
                    }

                    int remainLen = (int)m_ReceiveStream.Length - fullLen;

                    if (remainLen < 1)
                    {
                        m_ReceiveStream.Position = 0;
                        m_ReceiveStream.SetLength(0);
                        break;
                    }

                    m_ReceiveStream.Position = fullLen;
                    byte[] remainBuffer = new byte[remainLen];
                    m_ReceiveStream.Read(remainBuffer, 0, remainLen);
                    m_ReceiveStream.Position = 0;
                    m_ReceiveStream.SetLength(0);
                    m_ReceiveStream.Write(remainBuffer, 0, remainLen);
                    remainBuffer = null;
                }
            }
            catch
            {
                IPEndPoint endPoint = m_Socket.RemoteEndPoint as IPEndPoint;
                Console.WriteLine("客户端:" + endPoint.Address.ToString() + "已断开连接");
                Close();
                return;
            }

            StartReceive();
        }

        private void CheckSendBuffer()
        {
            lock (m_SendQueue)
            {
                if (m_SendQueue.Count > 0)
                {
                    byte[] buffer = m_SendQueue.Dequeue();
                    m_Socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, m_Socket);
                }
            }
        }

        private void CheckReceiveBuffer(object state)
        {
            lock (m_ReceiveQueue)
            {
                if (m_ReceiveQueue.Count < 1) return;
                byte[] buffer = m_ReceiveQueue.Dequeue();
                byte[] msgContent = new byte[buffer.Length - 2];
                ushort msgCode = 0;

                using (MemoryStream ms = new MemoryStream(buffer))
                {
                    byte[] msgCodeBuffer = new byte[2];
                    ms.Read(msgCodeBuffer, 0, msgCodeBuffer.Length);
                    msgCode = BitConverter.ToUInt16(msgCodeBuffer, 0);
                    ms.Read(msgContent, 0, msgContent.Length);
                }

                Console.WriteLine("消息编号:" + msgCode + ",内容:" + Encoding.UTF8.GetString(msgContent));
            }
        }

        private void SendCallback(IAsyncResult ir)
        {
            m_Socket.EndSend(ir);
            CheckSendBuffer();
        }

        private bool m_IsConnected = false;
        private Queue<byte[]> m_ReceiveQueue = null;
        private Queue<byte[]> m_SendQueue = null;
        private MemoryStream m_ReceiveStream = null;
        private byte[] m_ReceiveBuffer = null;
        private Socket m_Socket = null;
        private List<RoleClient> m_OtherClients = null;
    }
}

Next, the final test is performed, that is, the client and the server communicate with each other.


test part

The client enters any string, and then clicks send. Here, the message code is hard-coded as 1, but in actual development, each message has its own number. It is necessary to decide which message to send according to the actual situation.

The server successfully receives the data and parses out the message code and specific content

 

The server enters any string to see if the client can receive the message

The client also successfully parsed the content

 

Enter close all on the server to see if it can disconnect from the client

The client prints the log and disconnects

 

Forcibly close the client, the server can also detect the disconnection of the client 

So far, the client and server of Tcp communication have basically been realized.

But in actual development, the communication content is not just a string, but some very complex data structures. Detecting client disconnection cannot determine client disconnection simply by relying on the length of endreceive being 0. The data between modules must be distributed separately without causing coupling. How is this achieved?

In the next few articles, I will explain the serialization tool Protobuf, observer message distribution, and heartbeat mechanism in turn, and elaborate on how these functions are realized.

Guess you like

Origin blog.csdn.net/s_GQY/article/details/106192109