先看看效果图
以上分别是两个客户端相互通讯的情况的。
源码:https://github.com/zymix/Unity_ChatSystem
C#本身对Socket拥有着高度的封装,所以搭建这样的一个多人聊天系统是非常容易的事情。这里先提醒几点:
1.关于Unity内部编码问题,Unity在debug阶段时其编码模式是跟操作系统一致的,但是当其发布以后,由于Unity的跨平台性使其编码改变成UTF8的形式,所以无论哪个阶段,都应该注意客户端与服务器段的编码模式一致。
2.关于Scroll View的问题,想滚动条动态的改变滚动长度需要,动态修改Scroll Rect组件的Content对象的RectTransform的deltaSize
剩下的,看注释,算是比较清晰了
客户端代码
- using UnityEngine;
- using System.Net.Sockets;
- using System;
- using System.Collections.Generic;
- using UnityEngine.UI;
- using System.Collections;
- public class ChatClient : MonoBehaviour
- {
- public GameObject content;
- public InputField input; //用于发送输入框的信息
- public InputField usernameInput;//客户端用户名
- public GameObject messageItem;//预制件的宽高
- public Button connectButton; //连接按钮
- TcpClient client;//客户端
- NetworkStream nstream;//网络数据流
- string ip = "192.168.10.5";//客户端IP
- int port = 10086;//客户端端口
- byte[] data;//客户端收发数据
- bool isConnect = false;
- List<GameObject> messageList;//存储接受的消息列表
- public int messagesMaxSize = 20;//存储接受的消息列表最大长度
- bool isNewMessage = false;//消息更新的标志位
- string newMessage;//接受到的最新信息
- //预制件的宽高
- float height = 0;
- float width = 0;
- void Start()
- {
- messageList = new List<GameObject>();
- messageList.Capacity = messagesMaxSize;
- //获取预设件的宽高
- width = messageItem.GetComponent<RectTransform>().rect.width;
- height = messageItem.GetComponent<RectTransform>().rect.height;
- //初始化组件
- if (content == null)
- content = transform.FindChild("Chat View/Viewport/Content").gameObject;
- if (input == null)
- input = transform.FindChild("InputField").GetComponent<InputField>();
- if (usernameInput == null)
- usernameInput = transform.FindChild("UsernameInput").GetComponent<InputField>();
- if (connectButton == null)
- connectButton = transform.FindChild("ConnectButton").GetComponent<Button>();
- //给连接按钮添加OnConnect事件,控制链接开启和关闭
- connectButton.onClick.AddListener(delegate { OnConnect(); });
- }
- void Update()
- {
- if (isNewMessage)
- {//若存在新的消息,则添加消息更新界面
- AddMessage(newMessage);
- isNewMessage = false;
- }
- }
- public void OnDestroy()
- {
- Disconnect();
- }
- //连接按钮的点击事件调用Onconnect连接函数
- public void OnConnect()
- {
- isConnect = !isConnect;
- if (isConnect)
- {
- connectButton.transform.FindChild("Text").GetComponent<Text>().text = "断开连接";
- Connect();
- }
- else
- {
- connectButton.transform.FindChild("Text").GetComponent<Text>().text = "连接服务器";
- Disconnect();
- }
- }
- //中断网络连接
- void Disconnect()
- {
- //关闭流和客户端
- if(nstream !=null&&(nstream.CanRead|| nstream.CanWrite))
- nstream.Close();
- if (nstream != null && client.Connected)
- client.Close();
- }
- //网络连接
- void Connect()
- {
- Info.debugStr = "";
- if ("" == usernameInput.text)
- {
- Info.debugStr = DateTime.Now + "->用户名不能为空";
- return;
- }
- try
- {
- //创建TCP连接
- client = new TcpClient(ip, port);
- nstream = client.GetStream();
- //先发送用户名
- SendMessage(usernameInput.text);
- //初始化收发数据
- data = new byte[client.ReceiveBufferSize];
- //开启异步从服务器获取消息,获取到的数据流存入data,回调ReceiveMessage方法
- nstream.BeginRead(data, 0, data.Length, AsyncReceive, null);
- }
- catch (SocketException socketEx)
- {//输出错误码
- Info.debugStr = DateTime.Now + "->" + socketEx.ErrorCode + ": " + socketEx.Message;
- //关闭流和客户端
- client.Close();
- }
- catch (Exception e)
- { //显示错误信息
- Info.debugStr = DateTime.Now + "->" + e.Message;
- //断开连接
- Disconnect();
- }
- }
- //发送消息到服务器
- public void Send()
- {
- Info.debugStr = "";
- if ("" == input.text)
- {
- Info.debugStr = DateTime.Now + "->消息不能为空";
- return;
- }
- //发送消息到服务器
- SendMessage(input.text);
- //清空输入框
- input.text = "";
- }
- new public void SendMessage(string message)
- {
- try
- { //把输入框的消息写入数据流,发送服务器
- //NetworkStream stream = client.GetStream();
- byte[] bytes = System.Text.Encoding.UTF8.GetBytes(message);
- nstream.Write(bytes, 0, bytes.Length);
- }
- catch (Exception e)
- {
- //显示错误信息
- Info.debugStr = DateTime.Now + "->" + e.Message;
- //断开连接
- Disconnect();
- }
- }
- //异步处理从服务器获得的消息
- void AsyncReceive(IAsyncResult ar)
- {
- Info.debugStr = "";
- try
- {
- int bytesRead = client.GetStream().EndRead(ar);
- if (bytesRead < 1)
- { //接收不到任何消息
- return;
- }
- else
- {
- //把接收到的消息编码为UTF8,跟Unity发布后编码方式一致
- newMessage = System.Text.Encoding.UTF8.GetString(data, 0, bytesRead);
- //设置标志位,在UI上添加一条消息
- isNewMessage = true;
- }
- //再次开启异步从服务器获取消息,形成循环等待服务器消息
- nstream.BeginRead(data, 0, client.ReceiveBufferSize, AsyncReceive, null);
- }
- catch (Exception e)
- {
- Info.debugStr = DateTime.Now + "->" + e.Message;
- //断开连接
- Disconnect();
- }
- }
- //在UI上添加一条消息
- private void AddMessage(string uiMessage)
- {
- //把接收到的消息存入消息列表中
- GameObject item = Instantiate<GameObject>(messageItem);
- item.GetComponent<Text>().text = uiMessage;
- //超出列表上限时,再删除第一条
- if (messageList.Count > messageList.Capacity)
- messageList.RemoveAt(0);
- messageList.Add(item);
- //刷新界面
- ShowMessages();
- }
- private void ShowMessages()
- {
- int i = 0;
- foreach (GameObject m in messageList)
- {
- //设置为显示内容面板的子对象,便于位置的设置位置
- m.transform.SetParent(content.transform, false);
- //按序设置位置
- RectTransform rf = m.GetComponent<RectTransform>();
- rf.sizeDelta = new Vector2(width, height);
- rf.localPosition = new Vector2(0, -i * height);
- ++i;
- }
- content.GetComponent<RectTransform>().sizeDelta = new Vector2(width, i * height);
- }
- }
简单的负责debug的提示信息类
- using UnityEngine;
- using System.Collections;
- public class Info : MonoBehaviour {
- public static string debugStr = "";
- void OnGUI() {
- GUILayout.Label(debugStr);
- }
- }
服务器端的代码
主程序
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading.Tasks;
- namespace ChatSystem
- {
- class Program
- {
- static void Main(string[] args)
- {
- //设置监听的端口号
- Console.WriteLine("请输入服务器需要监听的端口: ");
- string input = Console.ReadLine();
- int port = int.Parse(input);
- //调用方法启动服务器
- Server(port);
- }
- static void Server(int port)
- {
- //初始化服务器ip
- //IPAddress localAddress = IPAddress.Parse("127.0.0.1");
- //设置监听
- TcpListener listener = new TcpListener(IPAddress.Any, port);
- listener.Start();
- //提示信息
- Console.WriteLine("{0:HH:mm:ss}->监听端口{1}....", DateTime.Now, port);
- //循环等待客户端的连接请求
- while(true)
- {
- ChatServerHandle usr = new ChatServerHandle(listener.AcceptTcpClient());
- Console.WriteLine(usr.ip + "加入聊天室");
- }
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Sockets;
- using System.Net;
- using System.Text;
- using System.Threading.Tasks;
- using System.Collections;
- namespace ChatSystem
- {
- class ChatServerHandle
- {
- public static Hashtable Clients = new Hashtable();//客户端连接记录表
- public TcpClient client;//客户端
- public string username;//客户端用户名
- public string ip;//客户端IP
- public int port;//客户端端口
- public byte[] data;//客户端收发数据
- bool firstConnet = true;
- public ChatServerHandle(TcpClient client)
- {
- this.client = client;
- //保存客户端IP和端口
- IPEndPoint ipEndPoint = client.Client.RemoteEndPoint as IPEndPoint;
- this.ip = ipEndPoint.Address.ToString();
- this.port = ipEndPoint.Port;
- //初始化收发数据
- this.data = new byte[client.ReceiveBufferSize];
- //开启异步从客户端获取消息,获取到的数据流存入data,回调ReceiveMessage方法
- client.GetStream().BeginRead(data, 0, data.Length, ReceiveMessage, null);
- }
- //向客户端发送消息
- public void SendMessage(string message)
- {
- try
- {
- NetworkStream stream;
- lock(client.GetStream())
- {
- stream = client.GetStream();
- }
- byte[] bytes = Encoding.UTF8.GetBytes(message); //注意Unity发布后程序已UTF8编码
- stream.Write(bytes, 0, bytes.Length);
- //stream.Flush();
- }
- catch (Exception e)
- {
- Console.WriteLine("{0:HH:mm:ss}->[SendMessage]Error: {1}", DateTime.Now, e.Message);
- }
- }
- //从客户端接受到的数据,再广播给所有客户端
- public void ReceiveMessage(IAsyncResult ar)
- {
- try
- {
- int bytesRead;
- lock(client.GetStream())
- {
- bytesRead = client.GetStream().EndRead(ar);
- }
- if (bytesRead < 1)
- {//接收不到任何数据,则删除在客户端连接记录表
- Clients.Remove(this.ip);
- //向所有客户端广播该用户的下线信息
- Broadcast("<color=#00ffffff>"+this.username + "</color> 于" + DateTime.Now + " 已下线....");
- return;
- }
- else
- {
- string recMessage = Encoding.UTF8.GetString(data, 0, bytesRead); //注意Unity发布后程序已UTF8编码
- if (firstConnet)
- { //检查是不是同一台电脑重复连接
- if(Clients.ContainsKey(this.ip))
- return;
- //第一次连接,将客户端信息记录入客户端连接记录表中
- Clients.Add(this.ip,this);
- //获取发送的用户名信息
- this.username = recMessage;
- //向所有客户端广播该用户的上线信息
- Broadcast("<color=#00ffffff>" + this.username + "</color> 于" + DateTime.Now+ " 已上线....");
- //不在是第一次连接
- firstConnet = false;
- }
- else
- {
- //向所有客户端广播该用户发送的
- Broadcast(DateTime.Now + " ->【"+ this.username+"】" + recMessage);
- }
- //Console.WriteLine(recMessage);
- }
- lock(client.GetStream())
- {
- //再次开启异步从服务器获取消息,形成循环
- client.GetStream().BeginRead(data, 0, client.ReceiveBufferSize, ReceiveMessage, null);
- }
- }
- catch(Exception e)
- {
- //删除连接记录
- Clients.Remove(this.ip);
- //向所有客户端广播该用户的下线信息
- Broadcast("<color=#00ffffff>" + this.username + "</color> 于" + DateTime.Now + " 已下线....");
- Console.WriteLine("{0:HH:mm:ss}->[ReceiveMessage]Error: {1}", DateTime.Now, e.Message);
- }
- }
- //向所有连接到的客户端广播
- public void Broadcast(string message)
- {
- Console.WriteLine(message);
- foreach (DictionaryEntry item in Clients)
- {
- ChatServerHandle c = item.Value as ChatServerHandle;
- c.SendMessage(message);
- }
- }
- }
- }