Tcp多人聊天C#,Unity实现

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工具生成),MessagePackage

using 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(Name=@"TalkMessage")]
  public partial class TalkMessage : global::ProtoBuf.IExtensible
  {
    public TalkMessage() {}  
    private string _name = "";
    [global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"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, Name=@"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,TalkState

using 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);
        }
    }
}

服务器:

Sererver

using 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的通讯,一个简单的消息派发,并没有用委托去做,我觉得主要的难点就是在具体信息操作类的逻辑的编写,对于通讯协议也定义的比较简单,没有做粘包的处理,在这里只是多人聊天,如果要进行一对一的聊天,需要写一个数据类,其中包含用户的名字,用户的套接字,然后服务器进行用户存储的时候存储数据类的实例即可,发送的时候也是要一起发送,客户端接受的时候也要一起存起来,实现点击不同的人进行一对一的聊天哦。


猜你喜欢

转载自blog.csdn.net/er_namespace/article/details/79046134
今日推荐