【C#学习10】socket、Unity简易聊天室

目录

  • socket(套接字)
    • tcp
    • udp
    • tcp和udp的区别
  • 简单的socket-tcp服务器和客户端
  • Unity简易聊天室
    • NGUI插件的使用
    • 服务器脚本
    • 客户端脚本
  • 简单的socket-udp服务器和客户端
  • TcpClient,TcpListener,UdpClient
    • TcpClient和TcpListener的使用
      • TcpClient类
      • TcpListener类
    • UdpClient

socket(套接字)

关于Sokect的含义,还有Http等,可以看这篇文章Socket使用大全、Unity高级-Socket

tcp

  • 基于tcp协议的socket通讯类似于B/S架构面向连接,但不同的是服务器端可以向客户端主动推送消息
  • 使用tcp协议通讯需要具备以下几个条件:
  1. 建立一个套接字(socket)
  2. 绑定服务器端ip地址以及端口号-服务器端
  3. 利用Listen()方法开启监听-服务器端
  4. 利用Accept()方法尝试与客户端建立一个连接-服务器端
  5. 利用Connect方法()与服务器建立连接-客户端
  6. 利用Send()方法向建立连接的主机发送消息
  7. 利用Receive()方法接收来自建立连接的主机的消息(可靠连接)

udp

  • 基于udp协议是无连接模式通讯,占用资源少,响应速度快,延时低。至于可靠性,可通过应用层的控制来满足(不可靠连接)
  • 使用udp协议通讯的步骤:
  1. 建立一个套接字(socket)
  2. 绑定服务器端ip地址及端口号-服务器端
  3. 通过SendTo()方法向指定主机发送消息(需提供主机ip地址及端口号)
  4. 通过ReciveFrom()方法接收指定主机发送的消息(需提供主机ip地址及端口号)

tcp和udp的区别

TCP协议和UDP协议连接过程的区别:

  1. 基于连接和无连接
  2. 对系统资源的要求(TCP较多,UDP少)
  3. UDP程序结构简单
  4. TCP采用流模式,UDP采用数据报模式
  5. TCP保证数据正确性,UDP可能丢包;TCP保证数据顺序(先发先收),UDP不保证

简单的socket-tcp服务器和客户端

服务器端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets; //添加命名空间
using System.Net; //添加命名空间

namespace _041_socket编程_tcp协议_服务器端
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.创建socket(socket本身就是一个类)
            //AddressFamily.InterNetwork 内网(可省略),SocketType.Stream 通信方式,ProtocolType.Tcp 通信协议
            Socket tcpServer = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,ProtocolType.Tcp);

            //2.绑定ip跟端口号
            //通过IP地址找到计算机,通过端口号找到软件(可以在cmd窗口中输入ipconfig获得)
            //端口号0-60000,尽量取大点避免端口被占用
            IPAddress ipaddress = new IPAddress(new byte[] {
      192,168,0,102});//IP地址(因为IP是由4个255组成,所以使用new byte[])
            EndPoint point = new IPEndPoint(ipaddress,7788); //ipendpoint是对ip+端口做了一层封装的类
            //向操作系统申请一个可用的ip跟端口号用来做通信
            tcpServer.Bind(point);

            //3.开始监听(等待客户端连接)
            tcpServer.Listen(100); //参数为最大连接数
            Console.WriteLine("开始监听");
            //暂停当前线程,接收客户端连接,之后进行下面的代码
            Socket clientSocket = tcpServer.Accept();
            Console.WriteLine("一个客户端连接成功");

			//4.向客户端发送数据
            //使用返回的socket跟客户端进行通信
            string message = "已成功连接客户端";
            byte[] data = Encoding.UTF8.GetBytes(message);//将字符串以UTF-8的编码格式转换为字节数组
            clientSocket.Send(data); //向客户端发送消息
            Console.WriteLine("向客户端发送数据");

            ///
            ///服务器端接收客户端发送回的数据 
            ///
            byte[] data2 = new byte[1024];
            int length = clientSocket.Receive(data2);
            string message2 = Encoding.UTF8.GetString(data2, 0, length);
            Console.WriteLine("从客户端接收消息:" + message2);
            

            Console.ReadKey();
        }
    }
}

客户端:

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

namespace socket编程_tcp协议_客户端
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.创建socket
            Socket tcpClient = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,ProtocolType.Tcp);
            
            //2.与服务器端连接(注意IP地址和端口要一致)
            //可以把一个字符串的ip地址转化为ipadress的对象
            IPAddress ipaddress = IPAddress.Parse("192.168.0.102"); 
            //ip地址+端口号需与服务器端相同
            EndPoint point = new IPEndPoint(ipaddress,7788);
            //在客户端发起连接,通过ip和端口号定位一个要连接的服务器端
            tcpClient.Connect(point);

			//3.接收服务器端的数据
            byte[] data = new byte[1024];
            //data数组作为容器,用来接收数据,并返回一个表示接收了多少字节的长度
            int length = tcpClient.Receive(data);
            //从0开始,取length个字节的数据(不然会把所有数据转化)
            string message = Encoding.UTF8.GetString(data, 0, length);
            Console.WriteLine(message);

            ///
            /// 向服务器端发送消息
            /// 
            string message2 = Console.ReadLine();
            tcpClient.Send(Encoding.UTF8.GetBytes(message2));


            Console.ReadKey();
        }
    }
}

Unity简易聊天室

NGUI插件的使用

  1. 下载和导入NGUI
  • 首先先去网上下载资源包,注意Unity2018的版本是NGUI v3.12.0,可以从这里下地址
  • 这时Unity的菜单栏上会出现NGUI
    【C#学习10】socket、Unity简易聊天室_第1张图片
  1. 使用
  • 选择"Open->Prefabs Toolbar"打开UI菜单栏,有很多封装好的UI界面
    【C#学习10】socket、Unity简易聊天室_第2张图片
  • 选中第一个拖到场景中,作为背景,并把视图设置为2D
  • 选中"UI Root",右键背景创建一个"Label",作为聊天的窗口
    在这里插入图片描述
  • 调整"Label"的参数
    【C#学习10】socket、Unity简易聊天室_第3张图片
  • 最终NGUI界面效果
    【C#学习10】socket、Unity简易聊天室_第4张图片

服务器脚本

Client类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加下列命名空间
using System.Net.Sockets;
using System.Threading;

namespace _042_聊天室_socket_tcp服务器端
{
     
    class Client
    {
     
        private Socket clientSocket;
        private Thread t;
        private byte[] data = new byte[1024];

        public Client(Socket s)
        {
     
            clientSocket = s;
            //启动一个线程,处理客户端的数据接收
            t = new Thread(ReceiveMessage);
            t.Start();
        }

        private void ReceiveMessage()
        {
     
            while (true) //一直接收客户端的数据 
            {
     
                //在接收数据前,判断socket连接是否断开
                //1.clientSocket.Connected == false
                //2.另外一种方法判断是否连接(响应时间,判断是否能从客户端接收消息)
                if (clientSocket.Poll(10, SelectMode.SelectRead))
                {
     
                    clientSocket.Close();//关闭链接
                    break; //跳出一直接收数据的循环,终止线程执行
                }
                else
                {
     
                    int length = clientSocket.Receive(data);
                    string message = Encoding.UTF8.GetString(data, 0, length);

                    //接收到数据的时候,要把这个数据分发到客户端(相当于广播消息)
                    //因为每个客户端都要看到这条消息嘛
                    Program.BroadcastMessage(message);

                    Console.WriteLine("接收到消息:" + message);
                }   
            }
        }

        public void SendMessage(string message)
        {
     
            byte[] data = Encoding.UTF8.GetBytes(message);
            clientSocket.Send(data);
        }

        public bool Connected //属性,是否断开连接
        {
     
            get{
      return clientSocket.Connected; }
        }
    }
}

Program类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加下列命名空间
using System.Net.Sockets;
using System.Net;

namespace _042_聊天室_socket_tcp服务器端
{
     
    class Program
    {
     
        //1.创建socket,绑定ip和端口
        static List<Client> clientList = new List<Client>();

        static void Main(string[] args)
        {
     
            Socket tcpServer = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            tcpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.104"), 7788));

            //2.开始监听
            tcpServer.Listen(100);
            Console.WriteLine("server is running....");

            //3.创建客户端对象
            while (true) //使server能够一直接收到客户端的连接
            {
     
                Socket clientSocket = tcpServer.Accept();
                Console.WriteLine("a client is connected!");
                //把每个与客户端通信的逻辑放到client类里进行处理
                Client client = new Client(clientSocket);
                clientList.Add(client); //创建一个链表,添加连接的客户端
            }
        }

        /// 
        /// 广播消息
        /// 
        /// 
        //服务器端接收到消息后对所有客户端进行广播
        public static void BroadcastMessage(string message)
        {
     
            //保存已经断开的连接的客户端
            var notConnectedList = new List<Client>();
            foreach (var client in clientList)
            {
     
                if (client.Connected)//判断连接是否还在,才发送广播
                {
     
                    client.SendMessage(message);
                }
                else//断开的话则保存到notConnectedList中
                {
     
                    notConnectedList.Add(client);
                }
            }

            foreach (var temp in notConnectedList)
            {
     
                clientList.Remove(temp);
            }
        }
    }
}

客户端脚本

  1. 创建空物体"ChatManager",挂载脚本"ChatManager",实现功能“点击按钮,发送的输入语句”

  2. 按钮绑定事件
    【C#学习10】socket、Unity简易聊天室_第5张图片

  3. ChatManager脚本

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

public class ChatManager : MonoBehaviour
{
     
    private Socket clientSocket;
    private Thread t;
    private byte[] data = new byte[1024]; //数据容器
    private string message = ""; //消息容器

    public string ipaddress = "192.168.0.104"; //IP地址
    public int port = 7788; //端口号

    public UIInput textInput; //获取按钮组件 
    public UILabel chatLabel; //获取聊天窗口

    // Start is called before the first frame update
    void Start()
    {
     
        ConnectToServer();
    }
      
    // Update is called once per frame
    void Update()
    {
     
        if (message!=null&&message!="") //消息显示在聊天窗口上
        {
     
            chatLabel.text += "\n"+message;
            message = ""; //清空消息
        }
    }

    //跟服务器端建立连接
    void ConnectToServer()
    {
     
        clientSocket = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream,ProtocolType.Tcp);
        clientSocket.Connect(new IPEndPoint(IPAddress.Parse(ipaddress), port));

        //创建一个线程用来接收消息
        t = new Thread(ReceiveMessage);
        t.Start();
    }

    //向服务器端发送消息
    void SendMessage(string message)
    {
     
        clientSocket.Send(Encoding.UTF8.GetBytes(message));
    }

    //事件-发送输入框的值
    public void OnSendButtonClick()//UGUI按钮组件的On Click需要绑定这个事件
    {
     
        string value = textInput.value; //获取输入框的值
        SendMessage(value);
        textInput.text = ""; //发送完清空输入框
    }

    void OnDestroy() //OnDestroy是Unity的生命周期函数,跟Start()一样默认调用
    {
     
        clientSocket.Shutdown(SocketShutdown.Both);//关闭数据的发送和接收
        clientSocket.Close(); //断开连接
    }

    /// 
    /// 线程:用来循环接收消息
    /// 
    void ReceiveMessage()
    {
     
        while(true)
        {
     
            if (clientSocket.Connected == false)
            {
     
                break;
            }
            else
            {
     
                int length = clientSocket.Receive(data);
                message = Encoding.UTF8.GetString(data, 0, length);
            }

            //unity不允许在单独的线程里操作Unity里的组件
            //所以只能在Unity的生命周期函数里进行组件控制
            //chatLabel.text += "\n" + message;
        }
    }
}

简单的socket-udp服务器和客户端

服务器端:

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

namespace _043_socket编程_udp协议_服务器端
{
     
    class Program
    {
     
        private static Socket udpServer;
        static void Main(string[] args)
        {
     
            //1.创建socket
            //SocketType.Dgrams以数据包形式发送
            udpServer = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram,ProtocolType.Udp);

            //2.绑定ip和端口号
            //与tcp相比,udp少了监听这一步骤
            udpServer.Bind(new IPEndPoint(IPAddress.Parse("192.168.0.104"), 7788));

            //3.接收数据
            //设置为后台线程,当程序关闭则结束线程
            new Thread(ReceiveMessage) {
      IsBackground=true}.Start();
            //udpServer.Close(); //关闭连接

            Console.ReadKey();
        }

        static void ReceiveMessage()
        {
     
            while(true) //udp不需要判断是否连接
            {
     
                byte[] data = new byte[1024];
                //不需要指定具体的ip和端口号,因为是要用来赋值的
                EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                //ref能够改变传入参数的值,用来存放接收到的ip和端口号
                //ReceiveFrom会暂停等待参数的传递,直到接收完才运行下面的代码
                int length = udpServer.ReceiveFrom(data, ref remoteEndPoint);

                string message = Encoding.UTF8.GetString(data, 0, length);
                Console.WriteLine("从ip:" + (remoteEndPoint as IPEndPoint).Address
                    + ",端口号:" + (remoteEndPoint as IPEndPoint).Port
                    + "接收到了数据:" + message);
            }
        }
    }
}

客户端:

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

namespace _002_socket编程_udp协议_客户端
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.创建socket
            Socket udpClient = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram,ProtocolType.Udp);

            //2.发送数据(不需要连接)
            while (true)
            {
     
                EndPoint serverPoint = new IPEndPoint(IPAddress.Parse("192.168.0.104"), 7788);
                string message = Console.ReadLine();
                byte[] data = Encoding.UTF8.GetBytes(message);
                udpClient.SendTo(data, serverPoint);
            }

            Console.ReadKey();
        }
    }
}

TcpClient,TcpListener,UdpClient

  • 应用程序可以通过TCPClient、TCPListener和UDPClient类使用 传输控制协议(TCP)用户数据文报协议(UDP) 服务。这些协议类建立在System.Net.Sockets类的基础上,负责数据传送的细节。
    (就是说TCPClient、TCPListener和UDPClient是用来简化socket的)

  • TCPClient和TCPListener使用NetworkStream类表示网格,使用GetStream方法返回网络流,然后调用该流的Read和Write方法。NetWorkStream不拥有协议类的基础套接字,因此关闭它并不影响套接字

  • UdpClient类使用字节数组保存UDP数据文报。使用Send方法向网络发送数据,使用Receive方法接收传入的数据文报

TcpClient和TcpListener的使用

TcpClient类

TcpClient类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。为使TcpClient连接并交换数据,使用TCP ProtocolType创建的TcpListener或Socket必须监听是否有传入的连接请求

可以使用下面两种方法之一连接到该侦听器:

  1. 创建一个TcpClient,并调用三个可用的Connect方法之一
  2. 使用远程主机的主机名和端口号创建TcpClient。此构造函数将自动创建一个连接

给继承者说明要发送和接收数据,请使用GetStream方法来获取一个NetworkStream。调用NetworkStream的Write和Read方法与远程主机之间发送和接收数据,使用Close方法释放与TcpClient关联的所有资源

TcpListener类

  • TcpListener类提供一些简单方法,用于在阻止同步模式下侦听和接收传入连接请求
    • 可使用TcpClient或Socket来连接TcpListener。
    • 可使用IPEndPoint、本地IP地址及端口号,或者仅使用端口号来创建TcpListener。
    • 可以将本地IP地址指定为Any,将本地端口号指定为0(如果希望基础服务提供程序为您分配这些值),如果您选择这样做,可在连接套接字后使用LocalEndpoint属性来标识已指定的信息
    • Start方法用来开始侦听传入的连接请求。Start将对传入连接进行排队,直至您调用Stop方法或它已经完成MaxConnections排队为止。
    • 可使用AcceptSocket或AcceptTcpClient从传入连接请求队列提取连接
    • 如果要避免阻止,可首先使用Pending方法来确定队列中是否有可用的连接请求
  • 调用Stop方法来关闭TcpListener

服务器端:

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

namespace _044_tcplistener
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.TcpListener对socket进行了一层封装,这个类里面自己会去创建socket对象
            TcpListener tcpListener = new TcpListener(IPAddress.Parse("192.168.0.104"),7788);
           
            //2.开始监听
            tcpListener.Start();
            
            //3.等待客户端进行连接
            TcpClient client = tcpListener.AcceptTcpClient();
            
            //4.取得客户端发来的数据
            //获得一个网络流,从这个网络流可以取得客户端发送过来的数据
            NetworkStream stream = client.GetStream();

            byte[] data = new byte[1024];//创建一个数据容器,用来装载数据
            while (true)
            {
     
                //00表示从数组的第几个索引开始存放数据
                //1024表示最大可读取的字节数
                int length = stream.Read(data, 0, 1024);
                string message = Encoding.UTF8.GetString(data, 0, length);
                Console.WriteLine("获得的消息为:" + message);
            }

            //收到消息后,释放程序
            stream.Close();
            client.Close();
            tcpListener.Stop(); //停止监听

            Console.ReadKey();
        }
    }
}

客户端:

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

namespace _003_tcpclient
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.当我们创建tcpclient对象的时候,会自动跟server建立连接
            TcpClient client = new TcpClient("192.168.0.104",7788);

            //2.通过网络流进行数据的交换
            NetworkStream stream = client.GetStream();

            //3.发送数据
            while (true)
            {
     
                string message = Console.ReadLine();
                byte[] data = Encoding.UTF8.GetBytes(message);
                //read用来读取数据(接收数据),write用来写入数据(发送数据)
                stream.Write(data, 0, data.Length);
            }
            stream.Close();
            client.Close();

            Console.ReadKey();
        }
    }
}

UdpClient

UdpClient类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接UDP数据报。因为UDP是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。

可以选择使用下面两种方法之一来建立默认远程主机:

  • 使用远程主机名和端口号作为参数创建UdpClient类的实例
  • 创建UdpClient类的实例,然后调用Connect方法
  • 可以使用在UdpClient中提供的任何一种发送方法数据发送到远程设备,使用Receive方法可以从远程主机接收数据
  • UdpClient方法还允许发送和接收多路广播数据报。使用JoinMulticastGroup方法可以将UdpClient预订给多路广播组,使用DropMulticastGroup方法可以从多路广播中取消对UdpClient的预订

服务端:

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

namespace _045_udpclient
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.创建udpclient,绑定ip和端口号
            UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.0.105"), 7788));

            //2.创建一个IPEndPoint接收数据
            IPEndPoint point = new IPEndPoint(IPAddress.Any,0);
            while (true)
            {
     
                byte[] data = udpClient.Receive(ref point);

                string message = Encoding.UTF8.GetString(data);
                Console.WriteLine("收到了消息:" + message);
            }

            udpClient.Close();
            Console.ReadKey();
        }
    }
}

客户端:

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

namespace _004_udpclient
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            //1.创建udpclient对象
            UdpClient client = new UdpClient();

            //2.发送数据
            while (true)
            {
     
                string message = Console.ReadLine();
                byte[] data = Encoding.UTF8.GetBytes(message);
                client.Send(data, data.Length, new IPEndPoint(IPAddress.Parse("192.168.0.105"), 7788));
            }

            client.Close();
            Console.ReadKey();
        }
    }
}

你可能感兴趣的:(C#,c#)