UDP通信学习

示例一:

客户端向服务端(虚拟助手)发送消息,服务端接收到消息;

客户端代码:

using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine.UI;

public class UDPClient : MonoBehaviour
{
    public Button btn_StartConnect;
    public Button btn_Disconnect;
    public Button btn_Send;
    public InputField input_IP;
    public InputField input_Port;
    public InputField input_Data;
    public Text txt_ConnectState;

    UdpClient udpClient;
    IPEndPoint ipEndPoint; //客户端端口
    byte[] sendData = new byte[1024]; //发送的数据,必须为字节

    void Awake()
    {
        btn_StartConnect.onClick.AddListener(ButtonStartConnect);
        btn_Disconnect.onClick.AddListener(ButtonDisconnect);
        btn_Send.onClick.AddListener(ButtonSend);
    }

    void OnApplicationQuit()
    {
        ButtonDisconnect();
    }

    /// <summary>
    /// 开始连接
    /// </summary>
    public void ButtonStartConnect()
    {
        ipEndPoint = new IPEndPoint(IPAddress.Parse(input_IP.text), int.Parse(input_Port.text));//定义连接的服务器ip和端口
        udpClient = new UdpClient();//初始化一个客户端
        IPEndPoint senderReceive = new IPEndPoint(IPAddress.Any, 0);
        txt_ConnectState.text = "已经建立连接" + "\n";
    }

    /// <summary>
    /// 断开连接
    /// </summary>
    public void ButtonDisconnect()
    {
        txt_ConnectState.text += "断开连接";
        if (udpClient != null)
            udpClient.Close();
    }

    /// <summary>
    /// 客户端发送数据
    /// </summary>
    /// <param name="sendStr"></param>
    void SocketSend(string sendStr)
    {
        //清空发送缓存
        sendData = new byte[1024];
        //数据类型转换
        sendData = Encoding.UTF8.GetBytes(sendStr);
        //发送给所有服务端
        udpClient.Send(sendData, sendData.Length, ipEndPoint);
        txt_ConnectState.text += "客户端发送了:" + sendStr + "\n";
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    public void ButtonSend()
    {
        if ((!string.IsNullOrEmpty(input_Data.text))&&udpClient!=null)
        {
            SocketSend(input_Data.text);
        }
        else if (string.IsNullOrEmpty(input_Data.text) && udpClient != null)
        {
            txt_ConnectState.text += "请输入要发送的消息" + "\n";
        }
        else if(udpClient==null)
        {
            txt_ConnectState.text += "请先建立连接" + "\n";
        }
    }
}

单片机多功能调试助手 下载链接:百度网盘 请输入提取码
提取码:izmn

示例二:

客户端向服务端发送一张图片,服务端接收该图片后,显示5秒,并保存在Assets文件夹下;

客户端代码:

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

public class UDPClient : MonoBehaviour
{
    public static UDPClient instance;//单例模式(当游戏中的游戏对象只有一个实例的时候,宜用之)

    public Text txt_ConnectState;//连接状态

    string UDPClientIP;//客户端IP
    Socket socket;//Socket(套接字)
    EndPoint serverEnd;//服务端
    IPEndPoint ipEnd;//用网络端点表示 IP和端口
    string recvStr;//接收的字符串
    byte[] recvData;//字节数组:接收的数据
    byte[] sendData;//字节数组:发送的数据
    int recvLen = 0;//接收数据的长度
    Thread connectThread;//开启一个线程

    bool isClientActive = false;//客户端是否激活
    bool isStartHeart = false;//是否开始心跳
    int port;//端口号
    bool isReSend = false;//是否让客户端重新发送数据包
    string reSendStrIndex;//要重新发送的字符串

    float timerRate = 5;//发送消息频率
    float timerInterval = 0f;//接收服务端心跳反馈的时间间隔

    public delegate void ReSendIndexDeledate(string str);//委托
    public event ReSendIndexDeledate ReSendIndexEvent;//事件

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        UDPClientIP = "127.0.0.1";//客户端IP(默认本地IP:127.0.0.1)
        port = 9000;//端口
        UDPClientIP = UDPClientIP.Trim();//删除字符串中所有的空格
        isClientActive = true;//客户端被激活
        InitSocket();
    }

    void Update()
    {
        if (isStartHeart)//是否开始心跳
        {
            HeartSend();
        }
        timerInterval += Time.deltaTime;//检测 发送心跳 与 心跳反馈回来 间隔的时间

        if (timerInterval > 6f)//间隔时间大于6秒
        {
            print("连接异常");
            txt_ConnectState.text = "连接异常";
            timerInterval = 0f;
        }

        if (isReSend)//是否重新发送
        {
            print("重新发送");
            txt_ConnectState.text = "重新发送";
            if (ReSendIndexEvent != null)//如果重新发送的事件不为空
            {
                ReSendIndexEvent(reSendStrIndex);//重新发送这个字符串
                reSendStrIndex = null;//置空
                isReSend = false;//置为默认值
            }
        }
    }

    void OnApplicationQuit()
    {
        isStartHeart = false;
        isClientActive = false;
        SocketQuit();
        Thread.Sleep(25);
    }

    /// <summary>
    /// 初始化Socket
    /// </summary>
    private void InitSocket()
    {
        //定义连接的服务器ip和端口(可以是本机ip,局域网,互联网) 
        ipEnd = new IPEndPoint(IPAddress.Parse(UDPClientIP), port);
        //定义套接字类型,在主线程中定义 
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        //socket.Bind(ipEnd);
        //定义服务端
        IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
        serverEnd = (EndPoint)sender;
        print("等待连接");
        txt_ConnectState.text="等待连接";
        isStartHeart = true;//开始心跳监听
        //客户端发送心跳消息后,计时器开始计时,判断5秒内是否能收到服务端的反馈
        HeartSend();
        //开启一个线程连接,否则主线程卡死
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }

    /// <summary>
    /// 开始向服务端发送心跳包
    /// </summary>
    private void HeartSend()
    {
        timerRate += Time.deltaTime;
        if (timerRate > 5f)
        {
            try
            {
                SocketSend("alive");
            }
            catch
            {
            }
            timerRate = 0f;
        }
    }

    /// <summary>
    /// 客户端发送给服务端数据
    /// </summary>
    /// <param name="sendStr">字符串</param>
    public void SocketSend(string sendStr)
    {
        //清空发送缓存
        sendData = new byte[1500];
        //数据类型转换
        sendData = Encoding.UTF8.GetBytes(sendStr);
        //发送给指定服务端
        socket.SendTo(sendData, sendData.Length, SocketFlags.None, ipEnd);
    }

    /// <summary>
    /// 客户端接收服务端发来的数据
    /// </summary>
    public void SocketReceive()
    {
        //进入接收循环 
        while (isClientActive)
        {
            //对接收数据缓存清零 
            recvData = new byte[20000];
            try
            {
                //获取服务端数据
                recvLen = socket.ReceiveFrom(recvData, ref serverEnd);
                if (isClientActive == false)
                {
                    break;
                }
            }
            catch
            {

            }
            //输出接收到的数据 
            if (recvLen > 0)
            {
                //接收到的信息
                recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
            }
            print("接收到服务端发来的数据:" + recvStr);
            //txt_ConnectState.text = "接收到服务端发来的数据:" + recvStr;

            //心跳回馈
            if (recvStr == "keeping")//接收到服务端反馈的数据:keeping
            {
                print("连接正常");
                //txt_ConnectState.text = "连接正常";
                timerInterval = 0;
            }
            else if (recvStr != null)//如果没有收到服务端发来的keeping,且 接收的字符串中不为空
            {
                //重新发送
                reSendStrIndex = recvStr;
                isReSend = true;
            }
        }
    }

    //关闭Socket连接
    public void SocketQuit()
    {
        //关闭这条线程
        if(connectThread!=null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //关闭socket连接
        if (socket != null)
            socket.Close();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.IO;

public class SendImage : MonoBehaviour
{
    public Button btn_Send;

    string imageString;//字符串表示图片
    string path;//路径
    string picStr;//图片字符串
    int num;
    byte[] imagebytes;//字节数组表示图片
    Dictionary<int, string> UDPStringDic = new Dictionary<int, string>();//创建一个字典

    void Awake()
    {
        btn_Send.onClick.AddListener(ButtonSend);//按钮事件监听
    }

    void Start()
    {
        UDPClient.instance.ReSendIndexEvent += GetReSendIndexFromUDPClient;//事件监听:客户端重新发送数据
        path = Application.streamingAssetsPath + "/" + "电网.jpg";//获取文件路径
        FileStream files = new FileStream(path, FileMode.Open);//文件流
        imagebytes = new byte[files.Length];//字节数组:图片 
        files.Read(imagebytes, 0, imagebytes.Length); //读取字节数组
        files.Close();//关闭文件流,释放资源
        //files.Close();//再关闭文件流,释放资源
        picStr = Convert.ToBase64String(imagebytes);//将数组转换为64位基数的字符串
    }

    void OnDisable()
    {
        UDPClient.instance.ReSendIndexEvent -= GetReSendIndexFromUDPClient;//移除监听
    }

    /// <summary>
    /// 按钮:发送
    /// </summary>
    public void ButtonSend()
    {
        StartCoroutine(SendPicture());//开启一段协程:发送图片数据给服务端
    }

    /// <summary>
    /// 发送图片数据给服务端
    /// </summary>
    /// <returns>返回一个协程</returns>
    IEnumerator SendPicture()
    {
        UDPSplit(picStr);//分割图片字符串
        yield return new WaitForSeconds(0.1f);//间隔0.1秒
        for (int i = 0; i < num - 1000; i++)
        {
            if (UDPStringDic.TryGetValue(i, out imageString))//根据指定的键 获取关键的值  
                /*out参数:如果在一个方法中 返回多个相同类型的值时,可以考虑返回一个数组;
                    但是,如果返回多个不同类型的值时,就可以考虑使用out参数。*/
            {
                UDPClient.instance.SocketSend(imageString);
            }
        }
        yield return new WaitForSeconds(0.1f);//间隔0.1秒
        //发送完成后 发一条信息给服务端
        UDPClient.instance.SocketSend("done");
    }

    /// <summary>
    /// 客户端重新发送数据
    /// </summary>
    /// <param name="str"></param>
    public void GetReSendIndexFromUDPClient(string str)
    {
        string[] reSendNum;//字符串数组:重新发送
        int newindex;
        reSendNum = str.Split('_');//下划线分割出来的字符串数组

        for (int i = 0; i < reSendNum.Length; i++)
        {
            if (int.TryParse(reSendNum[i], out newindex))
            {
                if (UDPStringDic.TryGetValue(newindex, out imageString))
                {
                    UDPClient.instance.SocketSend(imageString);
                }
            }
        }
        //发送完成后 再发一条信息给服务端
        UDPClient.instance.SocketSend("done");
        print("重新发送完毕");
        UDPClient.instance.txt_ConnectState.text = "重新发送完毕";
    }

    /// <summary>
    /// 分割数据
    /// </summary>
    /// <param name="str"></param>
    private void UDPSplit(string str)
    {
        int index = 0;//索引
        int maxIndex = 1000;//最大索引
        string newstr;//新字符串
        int stringTag = 1000;//字符串标签号
        UDPStringDic.Clear();//清空这个字典
        num = (str.Length / 1000) + 1 + 1000;   //将数字变成四位数的,三个字节
                                                //  print(num-1000);
        for (int i = 0; i < num - 1000; i++)
        {
            if (maxIndex > str.Length - index)
            {
                maxIndex = str.Length - index;
            }
            newstr = "1551683020" + "_" + num + "_" + stringTag + "_" + str.Substring(index, maxIndex); //包名,包长,包的顺序号,包的内容

            UDPStringDic.Add(stringTag - 1000, newstr);
            stringTag++;
            index += 1000;
        }
    }
}

服务端代码:

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

public class UDPServer : MonoBehaviour
{
    public static UDPServer instance;

    public Text txt_ConnectState;//连接状态
    public string recvStr; //接收的字符串
    public bool isStartSend = false;//是否开始发送

    Socket socket; //目标socket
    EndPoint clientEnd; //客户端
    IPEndPoint ipEnd; //侦听端口

    byte[] recvData; //字节数组:接收的数据
    byte[] sendData = new byte[1024]; //字节数组:发送的数据
    int recvLen; //接收数据的长度
    Thread connectThread; //连接线程
    int port;//端口号
    bool isSendImage;//是否发送图片
    bool isServerActive = false;//是否启动服务端

    string newCombineStr;//新合并的字符串
    string newImageName;//图片名字
    int newImageCount = 0;//包的长度
    int newStrIndex = 0;//新字符串的索引
    string newImageMessage;//包的内容
    
    bool isFirst = true;//判断是否是第一次接受消息
    string oldImageName;//旧图片的名字
    string dicStr;

    float timerInterval = 0;
    bool isStartCheck = false;

    Dictionary<int, string> newImageDic = new Dictionary<int, string>();//字典:新图片
    List<int> doneIndex = new List<int>();//集合:已完成传输的字符串索引值

    public delegate void UDPServerDeledate(Texture2D byths);//委托
    public event UDPServerDeledate UDPserverEvent;//事件

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        port = 9000;
        isServerActive = true;
        InitSocket(); //在这里初始化server
    }

    void Update()
    {
        timerInterval += Time.deltaTime;
        if (isStartCheck)
        {
            if (timerInterval > 6f)
            {
                print("网络连接异常");
                txt_ConnectState.text = "网络连接异常";
                timerInterval = 0f;
            }
        }

        if (isSendImage)
        {
            ParseByteArr(newCombineStr);
            newCombineStr = null;
            isSendImage = false;
        }
    }

    void OnDisable()
    {
        isServerActive = false;
        SocketQuit();
        Thread.Sleep(100);
    }

    /// <summary>
    /// 初始化Socket
    /// </summary>
    private void InitSocket()
    {
        //定义侦听端口
        ipEnd = new IPEndPoint(IPAddress.Any, port);
        //定义套接字类型
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
        //服务端需要绑定ip 
        socket.Bind(ipEnd);
        //定义客户端
        IPEndPoint sender = new IPEndPoint(IPAddress.Broadcast, 0);
        clientEnd = (EndPoint)sender;
        print("local:等待连接数据");
        txt_ConnectState.text = "等待连接数据";
        //开启一个线程连接
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }

    /// <summary>
    /// 服务器向客户端发送消息
    /// </summary>
    /// <param name="sendStr"></param>
    public void SocketSend(string sendStr)
    {
        //清空发送缓存 
        sendData = new byte[20000];
        //数据类型转换 
        sendData = Encoding.UTF8.GetBytes(sendStr);
        //发送给指定客户端 
        socket.SendTo(sendData, sendData.Length, SocketFlags.None, clientEnd);
    }

    /// <summary>
    /// 服务端接收来自客户端的消息
    /// </summary>
    private void SocketReceive()
    {
        //进入接收循环
        while (isServerActive)//激活服务端
        {
            //对data清零
            recvData = new byte[1500];
            try
            {
                //服务端接收从客户端发来的数据
                recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
                if (isServerActive == false)
                {
                    break;
                }
            }
            catch
            {

            }

            if (recvLen > 0)
            {
                recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
                //输出接收到的数据 
                Debug.Log("Clent:__" + recvStr + "++" + recvLen);
                //接收客户端心跳包,进行心跳反馈
                if (recvStr == "alive")
                {
                    HeartCheck();
                }
                else if (recvStr == "done")
                {
                    //当接收到的信息为done时,判断接收到的图片包数量是否够,不够就发送未收到的包的标识号,让客户端再发送一下
                    CheckPackage();
                }
                else if (recvLen > 18) //图片包头为29个字节
                {
                    // print("这是图片");
                    //合并发来的图片
                    ConmbineString(recvStr);
                }
            }
        }
    }

    /// <summary>
    /// 当接收到客户端发送的done消息后,判断接收到的图片包是否完整
    /// </summary>
    private void CheckPackage()
    {
        //未发送包的标识号
        string reSendPackageindex;
        reSendPackageindex = null;
        if (doneIndex.Count <= 0)
        {
            print("接收成功");
            for (int i = 0; i < newImageDic.Count; i++)
            {
                if (newImageDic.TryGetValue(i, out dicStr))
                {
                    newCombineStr = newCombineStr + dicStr;
                }
            }
            //置0置空
            isSendImage = true;
            newImageCount = 0;
            newStrIndex = 0;
            isFirst = true;
            newImageDic.Clear();
            doneIndex.Clear();
        }
        else
        {
            print("接收失败,重新请求");
            txt_ConnectState.text = "接收失败,重新请求";
            //判断哪些包没有收到
            for (int i = 0; i < doneIndex.Count; i++)
            {
                reSendPackageindex = doneIndex[i] + "_" + reSendPackageindex;
            }

            SocketSend(reSendPackageindex);
            print("请求发送未成功包");
            txt_ConnectState.text = "请求发送未成功包";
        }
    }

    /// <summary>
    /// 将分包发来的消息合成一个包
    /// </summary>
    /// <param name="perStr"></param>
    private void ConmbineString(string perStr)
    {
        //0.图片名字(21字节)--1.包的长度(1000为起始点,4字节)--2.包的下标(1000为起始点4个字节)--3.包的内容
        //分割字符串 "_"
        string[] strs = perStr.Split('_');
        //名字
        newImageName = strs[0];
        newImageCount = int.Parse(strs[1]) - 1000;
        newStrIndex = int.Parse(strs[2]) - 1000;
        newImageMessage = strs[3];

        if (isFirst)
        {
            oldImageName = newImageName;
            isFirst = false;
            newCombineStr = null;
            //将将要收到的包的标识号存进集合里边,每接收到对应的数据就移除该标识号
            for (int i = 0; i < newImageCount; i++)
            {
                doneIndex.Add(i);
            }
        }

        if (newImageName == oldImageName)
        {
            // print(newImageCount);
            if (!newImageDic.ContainsKey(newStrIndex))
            {
                //每接收到对应的数据就移除该标识号
                try
                {
                    doneIndex.Remove(newStrIndex);
                }
                catch
                {
                    print("数据传输失败");
                    txt_ConnectState.text = "数据传输失败";
                }

                newImageDic.Add(newStrIndex, newImageMessage);
            }
        }
    }

    /// <summary>
    /// 检测心跳包
    /// </summary>
    private void HeartCheck()
    {
        isStartCheck = true;
        timerInterval = 0f;
        SocketSend("keeping");
        print("连接正常");
        //txt_ConnectState.text = "连接正常";
    }

    /// <summary>
    /// 发来的字节包括:图片的字节长度(前四个字节)和图片字节
    /// 得到发来的字节中图片字节长度和图片字节数组
    /// </summary>
    private void ParseByteArr(string receStr)
    {
        byte[] bytes = Convert.FromBase64String(receStr);
        string timestamp = GetTimeStamp().ToString();
        string filename = Application.streamingAssetsPath+timestamp + ".jpg";
        File.WriteAllBytes(filename, bytes);//写入所有的字节

        Texture2D tex2D = new Texture2D(100, 100);//创建Texture2D贴图:宽100,高100
        tex2D.LoadImage(bytes);//根据字节数组加载图片

        if (UDPserverEvent != null)
        {
            UDPserverEvent(tex2D);//订阅这个事件
        }
    }

    /// <summary>
    /// 关闭Socket连接
    /// </summary>
    private void SocketQuit()
    {
        //关闭线程
        if(connectThread!=null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //关闭socket
        if (socket != null)
        {
            try
            {
                socket.Close();
            }
            catch
            {

            }
        }

        Debug.LogWarning("local:断开连接");
        txt_ConnectState.text = "断开连接";
    }

    /// <summary>
    /// 获取事件跨度(用秒和毫秒来表示)
    /// </summary>
    /// <param name="bflag"></param>
    /// <returns></returns>
    public static long GetTimeStamp(bool bflag = true)
    {
        //TimeSpan表示时间间隔;DataTime.UtcNow表示计算机上的当前日期
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        //long表示64位整数,int表示32位整数 short表示16位整数,
        long ret;
        if (bflag)
            ret = Convert.ToInt64(ts.TotalSeconds);//用秒来表示,强转为64位整数
        else
            ret = Convert.ToInt64(ts.TotalMilliseconds);//用毫秒来表示,强转为64位整数
        return ret;
    }
}
using UnityEngine;
using UnityEngine.UI;

public class LoadImageFromClient : MonoBehaviour
{
    public RawImage newImage;

    void Start()
    {
        UDPServer.instance.UDPserverEvent += ReceiveByteFromUDPServer;//监听事件:从服务端接收字节数组
    }

    void OnDisable()
    {
        UDPServer.instance.UDPserverEvent -= ReceiveByteFromUDPServer;//移除监听
    }

    /// <summary>
    /// 发来的字节包括:图片的字节长度(前四个字节)和图片字节
    /// 得到发来的字节中图片字节长度和图片字节数组
    /// </summary>
    void ReceiveByteFromUDPServer(Texture2D newTexture)
    {
        newImage.gameObject.SetActive(true);
        newImage.texture = newTexture;
        Invoke("SetDefultImage", 5f);
    }

    /// <summary>
    /// 图片置空
    /// </summary>
    void SetDefultImage()
    {
        newImage.texture = null;
    }
}

示例二源码链接:https://pan.baidu.com/s/1DYG1d3DGJtOIzhQ7dqyNTA
提取码:5s9c

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林枫依依

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值