UDP实现一个简易的聊天室(多个主机连接终端) 已完善
效果展示
网络编程 --使用UDP协议的简单聊天室
由于TCP协议是一种面向连接的协议,有三次挥手通信确认机制,且数据包没有代销限制,TCP协议确保在通信双方都收到对方确认信息的前提下,才开始为通信的双方传输信息(数据包)!
这样数据包的传输可以得到安全保障!所以简单的理解为安全的!当然一旦,出现网络问题,或者一些其他意外现象造成通信双方的连接出现异常或断开连接,使用TCP是不能传输信息的!
UDP不是一种面向连接的协议,就是说,在使用UDP协议进行数据交互的时候,彼此可以不连接,通信的一端突然地向另外一端发送数据包,但是对方没有启动接收程序,那么对方当时是收不到数据包的,或者当你发送数据的过程中,突然出现意外断网,停电等等意外现象,都有可能造成对方收不到信息!也就说,你发送的信息对方没收到,同时你也不知道对方有没有收到!这不好!
加载场景
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Net.Sockets; using System.Text; using Common; using UnityEngine.UI; using System; using System.Net; using UIWidgetsSamples; using UnityEngine.SceneManagement; namespace NS { ///<summary> /// 程序入口 ///</summary> public class GameMain : MonoSingleton<GameMain> { private InputField IPInputField; private InputField IPInputField1; //点击按钮 ButtonClientEnter初始化客户端服务类 private void Start() { transform.FindChildByName("ButtonClientEnter"). GetComponent<Button>().onClick.AddListener(OnEnterChatScene); //获取进入按钮 transform.FindChildByName("ButtonServerEnter"). GetComponent<Button>().onClick.AddListener(OnEnterServerScene); // InputServerIPForClient IPInputField1 = transform.FindChildByName("" + "InputServerIPForClient").GetComponent<InputField>(); IPInputField = transform.FindChildByName("" + "InputServerIPForServer").GetComponent<InputField>(); //获取文本框 拿到IP } private void OnEnterServerScene() { UDPServerNetWorkService.Instance.Initialized(IPInputField.text); SceneManager.LoadScene("Server"); } //进去聊天室 private void OnEnterChatScene() { UDPClientNetWorkService.Instance.Initialized (IPInputField1.text); //获取IP 初始化客户都安 SceneManager.LoadScene("Chat"); //切换到聊天窗口 加载不删除 } //加载场景 } }
Date数据类
using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using System.Net.Sockets; using System.Text; using Common; using UnityEngine.UI; using System; using System.Net; using UIWidgetsSamples; using System.Runtime.Serialization.Formatters.Binary; namespace NS { ///<summary> ///聊天消息 ///</summary> [System.Serializable]//表示类可以被序列化 public class ChatMessage { //消息类型 0 1 2 枚举 public UDPNetWork Type { get; set; } //发送者名称 public string SenderName { get; set; } //消息内容 public string Content { get; set; } //如何把一个对象转字节数组??? //序列化:将对象状态存储到某种介质的过程(场景还原,网络交互) //存储介质:磁盘 内存 网络 //反序列化:将某种介质中的对象转换成C#对象 public byte [] ObjectToBytes() { //1.二进制写入器 往内存流去写 支持跨平台 //MemoryStream ms = new MemoryStream(); //BinaryWriter writer = new BinaryWriter(ms); //byte[] bys = Encoding.UTF8.GetBytes(SenderName); ////值类型 因为大小固定 所以直接Write ////引用类型 大小不固定 所以需要写入长度 //writer.Write(bys.Length); ////写入长度 //writer.Write(bys); //写入内容 //2.序列化 //使用Using 可以在退出代码块的时候 自动关闭非托管资源 using (MemoryStream ms = new MemoryStream()) { //内存流 BinaryFormatter fomatter = new BinaryFormatter(); //二进制格式化器 fomatter.Serialize(ms, this); //序列化 将当前对象序列化到内存流中 //byte[] result = ms.ToArray(); //从内存流中获取字节数组 return ms.ToArray(); } } public static ChatMessage BytesToObject(byte [] bytes) { using (MemoryStream ms = new MemoryStream(bytes)) { //内存流 BinaryFormatter fomatter = new BinaryFormatter(); //二进制格式化器 return fomatter.Deserialize(ms) as ChatMessage; //反序列化 将内存流中 中的字节反序列化 } //自动释放资源 } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Net; namespace NS { ///<summary> /// 事件参数类 ///</summary> public class MessageArrivedEventArgs { public ChatMessage Message { get; set; } public DateTime DateTime { get; set; } public IPEndPoint senderEP { get; set; } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace NS { ///<summary> /// 消息类型 ///</summary> public enum UDPNetWork { Online, //上线 Offline, //下线 General //普通 } }
NetWork Service(网络服务)
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using Common; using System.Net.Sockets; using System.Text; using UnityEngine.UI; using System.Net; using UIWidgetsSamples; using System.Threading; namespace NS { ///<summary> ///基于UDP协议 客户端网络服务类 ///</summary> public class UDPClientNetWorkService : MonoSingleton<UDPClientNetWorkService> { //事件参数类 传递信息 public event Action<MessageArrivedEventArgs> MessageArrivedEventHandler; //初始化socket对象 ,由开始界面的Game Main类调用 private Thread receiverThread; //线程 private UdpClient udpServer; public void Initialized(string serverIP) { //加载不删除对象 udpServer = new UdpClient(); //客户端发送消息再给终结点 serverEP = new IPEndPoint(IPAddress.Parse(serverIP), 8989); Debug.Log(serverEP.Address); //分配IP和端口 没有保存 udpServer.Connect(serverEP); //Connect方法建立默认远程主机使用中指定的值endPoint参数。 //一旦建立,无需在每次调用中指定远程主机Send方法。 receiverThread = new Thread(ReceiveMessage); receiverThread.Start(); //提示上线 SendChatMessage(new ChatMessage() { Type = UDPNetWork.Online }); } private IPEndPoint serverEP; //发送消息 send按钮 public void SendChatMessage(ChatMessage msg) { byte[] dgram = msg.ObjectToBytes(); //获取报文 udpServer.Send(dgram, dgram.Length); //发送需要端口和IP Debug.Log(msg.Type); } //接收消息 接收阻塞 线程 public void ReceiveMessage() { while (true)//不停接收数据 { //接收 IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0); byte[] date = udpServer.Receive(ref remote); ChatMessage msg = ChatMessage.BytesToObject(date); Debug.Log(msg.Type); //引发事件 if (MessageArrivedEventHandler != null) { //线程交叉助手类调用 因为访问UnityAPI ThreadCrossHelper.Instance.ExecuteOnMainThread(() => { var args = new MessageArrivedEventArgs() { Message = msg, DateTime = DateTime.Now }; //创建事件参数类 参数没有赋值??? MessageArrivedEventHandler(args); //调用方法不需要传参数吗??? }); } } } //关闭 public void OnApplicationQuit() { SendChatMessage(new ChatMessage() { Type = UDPNetWork.Offline }); receiverThread.Abort(); udpServer.Close(); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using Common; using System.Net.Sockets; using System.Text; using UnityEngine.UI; using System.Net; using UIWidgetsSamples; using System.Threading; using System; namespace NS { ///<summary> /// 基于UDP的服务端网络服务类 ///</summary> public class UDPServerNetWorkService : MonoSingleton<UDPServerNetWorkService> { //客户端列表 //初始化Socket //发送消息 //接收消息 //事件参数类 传递信息 public event Action<MessageArrivedEventArgs> MessageArrivedEventHandler; //初始化socket对象 ,由开始界面的Game Main类调用 private Thread receiverThread; //线程 private UdpClient udpServer; public void Initialized(string serverIP) { //加载不删除对象 //客户端发送消息再给终结点 serverEP = new IPEndPoint(IPAddress.Parse(serverIP), 8989); udpServer = new UdpClient(serverEP); receiverThread = new Thread(ReceiveMessage); receiverThread.Start(); //注册事件 收到消息的时候执行 MessageArrivedEventHandler += OnMessageArrived; } //收到消息的时候分发 private void OnMessageArrived(MessageArrivedEventArgs obj) { switch (obj.Message.Type) { case UDPNetWork.Online: //存列表 AddClient(obj.senderEP); break; case UDPNetWork.Offline: //移除列表 RemoveClient(obj.senderEP); break; case UDPNetWork.General: //转发 TransmitMessage(obj.Message); break; } } private IPEndPoint serverEP; //发送消息 send按钮 public void SendChatMessage(ChatMessage msg,IPEndPoint remote) { byte[] dgram = msg.ObjectToBytes(); //获取报文 udpServer.Send(dgram, dgram.Length, remote); //发送需要端口和IP } //接收消息 接收阻塞 线程 public void ReceiveMessage() { while (true) //不停接收数据 { //接收 IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0); byte[] date = udpServer.Receive(ref remote); ChatMessage msg = ChatMessage.BytesToObject(date); Debug.Log(msg.Type + "adsadad"); //引发事件 if (MessageArrivedEventHandler != null) { //线程交叉助手类调用 因为访问UnityAPI ThreadCrossHelper.Instance.ExecuteOnMainThread(() => { var args = new MessageArrivedEventArgs() { Message = msg, DateTime = DateTime.Now, senderEP = remote }; Debug.Log(msg + "1"); //创建事件参数类 参数没有赋值??? MessageArrivedEventHandler(args); //调用方法不需要传参数吗??? Debug.Log(msg + "2"); }); } } } //关闭 public void OnApplicationQuit() { receiverThread.Abort(); udpServer.Close(); } private List<IPEndPoint> allListEP = new List<IPEndPoint>(); //添加客户端 public void AddClient(IPEndPoint clientEP) { allListEP.Add(clientEP); } //移除客户端 public void RemoveClient(IPEndPoint clientEP) { allListEP.Remove(clientEP); } //转发消息 private void TransmitMessage(ChatMessage msg) { foreach (var item in allListEP) { SendChatMessage(msg, item); //根据不同端口发送消息 } } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Net.Sockets; using System.Text; using Common; using UnityEngine.UI; using System; using System.Net; using UIWidgetsSamples; namespace NS { ///<summary> ///客户端显示信息界面类 ///</summary> public class ChatClient : MonoBehaviour { private InputField messageInput; private InputField nameInput; private void Start() { messageInput = transform.FindChildByName("MessageInput").GetComponent<InputField>(); nameInput = transform.FindChildByName("NameInput").GetComponent<InputField>(); transform.FindChildByName("Send").GetComponent<Button>(). onClick.AddListener(OnSendButtonClick); //获取各种组建 chatview = transform.FindChildByName("ChatView").GetComponent<ChatView>(); UDPClientNetWorkService.Instance.MessageArrivedEventHandler += OnMessageArrived; //注册事件 } //点击调用发送消息的方法 private void OnSendButtonClick() { ChatMessage msg = new ChatMessage() { Type = UDPNetWork.General, Content = messageInput.text, SenderName = nameInput.text }; UDPClientNetWorkService.Instance. SendChatMessage(msg); } private ChatView chatview; private void OnMessageArrived(MessageArrivedEventArgs args) { //注册方法 //显示消息 ChatLine line = new ChatLine() { //事件参数对象赋值???? Time = args.DateTime, Message = args.Message.Content, Type = ChatLineType.User, UserName=args.Message.SenderName }; chatview.DataSource.Add(line); //滚动 chatview.ScrollRect.verticalNormalizedPosition = 0; } } }
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Common; namespace NS { ///<summary> ///服务端显示面板 ///</summary> public class ChatServer : MonoBehaviour { private void Start() { text = transform.FindChildByName("Text").GetComponent<Text>(); UDPServerNetWorkService.Instance.MessageArrivedEventHandler += OnMessageArrived; } private Text text; //当服务端收到消息 显示在文本中 private void OnMessageArrived(MessageArrivedEventArgs obj) { Debug.Log(obj.Message.Type); text.text = string.Format("{0}---{1}", obj.Message.Type, obj.Message.Content); } } }
工程包链接: https://github.com/AnsssAS/Assect