untiy 使用UDP发送图片,并将接收到的图片保存到本地

前言:

UDP客户端作为图片发送方,UDP服务端作为图片接收方。

实现功能:

在发送方鼠标左键点击game视图,将本地图片发送到接收方,接收方将收到的图片保存到本地,并将接收到的图片在UI上展示几秒钟。

发送方脚本挂载方式:

udpclient.png

接收方脚本挂载方式:

udpserver.png

客户端代码:

NewUDPClient:

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

public class NewUDPClient : MonoBehaviour {

    public static NewUDPClient instance;
    //服务端的IP
    string UDPClientIP;
    //目标socket 
    Socket socket;
    //服务端 
    EndPoint serverEnd;
    //服务端端口 
    IPEndPoint ipEnd;
    //接收的字符串 
    string recvStr;
    //接收的数据,必须为字节 
    byte[] recvData;
    //发送的数据,必须为字节 
    byte[] sendData;
    //接收的数据长度 
    int recvLen = 0;
    //连接线程
    Thread connectThread;

    bool isClientActive = false;

    //连接服务器时发送的vector3类型
    Vector3 startVec = Vector3.zero;

    bool isStartHeart = false;


    int port;

    //判断是否让客户端重新发送数据包
    bool isReSend = false;
    string reSendStrIndex;

    public delegate void ReSendIndexDeledate(string str);
    public event ReSendIndexDeledate ReSendIndexEvent;


    private void Awake()
    {
        instance = this;
    }

    void Start()
    {
        UDPClientIP = "127.0.0.1";//服务端的IP.自己更改
        port = 9000;
        UDPClientIP = UDPClientIP.Trim();
        isClientActive = true;
        InitSocket(); //在这里初始化
    }

    private void Update()
    {
        if (isStartHeart)
        {
            HeartSend();
        }
        //检测心跳与心跳反馈的间隔时间,
        timerInterval += Time.deltaTime;

        if (timerInterval > 6f)
        {
            print("连接异常");
            timerInterval = 0f;
        }


        if (isReSend)
        {
            print(111);
            if (ReSendIndexEvent!=null)
            {
                ReSendIndexEvent(reSendStrIndex);
                reSendStrIndex = null;
                isReSend = false;
            }
        }
    }

    //初始化 
    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("local:等待连接");
        isStartHeart = true;
        //开始心跳监听
        //客户端发送心跳消息后,计时器开始计时,判断3秒内是否能收到服务端的反馈
        HeartSend();
        //开启一个线程连接,否则主线程卡死 
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }

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

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

    byte[] heartSendData = new byte[1024];

    /// <summary>
    /// 心跳
    /// </summary>
    void HeartSend()
    {
        timerRate += Time.deltaTime;
        if (timerRate > 5f)
        {
            try
            {
                SocketSend("alive");
            }
            catch 
            {
            }
            timerRate = 0f;
        }
    }

    //客户端接收服务器消息
    void SocketReceive()
    {
        //进入接收循环 
        while (isClientActive)
        {
            //对data清零 
            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("server:" + recvStr);

            //心跳回馈
            if (recvStr == "keeping")
            {
                // 当服务端收到客户端发送的alive消息时
                print("连接正常");
                timerInterval = 0;
            }
            else if(recvStr!=null)
            {
                reSendStrIndex = recvStr;
                isReSend = true;
            }
        }
    }

    //连接关闭
    void SocketQuit()
    {
        //最后关闭socket
        if (socket != null)
            socket.Close();
    }
    void OnApplicationQuit()
    {
        isStartHeart = false;
        isClientActive = false;
        SocketQuit();
        Thread.Sleep(25);
    }
}

SendImageScript:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Threading;
public class SendImageScript : MonoBehaviour {
    
    byte[] imagebytes;
    
    string newImageString;
    string path;

    private void Start()
    {
        NewUDPClient.instance.ReSendIndexEvent += GetReSendIndexFromUDPClient;
        path = Application.streamingAssetsPath + "/" + "222.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);
    }


    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
          
            StartCoroutine(SendPicture());
        }
    }

    string picStr;
    IEnumerator SendPicture() {
        UDPSplit(picStr);
        //将图片发送给投影机
       yield return new WaitForSeconds(0.1f);

        for (int i = 0; i < num - 1000; i++)
        {
            if (UDPStringDic.TryGetValue(i, out newImageString))
            {
                NewUDPClient.instance.SocketSend(newImageString);
            }
        }

        yield return new WaitForSeconds(0.1f);
        //发送完成后发一条信息给服务端
        NewUDPClient.instance.SocketSend("done");
    }

    string[] reSendNum;
    int newindex;
    void GetReSendIndexFromUDPClient(string str)
    {
        reSendNum = str.Split('_');

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

    }



    Dictionary<int, string> UDPStringDic = new Dictionary<int, string>();
    int index = 0;
    int maxIndex = 1000;
    string newstr;
    int num;
    
    void UDPSplit(string str)
    {
        index = 0;
        maxIndex = 1000;
        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;
        }
    }


    public static long GetTimeStamp(bool bflag = true)
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        long ret;
        if (bflag)
            ret = Convert.ToInt64(ts.TotalSeconds);
        else
            ret = Convert.ToInt64(ts.TotalMilliseconds);
        return ret;
    }

    private void OnDisable()
    {
        NewUDPClient.instance.ReSendIndexEvent -= GetReSendIndexFromUDPClient;
    }
}

服务端代码

NewUDPServer:

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

public class NewUDPServer : MonoBehaviour {

    public static NewUDPServer instance;
    Socket socket; //目标socket
    EndPoint clientEnd; //客户端
    IPEndPoint ipEnd; //侦听端口
    [HideInInspector]
    public string recvStr; //接收的字符串
    string sendStr; //发送的字符串
    byte[] recvData; //接收的数据,必须为字节
    byte[] sendData = new byte[1024]; //发送的数据,必须为字节
    int recvLen; //接收的数据长度
    Thread connectThread; //连接线程
    [HideInInspector]
    public bool isStartSend = false;
    int port;

    bool isSendImage;
    public delegate void UDPServerDeledate(Texture2D byths);
    public event UDPServerDeledate UDPserverEvent;

    //接收到的图片字节数组的图片字节长度
    int imageLength;

    string imageStr;

    /// <summary>
    /// 初始化
    /// </summary>
    void InitSocket()
    {
        //定义侦听端口,侦听任何IP 
        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:等待连接数据");
        //开启一个线程连接
        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);
    }

    bool isServerActive = false;

    /// <summary>
    /// 服务器接收来自客户端的消息
    /// </summary>
    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);
                }
            }
        }
    }

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

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

    
    string newConbineStr;
    string newImageName;
    int newImageCount = 0;
    int newStrIndex = 0;
    string newImageMessage;
    //判断是否是第一次接受消息
    bool isFirst = true;
    string oldImageName;
    Dictionary<int, string> newImageDic = new Dictionary<int, string>();
    List<int> doneIndex = new List<int>();

    string dicStr;
    //将分包发来的消息合成一个包
    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;
            newConbineStr = 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("数据传输失败");
                }

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

    float timerInterval = 0;
    bool isStartCheck = false;

    void HeartCheck()
    {
        isStartCheck = true;
        timerInterval = 0f;
        SocketSend("keeping");
        print("连接正常");
    }

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

        if (isSendImage)
        {
            ParseBYTeArr(newConbineStr);
            newConbineStr = null;
            isSendImage = false;
        }
    }

    /// <summary>
    /// 发来的字节包括:图片的字节长度(前四个字节)和图片字节
    /// 得到发来的字节中图片字节长度和图片字节数组
    /// </summary>
    void ParseBYTeArr(string receStr)
    {
        byte[] bytes = Convert.FromBase64String(receStr);

        string timestamp = GetTimeStamp().ToString();
        string filename = Application.streamingAssetsPath + "/Imags/"+timestamp + ".jpg";
        File.WriteAllBytes(filename, bytes);

        Texture2D tex2D = new Texture2D(100, 100);
        tex2D.LoadImage(bytes);

        if (UDPserverEvent != null)
        {
            UDPserverEvent(tex2D);
        }
    }


    //连接关闭
    void SocketQuit()
    {
        //最后关闭socket
        if (socket != null)
        {
            try
            {
                socket.Close();
            }
            catch
            {

            }
        }

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

    private void Awake()
    {
        instance = this;
    }

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

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

    public static long GetTimeStamp(bool bflag = true)
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        long ret;
        if (bflag)
            ret = Convert.ToInt64(ts.TotalSeconds);
        else
            ret = Convert.ToInt64(ts.TotalMilliseconds);
        return ret;
    }
}

LoadImageFromClient:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoadImageFromClient : MonoBehaviour {

    public RawImage newImage;
    public Transform showPanel;

    void Start()
    {
        NewUDPServer.instance.UDPserverEvent += ReceiveByteFromUDPServer;
    }

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

    void SetDefultImage() {
        newImage.texture = null;
    }

    void OnDisable()
    {
        NewUDPServer.instance.UDPserverEvent -= ReceiveByteFromUDPServer;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容