今天跟着博主 科技7C100 做一个简单的 chatroom.
在 Unity 前端页面,设置了 Scroll View 来显示聊天内容,一个 InputField 来输入信息,还有两个按钮用于链接和发送。
在设计脚本之前,我们需要了解网络编程的基础知识
以下内容摘自 http://www.cnblogs.com/skynet/
网络中进程之间如何通信
网络通信首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程 PID 来唯一标识一个进程,但是在网络中这是行不通的。其实 TCP/IP 协议族已经帮我们解决了这个问题,网络层的 “ip 地址” 可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组( ip 地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用 TCP/IP 协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和 UNIX System V 的 TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用 socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆 socket ”。
什么是Socket
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写 write/read –> 关闭 close”模式来操作。我的理解就是 Socket 就是该模式的一个实现,socket 即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写 IO、打开、关闭)
前端设计
在Connect()方法中,我们这样设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static void Connect () { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1" ), 10010 )); socket.BeginReceive(buffer, 0 , buffer.Length,0 , _ =>{}, socket); }
Recevie()是(同步阻塞方式), 注意使用同步方法时,需要使用线程来开始方法,不然服务器不发送任何信息的话,Unity界面会卡死
这里使用使用BeginReceive(异步)
我们继续看Send的内部实现
1 2 3 4 5 6 7 8 9 public static void Send (string data ) { socket.Send(System.Text.Encoding.UTF8.GetBytes(data)); }
这里写一下回调方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void ReceiveCallback (IAsyncResult ar ) { Socket socket = (Socket)ar.AsyncState; int length = socket.EndReceive(ar); GameManager.Instance.messageQueue.Enqueue(() => { UIManager.Instance.connectText.text += System.Text.Encoding.UTF8.GetString(buffer, 0 , length); }); socket.BeginReceive(buffer, 0 , buffer.Length, 0 , ReceiveCallback, socket); }
重要的知识点来了!
Unity里的UI组件或者是其他仅仅存活于Unity生命周期中的组件,只在Unity主线程内执行
由此引出了消息队列的妙用,请看GameManager.cs
1 2 3 4 5 6 7 8 9 public Queue<Action> messageQueue = new Queue<Action>(); private void Update () { if (messageQueue.Count>0 ) { messageQueue .Dequeue() .Invoke(); } }
到这里,我们的前端设计就基本完成了,来看看后端设计吧
后端设计
后端设计就是服务器端的设计,这里我们使用C#的Socket类来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System.Net.Sockets;using System.Net;public class ChattingServer { public static Socket socket; public static void Main (string [] args ) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Any, 10010 )); socket.Listen(0 ); socket.BeginAccept(AcceptCallback, socket); System.Console.WriteLine("Server is running..." ); System.Console.ReadLine(); } }
还有我们需要用到的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void AcceptCallback (System.IAsyncResult ar ) { Socket socket = ar.AsyncState as Socket; Socket client = socket.EndAccept(ar); System.Console.WriteLine("Client connected" ); ClientInfo clientInfo = new ClientInfo(client); clientList.Add(clientInfo); client.BeginReceive(clientInfo.readBuff, 0 , 1024 , 0 , ReceiveCallback, clientInfo); socket.BeginAccept(AcceptCallback, socket); } public static void ReceiveCallback (System.IAsyncResult ar ) { ClientInfo info = ar.AsyncState as ClientInfo; int count = info.socket.EndReceive(ar); foreach (ClientInfo c in clientList) { c.socket.Send(info.readBuff,0 ,count,0 ); } info.socket.BeginReceive(info.readBuff, 0 , 1024 , 0 , ReceiveCallback, info); }
这里建立了一个 ClientInfo 类用于管理数据和客户端 socket. 非常方便
1 2 3 4 5 6 7 8 9 public class ClientInfo { public Socket socket; public byte [] readBuff = new byte [1024 ]; public ClientInfo (Socket socket ) { this .socket = socket; } }
当然,聊天室不是一个人的网络备忘录,必然会出现多个客户端链接的情况,所以有必要new 一个 List 存储客户端信息。
1 public static List<ClientInfo> clientList = new List<ClientInfo>();
到这里,Jason网络聊天室的基本功能就实现了。
本文作于2023-08-12,首发于个人博客https://rdququ.top/