Week 08-day01-Unity网络通讯之聊天室

目录

一、了解编写服务器demo 

 二、Demo一之中的不足:1.byte[1024]无内容部分都转成了空格,如何解决?2.客户端只能发送一条消息,如何解决?3.只能接受一个客户端发送的消息,如何改进?

 解决方法:

三:实现消息的广播

 四:改写成为异步服务器

思考:有没有问题呢?会不会出错呢?

五、Client类的封装

ClientPeer类

NetManager类:

main函数类:

六、动态链接库:


一、了解编写服务器demo 

基础知识:

        IP地址:计算机在网络中的位置
        DNS:将域名转化为对应IP地址的服务器
        端口号:计算机某一个程序,在操作系统中的编号
        协议:网络传输中的约定
        TCP:管道式传输
                三次握手:
                四次挥手:
        UDP:广播式传输

237c7b546a0f4816ae548aaa470a166c.png

服务器端代码1.0:

using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace NetWorkTest
{
    //ip:计算机在网络中的位置
    //端口:计算机应用程序在操作系统中的编号
    //传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
    //          UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
    class Program
    {
        static void Main(string[] args)
        {
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("192.168.117.1");
            int port = 9999;     
            serverSocket.Bind(new IPEndPoint(ip,port));
            serverSocket.Listen(10);

            Console.WriteLine("服务器启动了!");
           
            Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
            Console.WriteLine("服务器收到了一个客户端连接"+clientSocket.RemoteEndPoint.ToString());

            Byte[] MsgArr = new Byte[1024];
            clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
            string msg = Encoding.UTF8.GetString(MsgArr);
            Console.WriteLine("接受的数据为:"+msg);

            Console.ReadKey();
        }
    }
}

 客户端代码1.0:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ClientTestOne
{
    class Program
    {
        static void Main(string[] args)
        {
            Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
            int port = 9999;
            clientSocket.Connect(new IPEndPoint(iPAddress, port));
            Console.WriteLine("客户端开始连接服务器!");

            string content = "wo只会心疼哥哥!";
            byte[] msg = Encoding.UTF8.GetBytes(content);
            clientSocket.Send(msg);

            Console.ReadKey();
        }
    }
}

 二、Demo一之中的不足:
1.byte[1024]无内容部分都转成了空格,如何解决?
2.客户端只能发送一条消息,如何解决?
3.只能接受一个客户端发送的消息,如何改进?

思路:使用多线程

978d0af7928742c6838df06dd1293128.png

 解决方法:

1.

2. 

3.

服务端2.0

using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace NetWorkTest
{
    //ip:计算机在网络中的位置
    //端口:计算机应用程序在操作系统中的编号
    //传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
    //          UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
    class Program
    {
        static void Main(string[] args)
        {
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("192.168.117.1");
            int port = 9999;     
            serverSocket.Bind(new IPEndPoint(ip,port));
            serverSocket.Listen(10);

            Console.WriteLine("服务器启动了!");

            while (true)
            {
                Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
                Console.WriteLine("服务器收到了一个客户端连接" + clientSocket.RemoteEndPoint.ToString());
                Thread thread = new Thread(ReceiveMsg);
                thread.Start(clientSocket);
            }

            Console.ReadKey();
        }

        private static void ReceiveMsg(object obj)
        {
            Socket clientSocket = obj as Socket;
            byte[] MsgArr = new byte[1024];
            while (true)
            {
                int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
                string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
                Console.WriteLine("接受的数据为:" + msg);
            }
        }
    }
}

客户端2.0

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ClientTestOne
{
    class Program
    {
        static Socket clientSocket;
        static void Main(string[] args)
        {
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
            int port = 9999;
            clientSocket.Connect(new IPEndPoint(iPAddress, port));
            Console.WriteLine("客户端开始连接服务器!");

            Thread thread = new Thread(ReceiveMsg);
            thread.Start();
     
            while (true)
            {
                string content = Console.ReadLine();
                byte[] msg = Encoding.UTF8.GetBytes(content);
                clientSocket.Send(msg);

            }

            Console.ReadKey();
        }

        static void ReceiveMsg()
        {
            byte[] MsgArr = new byte[1024];
            while (true)
            {
                int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
                string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
                Console.WriteLine("接受的数据为:" + msg);
            }
        }

    }
   
}

又出现一个小问题:只要有客户端关闭,服务器就会出错,那么如何改进这个问题呢?解决方式:需要使用try...catch

改进后的服务器2.0代码:

using NetMQ.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace NetWorkTest
{
    //ip:计算机在网络中的位置
    //端口:计算机应用程序在操作系统中的编号
    //传输协议:TCP:管道式传输,好处:安全,信息的先后顺序保持不变;缺点:传输速度限制比较大
    //          UDP:广播式传输,好处:传输速度很快;缺点:安全性较低,丢包
    class Program
    {
        static void Main(string[] args)
        {
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("192.168.117.1");
            int port = 9999;     
            serverSocket.Bind(new IPEndPoint(ip,port));
            serverSocket.Listen(10);

            Console.WriteLine("服务器启动了!");

            while (true)
            {
                Socket clientSocket = serverSocket.Accept();//这是一个阻塞函数
                Console.WriteLine("服务器收到了一个客户端连接" + clientSocket.RemoteEndPoint.ToString());
                Thread thread = new Thread(ReceiveMsg);
                thread.Start(clientSocket);
            }

            Console.ReadKey();
        }

        private static void ReceiveMsg(object obj)
        {
            Socket clientSocket = obj as Socket;
            byte[] MsgArr = new byte[1024];
            while (true)
            {
                try
                {
                    //receive的返回值,就是结束到的数据的长度,Receive()是一个阻塞函数
                    int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
                    if (Length == 0)
                    {
                        clientSocket.Close();
                        return;

                    }
                    string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
                    Console.WriteLine("接受的数据为:" + msg);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    clientSocket.Close();
                    return;
                }
               
            }
        }
    }
}

 运行截图:

93ed5f79fcb84cefadb8cb8e9d19cf92.png

三:实现消息的广播

提示:客户端自己不需要给自己发消息,使用List存储客户端连接

服务端3.0

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace ChatServer
{
    class Program
    {
        //消息的广播:需要使用List容器存储与客户端建立的连接
        private static List<Socket> SocketPoolList = new List<Socket>();

        static void Main(string[] args)
        {
            try
            {
                //连接客户端
                Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                int port = 8899;
                IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
                Serversocket.Bind(new IPEndPoint(iPAddress, port));
                Serversocket.Listen(10);

                Console.WriteLine("服务器启动了");

                //主线程中最好不要放置死循环的代码
                Thread thread = new Thread(AcceptClient);
                thread.Start(Serversocket);

              
                Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message); 
                
            }
            
           
        }

        //接收客户端连接
        private static void AcceptClient(object obj)
        {
            Socket Serversocket = obj as Socket;
            if (Serversocket == null)
            {
                return;
            }
            Socket ClientServer = Serversocket.Accept();
            SocketPoolList.Add(ClientServer);
            Console.WriteLine("服务器收到了一个连接:" + ClientServer.RemoteEndPoint.ToString());

            Thread thread = new Thread(ReceiveMsg);
            thread.Start(ClientServer);

            //服务器需要一直接受客户端的消息
            AcceptClient(Serversocket);
        }

        private static void ReceiveMsg(Object obj)
        {
            Socket ClientSocket = obj as Socket;
            if (ClientSocket == null)
            {
                return;
            }
            byte[] msgArr = new byte[1024];
            while (true)
            {
                try
                {
                    //客户端接受消息
                    int Length = ClientSocket.Receive(msgArr, 0, msgArr.Length, SocketFlags.None);
                    if (Length == 0)
                    {
                        ClientSocket.Close();
                        SocketPoolList.Remove(ClientSocket);
                        return;
                    }
                    string clientMsg =  Encoding.UTF8.GetString(msgArr, 0, Length);
                    Console.WriteLine("客户端发送的消息为:"+clientMsg);

                    byte[] msg = new byte[Length];
                    Array.Copy(msgArr, 0, msg,0, Length);
                    for (int i = 0; i < SocketPoolList.Count; i++)
                    {
                        if(SocketPoolList[i] == ClientSocket)
                        {
                            continue;
                        }
                        SocketPoolList[i].Send(msg);
                    }

                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    ClientSocket.Close();
                    SocketPoolList.Remove(ClientSocket);
                    return;
                }
            }
            
        }

    }
}

客户端3.0

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ClientTestOne
{
    class Program
    {
        static Socket clientSocket;
        static void Main(string[] args)
        {
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            IPAddress iPAddress = IPAddress.Parse("192.168.117.1");
            int port = 8899;
            clientSocket.Connect(new IPEndPoint(iPAddress, port));
            Console.WriteLine("客户端开始连接服务器!");

            Console.WriteLine("请输入你的名字:");
            String name = Console.ReadLine();

            Thread thread = new Thread(ReceiveMsg);
            thread.Start();
     
            while (true)
            {
                string content = name + ":"+Console.ReadLine();
                byte[] msg = Encoding.UTF8.GetBytes(content);
                clientSocket.Send(msg);
            }

            Console.ReadKey();
        }

        static void ReceiveMsg()
        {
            byte[] MsgArr = new byte[1024];
            while (true)
            {
                int Length = clientSocket.Receive(MsgArr, 0, MsgArr.Length, SocketFlags.None);
                string msg = Encoding.UTF8.GetString(MsgArr, 0, Length);
                Console.WriteLine(msg);
            }
        }

    }
   
}

运行截图:

555827aa449b4c8ea09ff242c1eeca49.png

 四:改写成为异步服务器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AsyncChatServer
{
    class Program
    {
        //消息的广播:需要使用List容器存储与客户端建立的连接
        private static List<Socket> SocketPoolList = new List<Socket>();
        static void Main(string[] args)
        {
            try
            {
                Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress iPAdress = IPAddress.Parse("192.168.117.1");
                int port = 8899;
                Serversocket.Bind(new IPEndPoint(iPAdress, port));
                Serversocket.Listen(10);

                Console.WriteLine("服务器启动了");
                
                Serversocket.BeginAccept(AsyncAccept,Serversocket);//开启异步接受客户端连接
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadKey();

        }

        static byte[] msgArr = new byte[1024]; 
        private static void AsyncAccept(IAsyncResult ar)
        {
            Socket serverSocket = ar.AsyncState as Socket;
            if (serverSocket == null)
            {
                return;
            }
            Socket clientSocket = serverSocket.EndAccept(ar);
            SocketPoolList.Add(clientSocket);

            clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReveive, clientSocket);

            serverSocket.BeginAccept(AsyncAccept, serverSocket);


        }

        private static void AsyncReveive(IAsyncResult ar)
        {
            Socket clientSocket = ar.AsyncState as Socket;
            try
            {
                int Length = clientSocket.EndReceive(ar);
                if (Length ==0)
                {
                    clientSocket.Close();
                    SocketPoolList.Remove(clientSocket);
                    return;
                }
                string message = Encoding.UTF8.GetString(msgArr,0,Length);
                Console.WriteLine(message);

               
                //消息的广播
                byte[] msg = new byte[Length];
                Array.Copy(msgArr, 0, msg, 0, Length);
                for (int i = 0; i < SocketPoolList.Count; i++)
                {
                    if (SocketPoolList[i] == clientSocket)
                    {
                        continue;
                    }
                    SocketPoolList[i].Send(msg);
                }

                //重新开启监听
                clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReveive, clientSocket);

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                clientSocket.Close();
                SocketPoolList.Remove(clientSocket);
                return;
            }
        }
    }
}

效果同上

思考:有没有问题呢?会不会出错呢?

不会出错,但是有问题,在服务器上的客户端连接接受消息间隔时间很短的情况下,那么就会导致向客户端广播的消息为同一条消息,因为连接上了服务器的客户端在接受消息的时候是异步的,而且共用堆中的数据byte[],因此在间隔时间很短的情况下,那么就会导致客户端接受的消息为同一条消息。

底层原理:多线程在执行的时候,会共享堆内存中的数据(不共享栈内存中的数据)

五、Client类的封装

解决四中的问题,可以利用面向对象的思想改进,将client连接封装成为一个类,使每一个client都能拥有自己byte[]数组来接受消息。

ClientPeer类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AsyncChatServer
{
    class ClientPeer
    {
        Socket clientSocket = null;

        static byte[] msgArr = new byte[1024];

        internal ClientPeer(Socket clientSocket)
        {
            this.clientSocket = clientSocket;

            clientSocket.BeginReceive(msgArr,0,msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
        }

        internal void AsyncReceive(IAsyncResult ar)
        {
            try
            {
                Socket clientSocket = ar.AsyncState as Socket;
                int length = clientSocket.EndReceive(ar);
                if (clientSocket == null)
                {
                    clientSocket.Close();
                    NetManager.Instance.RemoveClient(this);
                    return;
                }

                string message = Encoding.UTF8.GetString(msgArr,0,length);
                Console.WriteLine(message);

                NetManager.Instance.BoardCastMessage(message, this);

                clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
               
                
            }
            catch (Exception e)
            {
                clientSocket.Close();
                NetManager.Instance.RemoveClient(this);
                Console.WriteLine(e.Message);
                return;
            }
            
        }

        public void SendMessage(byte[] message)
        {
            clientSocket.Send(message);
        }

        public void SendMessage(string message)
        {
            clientSocket.Send(Encoding.UTF8.GetBytes(message));
        }
    }
}

NetManager类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AsyncChatServer
{

    class NetManager
    {
        private static NetManager _instance = null;
        private NetManager()
        {

        }

        public static NetManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new NetManager();
                }
                return _instance;
            }
        }

        //消息的广播:需要使用List容器存储与客户端建立的连接
        private static List<ClientPeer> clientList = new List<ClientPeer>();

        public void AddClient(ClientPeer client)
        {
            clientList.Add(client);
        }

        public void RemoveClient(ClientPeer client)
        {
            clientList.Remove(client);
        }

        public void BoardCastMessage(string message,ClientPeer client = null)
        {
            for (int i = 0; i < clientList.Count; i++)
            {
                if (client!=null&& clientList[i] == client)
                {
                    continue;
                }
                clientList[i].SendMessage(message);
            }
        }
        public void BoardCastMessage(byte[] message, ClientPeer client = null)
        {
            for (int i = 0; i < clientList.Count; i++)
            {
                if (client != null && clientList[i] == client)
                {
                    continue;
                }
                clientList[i].SendMessage(message);
            }
        }

        public void SendMessage(byte[] message,ClientPeer client)
        {
            client.SendMessage(message);
        }

        public void SendMessage(string message, ClientPeer client)
        {
            client.SendMessage(message);
        }

    }
}

main函数类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace AsyncChatServer
{
    class Program
    {
       
        static void Main(string[] args)
        {
            try
            {
                Socket Serversocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress iPAdress = IPAddress.Parse("192.168.117.1");
                int port = 8899;
                Serversocket.Bind(new IPEndPoint(iPAdress, port));
                Serversocket.Listen(10);

                Console.WriteLine("服务器启动了");
                
                Serversocket.BeginAccept(AsyncAccept, Serversocket);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadKey();

        }

        static byte[] msgarr = new byte[1024];

        private static void AsyncAccept(IAsyncResult ar)
        {
            Socket serversocket = ar.AsyncState as Socket;
            if (serversocket == null)
            {
                return;
            }
            Socket clientsocket = serversocket.EndAccept(ar);

            ClientPeer clientPeer = new ClientPeer(clientsocket);
            NetManager.Instance.AddClient(clientPeer);

            //clientsocket.beginreceive(msgarr, 0, msgarr.length, socketflags.none, asyncreveive, clientsocket);

            serversocket.BeginAccept(AsyncAccept, serversocket);


        }
    }
}

按理来说:ip端口应该也不应该放在Main函数中,

六、动态链接库:

ddl:包含该动态链接库实际的函数和数据。在程序运行阶段,加载该文件,并将该文件映射到进程地址空间中,然后访问该文件中的相应函数。

一般用来实现一些工具类,配置接口

f2844f166e7449e6934bf1869679e53d.png

生成.dll文件

9491a18f6c5946b581516c5d18745f2b.png

七、将网络模块移植到unity中:

代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class NetManager : MonoBehaviour
{
    //单例
    private static NetManager _instance = null;

    private NetManager()
    {
        
    }

    private static NetManager Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = new NetManager();
            }
            return _instance;
        }
    }
    // Start is called before the first frame update
    //客户端连接服务器
    //1.连接到服务器
    //2.接受服务器的消息,并处理
    //3.向服务器发送消息
    byte[] msgArr = new byte[1024];
    Socket clientSocket = null;
    
    //消息队列,用于处理消息
    Queue<string> messageQueue = new Queue<string>();

    private void OnEnable()
    {
        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    }
    

    void Start()
    {
        
        clientSocket.Connect(new IPEndPoint(IPAddress.Parse(Protocol.ProtocolConfig.ip), Protocol.ProtocolConfig.port));
        Debug.Log("客户端开始连接服务器!");
        try
        {
            clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);
        }
        catch (System.Exception e)
        {
            Debug.Log(e.Message);
            
        }

    }

    //接受服务器的消息
    private void AsyncReceive(IAsyncResult ar)
    {
        Socket clientSocket = ar.AsyncState as Socket;
        if (clientSocket == null)
        {
            return;
        }
        int length = clientSocket.EndReceive(ar);
        string message = Encoding.UTF8.GetString(msgArr, 0, length);//接受的消息,思考为啥不在这个函数里面处理消息
                                                                    //异步的本质是一条多线程,很多Unity的API没有办法在支线程中使用
        messageQueue.Enqueue(message);

        clientSocket.BeginReceive(msgArr, 0, msgArr.Length, SocketFlags.None, AsyncReceive, clientSocket);

        
    }

    private void SendMessageToServer(string message)
    {
        clientSocket.Send(Encoding.UTF8.GetBytes(message));
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            SendMessageToServer("helloWorld");
        }

        if (messageQueue.Count>0)
        {
            string message = messageQueue.Dequeue();
            //处理这个消息
            Debug.Log(message);
        }
    }
}

运行成功:

8c22ac05086f4067b492a30d071f95ae.png

 思考:接受的消息为啥不在异步函数中执行?为啥要用到消息队列

猜你喜欢

转载自blog.csdn.net/qq_53663718/article/details/127342870