Unity, C# ,TCP实现的一个简单的对话同步功能

测试是在本机测试,客户端服务器都在一个机器上,IP就用127.0.0.1

服务端

> 处理TCP通讯以及监听功能
class Server
    {
        Dictionary<string, Player> dics=new Dictionary<string,Player>();//所有客户端信息
        static void Main(string[] args)
        {
            new Server().Start() ;//实例化一个对象并调用方法
        }
        //创建套接字 Socket所有功能
        public void Start()
        {
            IPAddress ip = IPAddress.Parse("127.0.0.1");//给IP赋值
            IPEndPoint point = new IPEndPoint(ip,500);// 端点    Ip地址和端口号
            //   创建Socket  IP类型  (IPV4地址) Socket类型 字节流   协议类型  TCP协议
            Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //服务器端的Socket才会做绑定和监听的工作
            mySocket.Bind(point);// 绑定  (端点和Socket绑定,Socket中加入IP地址和端口号) 只有在服务器端用  表明是服务器端的套接字
            mySocket.Listen(20);//监听是否有连接请求  20 队列  很多人同时发
            while (true)//服务器端Socket核心
            {//时刻监听是否有客户端连接
                //等待状态  等待客户端连接   执行了这一句 等client有数据后  客户端连接进来才会向下执行
               Socket client= mySocket.Accept();//阻断函数   接收客户端的Socket  等待一个Socket调用Connect方法

               Console.WriteLine("由客户端连接进来");


                //主线程卡住,继续监听是否有其他客户端连接进来    
                //客户端连接进来后数据交互的方法:分出两条分线程   分别处理两个客户端与服务端之间的数据交互
               //client.Send();//发送信息给另一方  发送的时候用的byte数组  
               //client.Receive();//接收对方发送的消息  也是阻断方法  接收的时候也要用byte数组


                //连接进来后开辟一条分线程
                SocketThread st = new SocketThread(client,dics);
               new Thread(new ThreadStart(st.Run)).Start();//没有添加对象直接实例化并调用方法

            }
        }
    }


> 服务端开辟新线程处理连接进来的客户端,进行数据交互



    //SocketThread线程类  每连接进来一个客户端就开一个线程
namespace SocketServer
{
    class SocketThread
    {
        //每一个玩家的基本信息  ’‘’‘’‘’可以封装到Player中,打点调用就可以
        public const int LOGIN = 1001;//请求类型
        public const int TALK = 1002;
        Player player;//读一无二的
        Socket socket;
        string name;
        //也记录一份 所有玩家数据
        Dictionary<string, Player> dics;
        //构造函数
        public SocketThread(Socket clientSocket, Dictionary<string, Player> dics)
        {
            this.socket = clientSocket;//得到自己的Socket
            this.dics = dics;//当前有多少玩家在线
        }


        //分线程进行的地方
        public void Run()//deleget 回调函数
        {//Socket交互,是时刻进行的,服务器和客户端必须有一方断开才能断开
            //添加死循环并有阻断函数
            while (true)
            {

                //接收数据的时候在字节流中接收
                //命令类型
                Byte[] bytes = new Byte[4];
                socket.Receive(bytes);//阻断方法   服务器Socket接收到bytes数组中
                int cmd = TypeConvert.getInt(bytes, true);//字节转int  true,false倒序和正序  
                //也可通过字符串类型进行判定
                //判断命令类型
                switch (cmd)
                {
                    case LOGIN:
                        //封装成方法
                        //信息长度   字符串长度
                        Byte[] str_lenth = new Byte[4];
                        socket.Receive(str_lenth);
                        int length = TypeConvert.getInt(str_lenth, true);
                        //信息内容  名字
                        Byte[] byteInfo = new Byte[length];
                        socket.Receive(byteInfo);
                        name = Encoding.UTF8.GetString(byteInfo, 0, length);//字节转字符串

                        //将客户端名字进行存储
                        if (dics.ContainsKey(name))
                        {
                            Console.WriteLine("用户名重复");
                            //发送给客户端 登录失败
                            SendPlayer("用户名重复");
                        }
                        else
                        {
                            Console.WriteLine(name + "进入房间");
                            //用服务器中的结构进行接收
                            player = new Player();
                            player.Name = name;
                            player.MySocket = socket;
                            dics.Add(player.Name, player);//存储
                            //发送给客户端登录成功
                            SendPlayer("登录成功  已进入房间");
                        }
                        break;
                    case TALK:
                        //字符串长度
                        Byte[] lenth = new Byte[4];
                        socket.Receive(lenth);

                        int strlen = TypeConvert.getInt(lenth, true);
                        Byte[] infos = new Byte[strlen];
                        socket.Receive(infos);
                        string content = Encoding.UTF8.GetString(infos);
                        content = name + "说:" + content;//构建字符串
                        Console.WriteLine(content);

                        //向客户端广发
                        SendAllPlayers(content);
                        break;
                }
            }
        }
        public void SendPlayer(string msg)
        {
            int length = Encoding.UTF8.GetBytes(msg).Length;
            player.MySocket.Send(TypeConvert.getBytes(length, true));
            player.MySocket.Send(Encoding.UTF8.GetBytes(msg));
        }
        void SendAllPlayers(string msg)
        {
            int length = Encoding.UTF8.GetBytes(msg).Length;
            Byte[] lengthBytes = TypeConvert.getBytes(length,true);
            Byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
            foreach (var it in dics)
            {
                Player playerData = it.Value;
                playerData.MySocket.Send(lengthBytes);
                playerData.MySocket.Send(msgBytes);
            }
        }
    }   
}

> 另外新线程中处理数据的时候用到字节和其它类型的转换,提供一个类型转换脚本
> 这个脚本在服务端以及客户端都需要有一份,才能保证两边解析byte数组正确
namespace LSocket.Type
{
    using System.Collections;

    public class TypeConvert
    {

        public TypeConvert()
        {
        }

        public  static byte[] getBytes(float s,bool asc){
            int buf = (int)(s * 100);
            return getBytes(buf,asc);
        }

         public static float getFloat(byte[] buf,bool asc){
            int i=getInt(buf,asc);
            float s=(float)i;
            return s/100;
        }

        public static byte[] getBytes(short s, bool asc)
        {
            byte[] buf = new byte[2];
            if (asc)
            {
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    buf[i] = (byte)(s & 0x00ff);
                    s >>= 8;
                }
            }
            else
            {
                for (int i = 0; i < buf.Length; i++)
                {

                    buf[i] = (byte)(s & 0x00ff);
                    s >>= 8;
                }
            }
            return buf;
        }
        public static byte[] getBytes(int s, bool asc)
        {
            byte[] buf = new byte[4];
            if (asc)
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    buf[i] = (byte)(s & 0x000000ff);
                    s >>= 8;
                }
            else
                for (int i = 0; i < buf.Length; i++)
                {
                    buf[i] = (byte)(s & 0x000000ff);
                    s >>= 8;
                }
            return buf;
        }

        public static byte[] getBytes(long s, bool asc)
        {
            byte[] buf = new byte[8];
            if (asc)
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    buf[i] = (byte)(s & 0x00000000000000ff);
                    s >>= 8;
                }
            else
                for (int i = 0; i < buf.Length; i++)
                {
                    buf[i] = (byte)(s & 0x00000000000000ff);
                    s >>= 8;
                }
            return buf;
        }
        public static short getShort(byte[] buf, bool asc)
        {
            if (buf == null)
            {
                //throw new IllegalArgumentException("byte array is null!");
            }
            if (buf.Length > 2)
            {
                //throw new IllegalArgumentException("byte array size > 2 !");
            }
            short r = 0;
            if (!asc)
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    r <<= 8;
                    r |= (short)(buf[i] & 0x00ff);
                }
            else
                for (int i = 0; i < buf.Length; i++)
                {
                    r <<= 8;
                    r |= (short)(buf[i] & 0x00ff);
                }
            return r;
        }
        public static int getInt(byte[] buf, bool asc)
        {
            if (buf == null)
            {
                // throw new IllegalArgumentException("byte array is null!");
            }
            if (buf.Length > 4)
            {
                //throw new IllegalArgumentException("byte array size > 4 !");
            }
            int r = 0;
            if (!asc)
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    r <<= 8;
                    r |= (buf[i] & 0x000000ff);
                }
            else
                for (int i = 0; i < buf.Length; i++)
                {
                    r <<= 8;
                    r |= (buf[i] & 0x000000ff);
                }
            return r;
        }
        public static long getLong(byte[] buf, bool asc)
        {
            if (buf == null)
            {
                //throw new IllegalArgumentException("byte array is null!");
            }
            if (buf.Length > 8)
            {
                //throw new IllegalArgumentException("byte array size > 8 !");
            }
            long r = 0;
            if (!asc)
                for (int i = buf.Length - 1; i >= 0; i--)
                {
                    r <<= 8;
                    r |= (buf[i] & 0x00000000000000ff);
                }
            else
                for (int i = 0; i < buf.Length; i++)
                {
                    r <<= 8;
                    r |= (buf[i] & 0x00000000000000ff);
                }
            return r;
        }
    }
}

客户端


> 处理客户端网络连接 Socket连接  以及简单的创建UI界面

public class ClientSocket : MonoBehaviour {

    SocketThread st;  //客户端的分线程
    public string message = " ";
    public string talk = " ";
    //服务器中用名字判断是否注册过
    public string username = "请输入用户名";
    void OnGUI()
    {
        if (st == null)
        {
            username = GUILayout.TextField(username);//玩家输入
            if (GUILayout.Button("连接服务器"))
            {
                Connect();
            }
        }
        else
        {
            Talk();
        }
    }
    void Connect()
    {
        IPAddress ip = IPAddress.Parse("192.168.1.4");
        IPEndPoint point = new IPEndPoint(ip, 500);
        Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //客户端连接Socket(套接字:记录了IP,端口,TCP协议)
        mySocket.Connect(ip,500);//连接  先三次握手,仅仅是三次握手 成功后,服务器中的client才能接收到客户端的数据
        //开辟自己用于网络交互的线程
        st = new SocketThread(mySocket,this);//this  当前类型的对象
        st.myThread = new Thread(new ThreadStart(st.Run));
        st.myThread.Start();
        //向服务器发送数据
        st.DoLogin(username);

    }
    void Talk()
    {
        message=GUI.TextArea(new Rect(0,0,200,300),message);//显示框   创建多行文本字段
        talk = GUI.TextField(new Rect(0,300,200,50),talk);//输入框   创建单行文本字段
        if (GUI.Button(new Rect(200, 300, 80, 50), "发送"))
        {
         st.SendMessage(talk); 
        }
    }
}

> 处理与服务端的信息

//玩家发出的命令类型
class CommandID
{
    public static int LOGIN = 1001;
    public static int TALK = 1002;
}
//客户端也要开线程
public class SocketThread  {
    public Thread myThread;//我的分线程
    Socket socketClient;//我的套接字
    ClientSocket demo;//记录绑定在人物身上的脚本

    //构造方法
    public SocketThread(Socket socket, ClientSocket demo)
    {
        this.socketClient = socket;
        this.demo = demo;
    }
    //跑线程
    public void Run()
    {
        while (true)
        {
            try
            {
                string info = ReceiveString();
                demo.message += "\r\n" + info;//“\r\n”换行符
            }
            catch (System.Exception ex)
            {
                MonoBehaviour.print(ex);
                myThread.Abort();//终止此线程
            }



        }
    }
    //把数据发送给服务器  登录
    public void DoLogin(string username)
    {
        try
        {
            //发送用户名给服务器
            Send(CommandID.LOGIN);//请求类型
            Send(username);//字符串
        }
        catch(System.Exception ex)
        {//出错了打印出来
            MonoBehaviour.print(ex.ToString());//新的打印语句
        }
    }
    //发送消息
    public void SendMessage(string talk)
    {
        try
        {
            Send(CommandID.TALK);
            Send(talk);
        }
        catch(System.Exception ex)
        {//错误输出
            MonoBehaviour.print(ex.ToString());
        }
    }
    //类型为int型
    void Send(int data)
    {//先转化   int-》byte[]
        byte[] datas = TypeConvert.getBytes(data,true);
        //Send在服务器和客户端都可以调用
        socketClient.Send(datas);//Socket中的静态方法   当前套接字将数据传给和我连接的另一个套接字
    }
    //用户名等信息为字符串
    void Send(string data)
    {//先转化   string->byte[]
        byte[] datas = Encoding.UTF8.GetBytes(data);
        //调用上面的Send方法,传递的是int值  字符串的长度
        Send(datas.Length);//先将长度传递过去,在服务器开辟号后面的信息字节长度
        socketClient.Send(datas);
    }
   string ReceiveString() 
    {
        int length =ReveiveInt();//接收到消息的长度
        byte[] recvBytes=new byte[length];//为消息内容开的字节数组长度
        socketClient.Receive(recvBytes);
        string data = Encoding.UTF8.GetString(recvBytes);//byte数组转化 为字符串
        return data;

    }
   int ReveiveInt()
    {
        byte[] bytes=new byte[4];
        socketClient.Receive(bytes,4,0);//??参数意义
        int data=TypeConvert.getInt(bytes,true);
        return data;
    }
}

> 客户端也要保存一份上面的TypeConvert脚本保证两边解析一样

同时注意的是,客户端和服务端都需要在连接成功后开辟新的线程处理数据交互,否则主线程会卡死。
对应代码注意添加引用,尤其是TypeConvert脚本的引用

下面是运行截图:
服务器监测情况
客户端1
客户端2

猜你喜欢

转载自blog.csdn.net/weixin_37608784/article/details/78538274