在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
C#模拟服务器,Unity模拟客户端效果图:思路:客户端连接服务器发送上线消息,服务器记录所有user,然后广播给已经在线的玩家,同时发送所有玩家的名字给上线的用户,实现同步好友列表,这里用的是PopList制作的用户列表,更新显示上线消息的,这用的是TextList(NGUI制作的UI),在客户端异步发送环节,并没有在messagecenter中写派发发送消息的方法,因为懒呗,只写了一个派发收到消息的函数,通过收到消息中的协议的协议号处理消息,这里我用的协议是协议号(ushort)+消息的长度(ushort)+消息的内容(byte[]),也就是MessagePackage的一个实例,发送的时候把三个字段转成byte数组进行拼接发送,在MyTools工具类定义的就是操作数据的方法,那么服务器的流程也是和客户端一样的结构,不一样的就是具体的消息操作类逻辑主要类:客户端:
UITalkController:这个是挂在类,用来显示消息,和交互服务器连接
Client:封装套接字客户端Socket,用来与服务器通讯,包含,连接,断开,发送,接受等方法
IMessage:消息处理类的接口
MessageCenter:消息中心
OnLineState:上线信息的具体操作类
TalkState:聊天消息的具体操作类
TalkMessage:消息的包(protobuf工具生成,此类主要是承载通讯的消息,通过protobuf进行序列化与反序列化)
MessagePackage:发送和接受的包
MyTools:工具类,主要方法,封包,分包,序列化,反序列化
服务器:
Program:主函数,用于开启服务器Server:封装服务器套接字,用于通讯
MyTools:工具类,主要方法,封包,分包,序列化,反序列化
IMessage:消息处理类的接口
MessageCenter:消息中心
OnLineState:上线信息的具体操作类
TalkState:聊天消息的具体操作类
TalkMessage:消息的包(protobuf工具生成,此类主要是承载通讯的消息,通过protobuf进行序列化与反序列化)
在编写的过程发现IMessage,MessageCenter,MyTools,TalkMessage,MessagePackage是通用的,原理基本都是一样的,下面贴出代码:客户端:挂载类:using System.Collections.Generic;
using UnityEngine; using day1_7MessageData; using System.Text; using Day1_7EVEServer; public class UITalkController : MonoBehaviour { //用户列表 public UIPopupList userPopList; //消息输入框 public UIInput ipInput; public UIInput portInput; public UIInput messageInput; public UIInput nickNameInput; //消息显示文本 public UITextList textList; public UIButton sendBtn; public UIButton clearBtn; public UIButton connectBtn; public UIButton disConnectBtn; public UIButton reConnectBtn; // Use this for initialization void Start () { userPopList.AddItem("所有人"); //按钮注册事件,两种方式 sendBtn.onClick.Add(new EventDelegate(SendBtnHandler)); clearBtn.onClick.Add(new EventDelegate(ClearBtnHandler)); connectBtn.onClick.Add(new EventDelegate(ConnectBtnHandler)); UIEventListener.Get(disConnectBtn.gameObject).onClick += DisConnectBtnHandler; UIEventListener.Get(reConnectBtn.gameObject).onClick += ReConnectBtnHandler; } /// <summary> /// 上线用户添加方法 /// </summary> private void UserFun() { List<string> list = userPopList.items; for (int i = 0; i < MessageCenter.Instance.userList.Count; i++) { if (!list.Contains(MessageCenter.Instance.userList[i])) { userPopList.AddItem(MessageCenter.Instance.userList[i]); } } } /// 重新连接服务器 private void ReConnectBtnHandler(GameObject go) { Client.Instance.ReConnectToServer(ipInput.value,int.Parse(portInput.value)); } /// <summary> /// 断开连接 /// </summary> /// <param name="go"></param> private void DisConnectBtnHandler(GameObject go) { Client.Instance.DisConnectToServer(); } /// <summary> /// 连接服务器按钮 /// </summary> private void ConnectBtnHandler() { Client.Instance.StartClient(); bool isOnline=Client.Instance.ConnectToServer(ipInput.value, int.Parse(portInput.value)); if (isOnline) { SendOnlineMessage(); } } /// <summary> /// 清屏按钮 /// </summary> private void ClearBtnHandler() { textList.Clear(); } /// <summary> /// 发送消息按钮 /// </summary> private void SendBtnHandler() { MessagePackage mPackage = new MessagePackage(); mPackage.packageID = 1002; TalkMessage talkMessage = new TalkMessage(); talkMessage.name = nickNameInput.value; talkMessage.data = messageInput.value; mPackage.protobufBytes = MyTools.Instance.ProtoBufSerialized<TalkMessage>(talkMessage); Client.Instance.SendToServer(mPackage); messageInput.value = ""; } /// <summary> /// 发送上线消息 /// </summary> private void SendOnlineMessage() { MessagePackage mPackage = new MessagePackage(); mPackage.packageID = 1001; TalkMessage talkMessage = new TalkMessage(); talkMessage.name = nickNameInput.value; talkMessage.data = "上线了"; mPackage.protobufBytes = MyTools.Instance.ProtoBufSerialized<TalkMessage>(talkMessage); Client.Instance.SendToServer(mPackage); } /// <summary> /// 显示消息 /// </summary> /// <param name="talkMessage"></param> private void UpdateMessageLabel(TalkMessage talkMessage) { StringBuilder sb = new StringBuilder(); sb.Append(talkMessage.name); sb.Append("说:"); sb.Append(talkMessage.data); textList.Add(sb.ToString()); } void Update () { //处理消息队列 if (MessageCenter.Instance.messageQueue.Count>0) { TalkMessage talkMessage= MessageCenter.Instance.messageQueue.Dequeue(); UpdateMessageLabel(talkMessage); } //如果有上线的用户 if (MessageCenter.Instance.isAddUser) { UserFun(); PrintToConsole.Instance.PrintTo("当前的用户的数量:" + MessageCenter.Instance.userList.Count); MessageCenter.Instance.isAddUser = false; } } } Client类using System.Net;
using System.Net.Sockets; using System; using Day1_7EVEServer; /// 客户端 public class Client : SingleTon<Client> { public Socket clientSocket; public IPEndPoint point; public IPAddress ip; public int port; byte[] receiveBytes = new byte[1024]; public Client() { } /// 开启客户端 public void StartClient() { } /// 连接服务器 public bool ConnectToServer(string ip,int port) { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); point = new IPEndPoint(IPAddress.Parse(ip), port); clientSocket.Connect(point); if (!clientSocket.Connected) { PrintToConsole.Instance.PrintTo("连接服务器失败"); } //开启异步接受消息 ReceiveFromServer(); return clientSocket.Connected; } /// 断开连接 public void DisConnectToServer() { //断开连接且允许重新连接 if (clientSocket!=null) { clientSocket.Close(); } } /// 重新连接 public bool ReConnectToServer(string ip, int port) { if (clientSocket != null) { ConnectToServer( ip,port); } return clientSocket.Connected; } /// 发送消息 public void SendToServer(MessagePackage mPackage) { byte[] data = MyTools.Instance.BuilderPackage(mPackage); if (clientSocket != null && clientSocket.Connected) { clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, AsyncSend, null); } } /// 异步发送回调 private void AsyncSend(IAsyncResult ar) { if (clientSocket != null) { int count=clientSocket.EndSend(ar); } } /// 接受消息 public void ReceiveFromServer() { if (clientSocket!=null) { clientSocket.BeginReceive(receiveBytes,0, receiveBytes.Length,SocketFlags.None,ReceiveCallBack,null); } } /// 异步接受消息回调 private void ReceiveCallBack(IAsyncResult ar) { if (clientSocket!=null) { int count=clientSocket.EndReceive(ar); if (count <= 0) { //断开连接 DisConnectToServer(); } else { // 拆包 byte[] data = new byte[count]; Buffer.BlockCopy(receiveBytes,0,data,0,count); // messageQueue.Enqueue(talkmessage); MessageCenter.Instance.DistributeMessage(clientSocket, data); } } ReceiveFromServer(); } }//End... MessageCenter:using System;
using System.Collections.Generic; using System.Net.Sockets; using day1_7MessageData; namespace Day1_7EVEServer { public class MessageCenter { private static MessageCenter instance; ///// 套接字对应的客户端名字 //public Dictionary<Socket, string> socketDic = new Dictionary<Socket, string>(); /// 用户列表 public List<string> userList = new List<string>(); public bool isAddUser = false; public TalkMessage talk; /// 消息类型对应消息处理类 public Dictionary<ushort, IMessage> messageTypeDic = new Dictionary<ushort, IMessage>(); //接受的消息队列 public Queue<TalkMessage> messageQueue = new Queue<TalkMessage>(); public static MessageCenter Instance { get { if (instance==null) { instance = new MessageCenter(); } return instance; } } public MessageCenter() { Regist(); } /// <summary> /// 注册消息处理类 /// </summary> public void Regist() { OnLineState online = new OnLineState(); messageTypeDic.Add(online.GetID, online); TalkState ts = new TalkState(); messageTypeDic.Add(ts.GetID, ts); } //派发消息 public void DistributeMessage(Socket clientSocket,byte[] packageMessage) { //根据包的包头,派发消息 MessagePackage mPackage=MyTools.Instance.SplitPackage(packageMessage); if (messageTypeDic.ContainsKey(mPackage.packageID)) { messageTypeDic[mPackage.packageID].DisposeMessage(clientSocket, mPackage); } else { Console.WriteLine("字典中不存在的消息类型"); } } } } 通用的消息接口using System;
using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; namespace Day1_7EVEServer { public interface IMessage { /// <summary> /// 返回消息的编号 /// </summary> ushort GetID { get; } /// <summary> /// 处理消息 /// </summary> void DisposeMessage(Socket clientSocket, MessagePackage mPackage); } } 通用的工具类,MyTools,TalkMessage(protobuf工具生成),MessagePackageusing ProtoBuf;
using System.IO; using System; /// <summary> /// 我的工具类 /// </summary> public class MyTools : SingleTon<MyTools> { //封包 public byte[] BuilderPackage(MessagePackage mPackage) { byte[] idBytes = BitConverter.GetBytes(mPackage.packageID); byte[] protobufBytes = mPackage.protobufBytes; byte[] lengthBytes = BitConverter.GetBytes((ushort)(4 + protobufBytes.Length)); byte[] data = new byte[idBytes.Length + protobufBytes.Length + lengthBytes.Length]; Buffer.BlockCopy(idBytes, 0, data, 0, idBytes.Length); Buffer.BlockCopy(lengthBytes, 0, data, idBytes.Length, lengthBytes.Length); Buffer.BlockCopy(protobufBytes, 0, data, 4, protobufBytes.Length); return data; } //拆包 public MessagePackage SplitPackage(byte[] data) { MessagePackage mPackage = new MessagePackage(); byte[] idBytes = new byte[2]; byte[] lengthBytes = new byte[2]; byte[] protobufBytes = new byte[data.Length - 4]; Buffer.BlockCopy(data, 0, idBytes, 0, idBytes.Length); Buffer.BlockCopy(data, 2, lengthBytes, 0, 2); Buffer.BlockCopy(data, 4, protobufBytes, 0, protobufBytes.Length); mPackage.packageID = BitConverter.ToUInt16(idBytes, 0); mPackage.packageLength = BitConverter.ToUInt16(lengthBytes, 0); mPackage.protobufBytes = protobufBytes; return mPackage; } //序列化 public byte[] ProtoBufSerialized<T>(T obj) { byte[] data = null; if (obj != null) { using (MemoryStream ms = new MemoryStream()) { Serializer.Serialize<T>(ms, obj); data = new byte[ms.Length]; ms.Position = 0; ms.Read(data, 0, data.Length); ms.Dispose(); } } return data; } //反序列化 public T ProtoBufDesSerialized<T>(byte[] data) { for (int i = 0; i < data.Length; i++) { Console.WriteLine(data[i]); } T obj = default(T); if (data.Length > 0) { using (MemoryStream ms = new MemoryStream()) { ms.Write(data,0,data.Length); ms.Position = 0; //可能会出错 obj = Serializer.Deserialize<T>(ms); } } return obj; } } //------------------------------------------------------------------------------
// <auto-generated> // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ // Generated from: TalkMessage.proto namespace day1_7MessageData { [global::System.Serializable, global::ProtoBuf.ProtoContract([email protected]"TalkMessage")] public partial class TalkMessage : global::ProtoBuf.IExtensible { public TalkMessage() {} private string _name = ""; [global::ProtoBuf.ProtoMember(1, IsRequired = false, [email protected]"name", DataFormat = global::ProtoBuf.DataFormat.Default)] [global::System.ComponentModel.DefaultValue("")] public string name { get { return _name; } set { _name = value; } } private string _data = ""; [global::ProtoBuf.ProtoMember(2, IsRequired = false, [email protected]"data", DataFormat = global::ProtoBuf.DataFormat.Default)] [global::System.ComponentModel.DefaultValue("")] public string data { get { return _data; } set { _data = value; } } private global::ProtoBuf.IExtension extensionObject; global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } } } using System.Collections;
using System.Collections.Generic; using UnityEngine; /// <summary> /// 要发送的数据包 /// </summary> public class MessagePackage { /// <summary> /// 协议号 /// </summary> public ushort packageID; /// <summary> /// 包的长度 /// </summary> public ushort packageLength; /// <summary> /// 数据类的字节数组 /// </summary> public byte[] protobufBytes; } 具体的消息处理类,OnLineState,TalkStateusing System.Net.Sockets;
using day1_7MessageData; namespace Day1_7EVEServer { /// <summary> /// 上线状态 /// </summary> public class OnLineState : IMessage { public ushort GetID { get { return 1001; } } public OnLineState() { } /// <summary> /// 收到所有的名字给客户端,接收到UserList中 /// </summary> /// <param name="clientSocket"></param> /// <param name="mPackage"></param> public void DisposeMessage(Socket clientSocket, MessagePackage mPackage) { TalkMessage users =MyTools.Instance.ProtoBufDesSerialized<TalkMessage>(mPackage.protobufBytes); MessageCenter.Instance.messageQueue.Enqueue(users); PrintToConsole.Instance.PrintTo("我的名字" + users.data); PrintToConsole.Instance.PrintTo("我的名字" + users.name); MessageCenter.Instance.talk = users; if (!MessageCenter.Instance.userList.Contains(users.name)) { MessageCenter.Instance.userList.Add(users.name); MessageCenter.Instance.isAddUser = true; } } } } using day1_7MessageData; using System.Net.Sockets; namespace Day1_7EVEServer { public class TalkState : IMessage { public ushort GetID { get { return 1002; } } public void DisposeMessage(Socket clientSocket, MessagePackage mPackage) { TalkMessage users = MyTools.Instance.ProtoBufDesSerialized<TalkMessage>(mPackage.protobufBytes); MessageCenter.Instance.messageQueue.Enqueue(users); } } } 服务器:Sererverusing System;
using System.Net; using System.Net.Sockets; /// 服务器 namespace Day1_7EVEServer { public class Server { private static Server instance; Socket serverSocket; public IPEndPoint point; public string ip="169.254.152.25"; public int port=52121; byte[] receiveBytes = new byte[1024]; /// 单例 public static Server Instance { get { if (instance == null) { instance = new Server(); } return instance; } } //构造函数初始化成员变量 public Server() { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); point = new IPEndPoint(IPAddress.Parse(ip), port); } /// 开启服务器 public void StartServer() { serverSocket.Bind(point); serverSocket.Listen(20); StartAccept(); } /// 开启服务器接受连接 public void StartAccept() { serverSocket.BeginAccept(AcceptCallBack,null); } /// 异步接受连接回调 private void AcceptCallBack(IAsyncResult ar) { Socket clientSocket=serverSocket.EndAccept(ar); Console.WriteLine("有客户端连接服务器"); //开启异步接受 clientSocket.BeginReceive(receiveBytes,0,receiveBytes.Length,SocketFlags.None,ReceiveCallBack,clientSocket); //递归调用 StartAccept(); } /// 异步接受消息回调 private void ReceiveCallBack(IAsyncResult ar) { Socket clientSocket = null; try { clientSocket = ar.AsyncState as Socket; int count = clientSocket.EndReceive(ar); byte[] data = new byte[count]; Buffer.BlockCopy(receiveBytes, 0, data, 0, count); if (count > 0) { //收到的消息是正确的,则派发消息 MessageCenter.Instance.DistributeMessage(clientSocket,data); //递归接受 clientSocket.BeginReceive(receiveBytes, 0, receiveBytes.Length, SocketFlags.None, ReceiveCallBack, clientSocket); } else { //收到的消息是不正确的说明,客户端已经掉线 MessageCenter.Instance.socketDic.Remove(clientSocket); clientSocket.Close(); } } catch (Exception e) { //用户非正常的掉线 Console.WriteLine(e); clientSocket.Close(); } } } } 主函数using System;
namespace Day1_7EVEServer { class Program { static void Main(string[] args) { //开启服务器 Server.Instance.StartServer(); //暂停控制台 Console.ReadKey(); } } } 总结:这个Demo主要是设计到Tcp的通讯,一个简单的消息派发,并没有用委托去做,我觉得主要的难点就是在具体信息操作类的逻辑的编写,对于通讯协议也定义的比较简单,没有做粘包的处理,在这里只是多人聊天,如果要进行一对一的聊天,需要写一个数据类,其中包含用户的名字,用户的套接字,然后服务器进行用户存储的时候存储数据类的实例即可,发送的时候也是要一起发送,客户端接受的时候也要一起存起来,实现点击不同的人进行一对一的聊天哦。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论