【Unity】 在Unity中实现Tcp通讯(2)—— 服务端

上一篇实现了Unity客户端的Tcp通讯,这篇把服务端的Tcp也实现一下,并使客户端和服务端进行联调。

对于客户端来说,一个应用(一个设备)对应一个Socket。

但服务端不同,一个服务端需要处理许多个客户端的请求,每有一个客户端和服务端成功建立连接都需要创建一个新的socket的对象,这也体现了Tcp协议中一对一通讯的这一特点。

服务端Socket的创建流程和客户端类似,依然需要三个参数

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

不明白每个参数代表什么意思可以看我的上一篇客户端的博客,里面有详细的介绍。

下面开始服务端Socket的工作流程(这里创建使用.NetFramework的控制台应用即可)


连接部分

1.调用Bind方法绑定一个ip和端口,也就是说客户端只有向这个ip和端口发起请求服务端才能接收到

2.Bind成功之后,调用Listen方法设置最大监听数,当连接的客户端超过这个数值就不再建立新的连接

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.另起一个线程并进入阻塞状态,等待客户端连接

4.有客户端成功连接,创建对应客户端的消息处理实例,并把该客户端加入一个表中以备广播时使用

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. 服务端程序关闭同时关闭所有客户端的连接

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();
}

到这里客户端请求连接,服务端收到连接并创建对应的请求实例的功能已经实现完了,下面开始联调一下试试

先启动服务器,如图,成功监听了本地127.0.0.1的ip和8888端口

然后客户端制作一个测试的界面,把SocketMgr挂到一个空物体上,并编写对应的测试代码

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就是我上一篇客户端部分封装的Socket通讯框架,具体的代码已经全部都贴到到了博客中,如果想自己试试的可以直接去复制来用。

 

接下来点击运行,见证奇迹的时刻就要到了

点击连接按钮就会同过connect方法向服务端发送建立连接的请求,这里成功的建立的了连接,此处应有掌声雷动.


拆包、发包部分

服务端拆包和发包与客户端基本类似,不同的地方就在于服务端不存在主线程和非主线程这种区分,拆包后直接在BeginReceive的回调中进行数据派发即可。因为客户端部分已经做了细致的阐述,这里就不多做说明了,直接上代码

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;
    }
}

接下来进行最后的测试,即客户端和服务端相互通讯。


测试部分

客户端输入任意字符串,然后点击发送,这里消息编码写死为1,但实际开发中每一个消息都有自己的编号要根据实际情况来决定要发送哪条消息。

服务端成功的接收到数据并解析出消息编码和具体内容

 

服务端输入任意字符串,看看客户端能否接到消息

客户端也成功的解析出了内容

 

服务端输入close all看能否跟客户端断开连接

客户端打印日志,断开连接

 

强制关闭客户端,服务端也能检测到客户端的断开 

到这里Tcp通讯的客户端和服务端已经基本实现完了。

但是实际开发中,通讯内容可不仅仅是字符串,而是十分复杂的一些数据结构。检测客户端断开也不能单单凭借endreceive的长度为0就确定客户端断开,各个模块间的数据要分别派发不能造成耦合,那这些是怎么实现的呢?

下面几篇我就依次来进行序列化工具Protobuf、观察者消息派发、以及心跳机制的讲解,详细阐述这些功能是如何实现的。

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity 是一款强大的游戏开发引擎,但本身并不直接支持 WebSocket 服务端。但是,我们可以借助 Unity 的网络功能和一些第三方库来实现 WebSocket 服务端。 要实现 WebSocket 服务端,我们可以使用 .NET 库的 HttpListener 类。首先,我们需要创建一个新的 C# 脚本,并在其引入 System.Net 命名空间。然后,我们可以创建一个 HttpListener 对象,设置监听的地址和端口。例如: HttpListener httpListener = new HttpListener(); httpListener.Prefixes.Add("http://localhost:8080/"); 接下来,我们需要开始监听这个地址和端口: httpListener.Start(); 然后,我们可以使用异步方式等待客户端的连接: IAsyncResult result = httpListener.BeginGetContext(new AsyncCallback(ListenerCallback), httpListener); 在 ListenerCallback 回调函数,我们可以获取客户端的连接和请求信息: void ListenerCallback(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); // 处理客户端的请求 } 在处理客户端请求的过程,我们可以根据不同的请求类型,实现相应的 WebSocket 逻辑。使用 .NET 的 WebSocket 类,我们可以从 HttpListenerContext 获取 WebSocket 对象,并执行诸如发送消息、接收消息等操作。 需要注意的是,为了实现完整的 WebSocket 逻辑,我们可能还需要处理握手过程的协议判断、消息编码解码等细节。 综上所述,要在 Unity 实现 WebSocket 服务端,我们可以利用 .NET 的 HttpListener 类来监听客户端连接,并在处理请求过程实现 WebSocket 的相关逻辑。这样就可以通过 Unity 实现 WebSocket 服务端了。 ### 回答2: Unity 是一款游戏开发引擎,通常用于开发各种类型的游戏。虽然 Unity 自身不支持直接实现 WebSocket 服务端,但我们可以通过使用插件或自定义脚本来实现。 首先,我们可以选择使用第三方插件或库,如 Best HTTP、WebSocket-Sharp 等来在 Unity 实现 WebSocket 服务端。这些插件可以通过提供的 API 来创建、监听和处理 WebSocket 连接。我们可以根据项目需求选择最适合的插件,然后按照其文档进行配置和使用。 另外,如果我们希望自己编写 WebSocket 服务端,可以通过使用 Unity 提供的网络相关 API 来实现。首先,我们可以通过 Unity 的 NetworkTransport 类来创建一个基于 UDP 或 TCP 的网络连接。然后,我们可以使用 NetworkTransport.ReceiveFromHost() 方法来接收来自客户端的消息,并使用 NetworkTransport.SendToHost() 方法向客户端发送消息。使用这些 API,我们可以在 Unity 实现一个简单的 WebSocket 服务端。 不过需要注意的是,WebSocket 是一种基于 TCP 的双向通信协议,与 HTTP 协议不同。在实现 WebSocket 服务端时,我们需要遵循 WebSocket 的协议规范,并正确处理握手、数据帧等操作。此外,我们还需要考虑并发连接、消息分发等问题,以确保 WebSocket 服务端的稳定性和可靠性。 总结来说,Unity 虽然不直接支持实现 WebSocket 服务端,但我们可以通过使用第三方插件或自定义脚本来实现。无论选择哪种方式,我们都需要理解 WebSocket 的协议规范,并根据需求正确配置和使用相关的插件或 API。 ### 回答3: 使用Unity实现WebSocket服务器可以通过Unity自带的Networking组件以及C#的WebSocketSharp库来实现。下面是一个简单的步骤: 1. 在Unity创建一个空的场景。然后创建一个空的游戏对象,并添加一个脚本来处理WebSocket服务器的逻辑。 2. 在脚本导入WebSocketSharp库。你可以通过下载WebSocketSharp库的源代码,然后将其导入到Unity项目,或者使用其他方法(如NuGet)从包管理器引入。 3. 在脚本添加WebSocket服务器的逻辑。你需要创建一个WebSocket服务器对象,并指定监听的端口号。例如: ``` using WebSocketSharp; using UnityEngine; public class WebSocketServer : MonoBehaviour { private WebSocketServer wsServer; private void Start() { wsServer = new WebSocketServer(12345); // 指定端口号 wsServer.Start(); wsServer.OnMessage += (sender, e) => { Debug.Log("Received message: " + e.Data); // 处理接收到的消息 }; wsServer.OnClose += (sender, e) => { Debug.Log("Connection closed"); // 处理连接关闭的逻辑 }; } private void OnDestroy() { if (wsServer != null) { wsServer.Stop(); wsServer = null; } } } ``` 4. 将脚本挂载到空的游戏对象上。然后按下Play按钮以在Unity启动WebSocket服务器。 5. 在客户端上使用WebSocket连接到服务器。你可以使用浏览器的WebSocket API或其他WebSocket库来实现。提供服务器的IP地址和端口号,然后进行连接。 这样,你就可以通过Unity实现基本的WebSocket服务器。你可以根据具体的需求在OnMessage和OnClose事件添加更多逻辑来处理消息和连接的关闭。同时需要注意,Unity的Networking组件也提供了一些网络功能,你也可以尝试使用这些组件来实现WebSocket服务器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值