深入浅出Socket通讯:Unity客户端和服务端

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/leemu0822/article/details/100016984

我会先讲解一下socket通讯的基础知识,希望大家可以认真看完,代码实现其实只是一种根据通讯原理一步步实现的逻辑,如果你了解了原理,换另外一种语言你也很快能写出来,而且通讯过程中遇到问题的时候你可以很快找到问题所在。

Socket又称为套接字,什么是套接字呢?网络套接字是IP地址与端口的组合。例如我的ip是192.168.0.1,开启的端口是8080,那么套接字就是192.168.0.1:8080。

为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接为流式套接字(SOCK-STREAM)、数据报套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。

(1)流式套接字。它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。在TCP/IP协议簇中,使用TCP协议来实现字节流的传输,当用户想要发送大批量的数据或者对数据传输有较高的要求时,可以使用流式套接字。

(2)数据报套接字。它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端按发送顺序接收数据。在TCP/IP协议簇中,使用UDP协议来实现数据报套接字。在出现差错的可能性较小或允许部分传输出错的应用场合,可以使用数据报套接字进行数据传输,这样通信的效率较高。

(3)原始套接字。该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备。 [1] 

我们一般都是使用流式套接字,至于数据报套接字,其实就是UDP,在特定的场景下,我们也有可能用到,UDP就是不需要建立连接的,直接发送,不管对方收没收到,至于用在什么场景,大家可以自己思考一下。

至于socket通讯的流程,可以看看下图:

两个socket可以理解成两个端点,他们连接的过程可以理解成有一根线把两个端点连接起来,主动发起连接的一般是客户端,举个例子:当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。

和客户端编程相比,服务器编程就要复杂一些。

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。同一个端口,被一个Socket绑定了以后,就不能被别的Socket绑定了。

讲了那么多,下面直接上代码吧,希望你们能对照着上面的图,然后一步步的看懂它,服务端代码:

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

public class SocketServer : MonoBehaviour
{

    // Use this for initialization
    public void StartServer()
    {
        bt_connnect_Click();
    }

    // Update is called once per frame
    void Update()
    {

    }

    void Start()
    {
        StartServer();
    }
    private void bt_connnect_Click()
    {
        try
        {
            int _port = 8888;
            string _ip = "127.0.0.1";

            //点击开始监听时 在服务端创建一个负责监听IP和端口号的Socket
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(_ip);
            //创建对象端口
            IPEndPoint point = new IPEndPoint(ip, _port);

            socketWatch.Bind(point);//绑定端口号
            Debug.Log("监听成功!");
            socketWatch.Listen(10);//设置监听,最大同时连接10台

            //创建监听线程
            Thread thread = new Thread(Listen);
            thread.IsBackground = true;
            thread.Start(socketWatch);
        }
        catch { }

    }

    /// <summary>
    /// 等待客户端的连接 并且创建与之通信的Socket
    /// </summary>
    Socket socketSend;
    void Listen(object o)
    {
        try
        {
            Socket socketWatch = o as Socket;
            
            while (true)
            {
                socketSend = socketWatch.Accept();//等待接收客户端连接
                Debug.Log(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功!");         
                //开启一个新线程,执行接收消息方法
                Thread r_thread = new Thread(Received);
                r_thread.IsBackground = true;
                r_thread.Start(socketSend);
            }
        }
        catch { }
    }

    /// <summary>
    /// 服务器端不停的接收客户端发来的消息
    /// </summary>
    /// <param name="o"></param>
    void Received(object o)
    {
        try
        {
            Socket socketSend = o as Socket;
            while (true)
            {
                //客户端连接服务器成功后,服务器接收客户端发送的消息
                byte[] buffer = new byte[1024 * 1024 * 3];
                //实际接收到的有效字节数
                int len = socketSend.Receive(buffer);
                if (len == 0)
                {
                    break;
                }
                string str = Encoding.UTF8.GetString(buffer, 0, len);
                Debug.Log("服务器打印:" + socketSend.RemoteEndPoint + ":" + str);
                
                Send("我收到了"+str);
            }
        }
        catch { }
    }

    /// <summary>
    /// 服务器向客户端发送消息
    /// </summary>
    /// <param name="str"></param>
    void Send(string str)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(str);
        
        socketSend.Send(buffer);
    }
}

客户端代码:

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

public class SocketClient : MonoBehaviour
{

    public InputField input;

    [SerializeField] private Button send_btn;
    [SerializeField] private Button connect_btn;
    // Use this for initialization
    public void StartClient()
    {
        bt_connect_Click();
    }

    // Update is called once per frame
    void Update()
    {

    }

    void Start()
    {

        send_btn.onClick.AddListener(() =>
        {
            SendMsg();
        });
        connect_btn.onClick.AddListener(() =>
        {
            StartClient();
        });
    }
    public void SendMsg()
    {
        bt_send_Click(input.text);
    }

    Socket socketSend;
    private void bt_connect_Click()
    {
        try
        {
            int _port = 8888;
            string _ip = "127.0.0.1";

            //创建客户端Socket,获得远程ip和端口号
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse(_ip);
            IPEndPoint point = new IPEndPoint(ip, _port);

            socketSend.Connect(point);
            Debug.Log("连接成功!");
            //开启新的线程,不停的接收服务器发来的消息
            Thread c_thread = new Thread(Received);
            c_thread.IsBackground = true;
            c_thread.Start();
        }
        catch (Exception)
        {
            Debug.Log("IP或者端口号错误...");
        }

    }

    /// <summary>
    /// 接收服务端返回的消息
    /// </summary>
    void Received()
    {
        while (true)
        {
            try
            {
                byte[] buffer = new byte[1024 * 1024 * 3];
                //实际接收到的有效字节数
                int len = socketSend.Receive(buffer);
                if (len == 0)
                {
                    break;
                }
                string str = Encoding.UTF8.GetString(buffer, 0, len);
                Debug.Log("客户端打印:" + socketSend.RemoteEndPoint + ":" + str);
            }
            catch { }
        }
    }

    /// <summary>
    /// 向服务器发送消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void bt_send_Click(string str)
    {
        try
        {
            string msg = str;
            byte[] buffer = new byte[1024 * 1024 * 3];
            buffer = Encoding.UTF8.GetBytes(msg);
            socketSend.Send(buffer);
        }
        catch { }
    }
}

客户端截图:

服务端截图:

希望对你有用,下一篇我会用python写一篇关于socket tcp的,直接获取新浪主页的内容保存下来,如果你感兴趣,可以关注我:

猜你喜欢

转载自blog.csdn.net/leemu0822/article/details/100016984