当前位置: 首页 > news >正文

Unity网络通信(part5.编写服务端与客户端)

目录

前言

基础写法

服务端基础写法

客户端基础写法

效果实现

服务端效果

客户端效果

服务端与多个客户端进行通信

封装服务端

客户端的Socket类

服务端Socket类

 启动类

封装客户端

网络管理类

启动类

最终实现


前言

        在前面的部分中,我们已经探讨了Unity网络通信的基础知识和相关概念。现在,我们将进入实际编程阶段,通过编写服务端与客户端的代码,来实现两者之间的通信。

        网络通信是现代游戏开发中不可或缺的一部分,它允许游戏内的玩家进行实时互动和数据传输。在Unity中,我们可以利用Socket编程来实现这一功能。Socket是一种网络通信的端点,它提供了在不同计算机或进程之间进行数据传输的能力。

        在本次教程中,我们将分别编写服务端和客户端的代码。服务端将负责监听客户端的连接请求,并处理客户端发送的数据。客户端则负责连接到服务端,并发送和接收数据。        


基础写法

服务端的 Accept、Receive是会阻塞主线程的,要等到执行完毕才会继续执行后面的内容。

服务端基础写法

using System.Net;
using System.Net.Sockets;
using System.Text;namespace TcpServer
{class Server{static void Main(string[] args){#region 服务端需要做的事情//1.创建套接字socket//2.用Bind方法将套接字与本地地址绑定//3.用Listen方法监听//4.用Accept方法等待客户端连接//5.建立连接,Accept返回新套接字//6.用send和Receive相关方法收发数据//7.用shutdown方法释放连接//8.关闭套接字#endregion#region 实现服务端的基本逻辑//1.创建套接字socket(TCP)Socket socketTcp = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//2.用Bind方法将套接字与本地地址绑定try{IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socketTcp.Bind(ipPoint);}catch (Exception e){Console.WriteLine("绑定IP地址与端口失败:"+e.Message);return;}//3.用Listen方法监听socketTcp.Listen(1024);//监听最大数量Console.WriteLine("服务端监听结束,等待客户端连入");//4.用Accept方法等待客户端连入//5.建立连接,Accept返回新套接字Socket socketClient = socketTcp.Accept();//阻塞方法,后面的方法会等待该方法执行完后才会执行Console.WriteLine("有客户端连入");//6.用Send和Receive相关的方法收发数据//发送socketClient.Send(Encoding.UTF8.GetBytes("服务端连接成功"));//接收byte[] result = new byte[1024];int receiveNumber = socketClient.Receive(result);//记录接收到了多少个字节数Console.WriteLine("接收到了{0}发来的消息:{1}",socketClient.RemoteEndPoint.ToString(),Encoding.UTF8.GetString(result,0,receiveNumber));//7.用Shutdown方法释放连接socketClient.Shutdown(SocketShutdown.Both);//接受和发送消息都停止Console.WriteLine("停止收发消息");//8.关闭套接字socketClient.Close();Console.WriteLine("连接已关闭");#endregionConsole.WriteLine("按任意键退出");Console.ReadKey();}}
}

客户端基础写法

using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;namespace TcpClient
{class Client: MonoBehaviour{void Start (){#region 客户端需要做的事情//1.创建套接字Socket//2.用Connect方法与服务端相连//3.用Send和Receive相关方法收发数据//4.用Shutdown方法释放连接//5.关闭套接字#endregion#region 实现客户端基本逻辑//1.创建套接字类型Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//用Connect方法与服务端相连//确定服务端的IP和端口IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"),8080);//此处应填写远端服务器的地址try{socket.Connect(ipPoint);}catch (SocketException e){if (e.ErrorCode == 10061){print("服务器拒绝连接");}elseprint("连接服务器失败"+e.ErrorCode);}//3.用Send和Recive相关方法收发数据//接收数据byte[] receiveBytes = new byte[1024];int receiveNum = socket.Receive(receiveBytes);print("收到服务端发来的消息:"+Encoding.UTF8.GetString(receiveBytes,0,receiveNum));//发送数据socket.Send(Encoding.UTF8.GetBytes("我是客户端,我来了。"));//4.用Shutdown方法释放连接socket.Shutdown(SocketShutdown.Both);//5.关闭套接字socket.Close();#endregion}

效果实现

先运行服务端代码(服务端代码为控制台工程),再将客户端脚本挂载到Unity场景中,运行Unity项目。

服务端效果

客户端效果


服务端与多个客户端进行通信

using System.Net.Sockets;
using System.Net;
using System.Text;namespace TcpServer
{class MultipleConnectionsServer{static Socket socketTcp;static List<Socket> clientSockets = new List<Socket>();//记录客户端的Socketstatic bool isClose = false;static void Main(string[] args){           //创建套接字socket(TCP)socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//用Bind方法将套接字与本地地址绑定try{IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socketTcp.Bind(ipPoint);}catch (Exception e){Console.WriteLine("绑定IP地址与端口失败:" + e.Message);return;}//用Listen方法监听socketTcp.Listen(1024);//监听最大数量Console.WriteLine("服务端监听结束,等待客户端连入");//建立客户端连接Thread acceptThread = new Thread(AcceptClientConnect);acceptThread.Start();//收发数据Thread receiveThread = new Thread(ReceiveMsg);receiveThread.Start();//关闭相关Console.WriteLine("输入Quit退出");while (true){string input = Console.ReadLine();//定义一个规则,关闭服务器,断开所有连接if (input =="Quit"){for(int i = 0;i<clientSockets.Count;i++){clientSockets[i].Shutdown(SocketShutdown.Both);clientSockets[i].Close();}break;}//定义一个规则 广播消息 就是让所有客户端和服务端发送的消息else if(input.Substring(0,2)=="B:"){for(int i=0;i<clientSockets.Count;i++){//给所有客户端广播消息clientSockets[i].Send(Encoding.UTF8.GetBytes(input.Substring(2)));}}}}static void AcceptClientConnect(){while (!isClose)//用来不停接收客户端的连入{Socket clientSocket = socketTcp.Accept();clientSockets.Add(clientSocket);clientSocket.Send(Encoding.UTF8.GetBytes("服务端连接成功"));}}static void ReceiveMsg(){//将临时变量声明到while循环外面降低对设备的开销Socket clientSocket;byte[] result = new byte[1024*1024];//1兆的最大容量int receiveNum;int i;while(!isClose){for(i = 0;i<clientSockets.Count;i++){clientSocket = clientSockets[i];//判断Socket是否有可以接收的消息,返回值就是字节数if(clientSocket.Available>0){receiveNum = clientSocket.Receive(result);//如果直接在这里收到消息就是处理,可能照成问题//不能够即使的处理别人的消息//为了不影响别人消息的处理,我们把消息处理交给新的线程,为了节约线程的开销,我们使用线程池//用过的线程放入池中,要用时将他拿出来//(clientSocket,Encoding.UTF8.GetString(result,0,receiveNum))第一个参数为通信通道ThreadPool.QueueUserWorkItem(HandleMsg,(clientSocket,Encoding.UTF8.GetString(result,0,receiveNum)));}}}}static void HandleMsg(Object obj){(Socket s, string str) info = ((Socket s, string str))obj;Console.WriteLine("收到客户端{0}f发来的信息:{1}",info.s.RemoteEndPoint,info.str);}}
}

封装服务端

客户端的Socket类

客户端的Socket类,服务端会连接多个客户端,因此对每个客户端对象的操作方法封装在这里。

using System.Net.Sockets;
using System.Text;namespace TcpServer
{class ClientSocket{private static int CLIENT_BEGIN_ID = 1;public int clientID;public Socket socket;public ClientSocket(Socket socket){this.clientID = CLIENT_BEGIN_ID;this.socket = socket;++CLIENT_BEGIN_ID;}//是否是连接状态public bool Conected => this.socket.Connected;//封装方法//关闭public void Close(){if(socket!=null){socket.Shutdown(SocketShutdown.Both);socket.Close();}}//发送public void Send(string info){if(socket != null){try{socket.Send(Encoding.UTF8.GetBytes(info));}catch(Exception e){Console.WriteLine("发送消息出错:"+e.Message);}}}//接收public void Receive(){if(socket != null){try{if(socket.Available >0){byte[] result = new byte[1024 * 5];//5KBint recevieNum = socket.Receive(result);ThreadPool.QueueUserWorkItem(MsgHandle,Encoding.UTF8.GetString(result,0,recevieNum));}}catch (Exception e){Console.WriteLine("接收消息出错:"+e.Message);Close();}}}private void MsgHandle(object obj){string? str = obj as string;Console.WriteLine("收到客户端{0}发来的消息:{1}",this.socket,str);}}
}

服务端Socket类

服务端ServerSocket类,封装了服务端开启、关闭和收发消息的方法。 

using System.Net;
using System.Net.Sockets;namespace TcpServer
{class ServerSocket{//服务端Socketpublic Socket socket;//客户端链接的所有Socketpublic Dictionary<int, ClientSocket> clientDic = new Dictionary<int, ClientSocket>();private bool isClose;//开启服务器端public void Start(string ip,int port,int num){isClose = false;socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip),port);socket.Bind(ipPoint);socket.Listen(num);ThreadPool.QueueUserWorkItem(Accpet);ThreadPool.QueueUserWorkItem(Receive);}//关闭服务器端public void Close(){foreach (ClientSocket client in clientDic.Values){client.Close();}clientDic.Clear();socket.Shutdown(SocketShutdown.Both);socket.Close();socket = null;}//接收客户端连入private void Accpet(object obj){while(!isClose){try{//连入了一个客户端Socket clientSockket = socket.Accept();ClientSocket client = new ClientSocket(clientSockket);client.Send("欢迎连入服务器");clientDic.Add(client.clientID,client);}catch (Exception e){Console.WriteLine("客户端连入报错"+e.Message);throw;}}}//接收客户端消息private void Receive(object obj){while(!isClose){if(clientDic.Count>0){foreach(ClientSocket client in clientDic.Values ){client.Receive();}}}}public void Broadcast(string info){foreach (ClientSocket client in clientDic.Values){client.Send(info);}}}
}

 启动类

namespace TcpServer
{class Program{static void Main(string[] args){ServerSocket socket =new ServerSocket();socket.Start("127.0.0.1",8080,1024);Console.WriteLine("服务开启成功");while(true){string input = Console.ReadLine();if(input == "Quit"){socket.Close();}else if(input.Substring(0,2)=="B:"){socket.Broadcast(input.Substring(2));}}}}
}

封装客户端

网络管理类

使用单例模式,将与客户端有关的方法都封装在这里

using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;public class NetMgr : MonoBehaviour
{private static NetMgr instance;public static NetMgr Instance => instance;//客户端Socketprivate Socket socket;//用于发送消息的队列 公共容器 主线程往里面放 发送线程往里面取private Queue<string> sendMsgQueue = new Queue<string>();//用于接收消息的队列 公共容器 子线程往里面放 主线程往里面取private Queue<string> receiveQueue = new Queue<string>();//用于收消息的容器private byte[] receiveBytes = new byte[1024*1024];//返回收到的字节数private int receiveNum;//是否连接private bool isConnect=false;private void Awake(){instance = this; DontDestroyOnLoad(this.gameObject);}private void Update(){if(receiveQueue.Count>0){print(receiveQueue.Dequeue());}}//连接服务端public void Connect(string ip,int port){//如果是连接状态 直接返回if(isConnect){return;}if(socket==null){socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);}//连接服务端IPEndPoint iPPoint = new IPEndPoint(IPAddress.Parse(ip),port);try{socket.Connect(iPPoint);isConnect=true;//开启发送线程ThreadPool.QueueUserWorkItem(SendMsg);//开启接收线程ThreadPool.QueueUserWorkItem(ReceiveMsg);}catch(SocketException e){if(e.ErrorCode == 10061){print("服务器拒绝连接");}else{print("连接失败"+e.ErrorCode+e.Message);}}}//发送消息public void Send(string info){sendMsgQueue.Enqueue(info);}private void SendMsg(object obj){while(isConnect){if(sendMsgQueue.Count>0){socket.Send(Encoding.UTF8.GetBytes(sendMsgQueue.Dequeue()));}}}//接收消息public void ReceiveMsg(object obj){while(isConnect){if(socket.Available >0){receiveNum = socket.Receive(receiveBytes);//收到消息 解析消息为字符串 并放入公共容器receiveQueue.Enqueue(Encoding.UTF8.GetString(receiveBytes,0,receiveNum));}}}//关闭连接public void Close(){if(socket!=null){socket.Shutdown(SocketShutdown.Both);socket.Close();isConnect = false;}}private void OnDestroy(){Close();}
}

启动类

将脚本挂载到场景里,绑定按钮和文本输入框。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class Main : MonoBehaviour
{public Button btn;public InputField input;void Start(){if(NetMgr.Instance==null){GameObject obj = new GameObject("Net");obj.AddComponent<NetMgr>();}NetMgr.Instance.Connect("127.0.0.1",8080);btn.onClick.AddListener(()=>{if(input.text!=""){NetMgr.Instance.Send(input.text);}});}
}

最终实现

 先启动服务端,再启动客户端,两端会不停的接收对方发送的消息。


http://www.mrgr.cn/news/67378.html

相关文章:

  • apt镜像源制作-ubuntu22.04
  • hf_transformers
  • 你使用过哪些MySQL中复杂且使用不频繁的函数?
  • 第二章:C语言基础(四)
  • 计算机网络八股文个人总结
  • 【Linux】Linux安全与密钥登录指南
  • Redis集群模式之Redis Sentinel vs Redis Cluster
  • 基于特征工程的勒索软件检测方法研究项目申请
  • AI - 使用LangChain请求LLM结构化生成内容
  • 科研绘图系列:python语言连线图(linechart)
  • 小鹏AI机器人XPENG Iron亮相 采用端到端AI鹰眼视觉系统
  • 【系统集成项目管理工程师教程】第12章 执行过程组
  • Java | Leetcode Java题解之第542题01矩阵
  • 构建 Tencent SGX 机密计算环境
  • 十二:java web(4)-- Spring核心基础
  • GPU释放威力:在Gymnasium环境中使用稳定基线3在AMD GPU上训练强化学习代理
  • 科比投篮预测的数据处理与分析
  • 网络初始:TCP/IP 五层协议模型 网络通信基本流程
  • 解释Python的排序方法sort和sorted的区别。
  • 详解Rust标准库:BTreeSet
  • 数字信号处理Python示例(7)生成复数指数函数
  • Python | Leetcode Python题解之第543题二叉树的直径
  • 提高区块链网络TPS七步曲
  • 【知识科普】使用 OpenSSL为特定域名生成自签名证书
  • Python 详细实现无损压缩之 DEFLATE 算法
  • VS2013安装报错“windows程序兼容性模式已打开,请将其关闭 ”解决方案