unity使用Socket进行文件上传

一、制作思路

1.首先大家要先了解一下、socket一些接口的使用

https://blog.csdn.net/qq_42345116/article/details/122185529

2.思路

2.1首先 我们知道Socket之间传输的数据是Byte数组,所以我们客户端要上传的文件必须是byte数组文件

使用这个方法将文件转换为byte[]数组

#region 将文件转换成byte[] 数组

    /// <summary>
    /// 将文件转换成byte[] 数组
    /// </summary>
    /// <param name="fileUrl">文件路径文件名称</param>
    /// <returns>byte[]</returns>
    protected byte[] GetFileData(string fileUrl)
    {
        FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read);
        lock (fs)
        {
            try
            {
                byte[] buffur = new byte[fs.Length];
                fs.Read(buffur, 0, (int)fs.Length);

                return buffur;
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                if (fs != null)
                {
                    //关闭资源
                    fs.Close();
                }
            }
        }
    }
    #endregion

2.2相应的服务器接收到客户端的byte数组文件后要将其反转成文件进行保存

    #region 将byte数组转换为文件并保存到指定地址
    /// <summary>
    /// 将byte数组转换为文件并保存到指定地址
    /// </summary>
    /// <param name="buff">byte数组</param>
    /// <param name="savepath">保存地址</param>
    public static void Bytes2File(byte[] buff, string savepath)
    {
        if (File.Exists(savepath))
        {
            File.Delete(savepath);
        }

        FileStream fs = new FileStream(savepath, FileMode.CreateNew);
        BinaryWriter bw = new BinaryWriter(fs);
        bw.Write(buff, 0, buff.Length);
        bw.Close();
        fs.Close();
    }
    #endregion

2.3但是保存文件需要地址(F:/)+文件名称(XXX文件名)+文件类型 (.png)而我们客户端传过来的byte数组没有这些信息,所以这里我用了Json数据进行组合传输

//1.Json数据类
[Serializable]
public class JsonData
{
    public string flieName;      //文件名称(文件名称)
    public byte[] fileData;      //文件byte[]数据
}

//2.获取文件名称 类型
 string[] splitData=path.Slip('/'); //将文件路径拆分 为了获取文件名和文件类型
 jsonData = new JsonData()
 {
    flieName = splitData[splitData.Length - 1], //拆分的最后一个一定是文件名.文件类型
    fileData = GetFileData(filePath) //将文件转换为byte[]
 };

string data=JsonMapper.ToJson(jsonData);//转换成json
byte[] sendData = Encoding.UTF8.GetBytes(data)//将string转换成byte数组

这样我们就获取到一个json的byte数组数据。

2.4这还没完,要知道在网络中数据是用包来传输的,每个包能存放的byte数量只有那么多,超出了就要分包发送,文件总是很大的,肯定要分包发送,但是我们服务器没有办法判断是否已经接收完客户端的所有文件数据,所以我们客户端需要在服务器接收数据前先发送一个byte数组(这个数组信息代表了文件的总大小),服务器知道了文件大小以后就开始整合数据,直到接收数据大小等于发送的数据大小后,开始转换byte数据

//1.接上文sendData就是我们要发送的json byte数组,我们获取他的长度通过Send发送给服务器
clientSocket.Send(Encoding.UTF8.GetBytes(sendData.Length.ToString()));

服务器接收处理

//2.这里是服务器要处理

  private byte[] result = new byte[1024]; //1.存入的byte值 最大数量1024
    private byte[] subpackageData; //1.分包数据存入信息

    int dataLength;     //要接收信息总的长度
    bool isEndData = false; //是否接收到信息总长度
    //开启线程接收数据 (将Socket作为值传入)
    private void ReceiveMessage(object clientSocket)
    {
        Socket myClientSocket = (Socket)clientSocket; //2.转换传入的客户端Socket
        while (true)
        {
            try
            {
                //接收数据  
                int receiveNumber = myClientSocket.Receive(result); //3.将客户端得到的byte值写入

                //没有接收信息总长度
                if (!isEndData)
                {
                    //接收
                    dataLength = int.Parse(Encoding.UTF8.GetString(result));
                    subpackageData = new byte[0]; //初始化缓冲区
                    Debug.Log("1.接收到信息的总长度: " + dataLength);
                    isEndData = true;
                }
                else
                {
                    //有数据 并且 接收到信息长度后执行
                    if (receiveNumber > 0 && isEndData)
                    {
                        //1.整合数据
                        byte[] newResult = new byte[(subpackageData.Length + receiveNumber)];
                        Array.Copy(subpackageData, 0, newResult, 0, subpackageData.Length); //将上一次分包数据赋值给容器
                        Array.Copy(result, 0, newResult, subpackageData.Length, receiveNumber); //将当前收到的数据赋值给容器

                        subpackageData = newResult;
                        Debug.Log("整合后数据长度: " + subpackageData.Length);


                        //整合的数据超出 或等于数据
                        if (subpackageData.Length >= dataLength)
                        {
                            isEndData = false; //设置没有接收到信息总长度
                            //数据解析
                            string data = Encoding.UTF8.GetString(subpackageData);
                            Debug.Log(data);
                            DisposeData(data);

                            myClientSocket.Send(Encoding.UTF8.GetBytes("文件转换成功" + DateTime.Now));
                        }
                    }
                    else
                    {
                        Debug.Log("client: " + ((IPEndPoint)myClientSocket.RemoteEndPoint).Address.ToString() + "断开连接");
                        threadDic[((IPEndPoint)myClientSocket.RemoteEndPoint).Address.ToString()].Abort(); //清除线程
                    }
                }

            }
            catch (Exception ex)
            {
                //myClientSocket.Shutdown(SocketShutdown.Both); //出现错误 关闭Socket
                Debug.Log(" 错误信息" + ex); //打印错误信息
                break;
            }
        }
    }

二、服务器最终代码

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

//Json数据
[Serializable]
public class JsonData
{
    public string operationType; //服务器事件类型(选择服务器要执行的方法)
    public string clientIP;      //客户端本地ip
    public string flieName;      //文件名称(文件名称)
    public byte[] fileData;      //文件byte[]数据
}

public class SocketServer : MonoBehaviour
{
    private string myip;
    private int myPort;   //端口  
    string saveFilePath;  //保存地址
    JsonData jsonData;

    static Socket serverSocket;
    Thread myThread;
    Dictionary<string, Thread> threadDic = new Dictionary<string, Thread>();//存储线程,程序结束后关闭线程
    private void Start()
    {
        myip = ConfigFile.LoadString("Server");
        myPort = int.Parse(ConfigFile.LoadString("port"));
        saveFilePath = Application.streamingAssetsPath + "/"; //文件保存路径


        //服务器IP地址  ,127.0.0.1 为本机IP地址
        IPAddress ip = IPAddress.Parse(myip);
        //IPAddress ip = IPAddress.Any; //本机地址
        Debug.Log(ip.ToString());
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        IPEndPoint iPEndPoint = new IPEndPoint(ip, myPort);
        serverSocket.Bind(iPEndPoint);  //绑定IP地址:端口  
        serverSocket.Listen(10);        //最多10个连接请求  
                                        //Console.WriteLine("creat service {0} success",
                                        //    serverSocket.LocalEndPoint.ToString());

        myThread = new Thread(ListenClientConnect);
        myThread.Start();
        Debug.Log("服务器启动...........");

    }

    // 监听客户端是否连接  
    private void ListenClientConnect()
    {
        while (true)
        {
            Socket clientSocket = serverSocket.Accept(); //1.创建一个Socket 接收客户端发来的连接请求 没有时堵塞
            string clientIp = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();

            Debug.Log("客户端连接成功:" + clientIp);
            clientSocket.Send(Encoding.UTF8.GetBytes("连接成功")); //2.向客户端发送 连接成功 消息
            Thread receiveThread = new Thread(ReceiveMessage); //3.为已经连接的客户端创建一个线程 此线程用来处理客户端发送的消息
            receiveThread.Start(clientSocket); //4.开启线程

            //将已经连接的客户端添加到字典中
            if (!threadDic.ContainsKey(clientIp))
            {
                threadDic.Add(clientIp, receiveThread);
            }
        }
    }


    private byte[] result = new byte[1024]; //1.存入的byte值 最大数量1024
    private byte[] subpackageData; //1.分包数据存入信息

    int dataLength;     //要接收信息总的长度
    bool isEndData = false; //是否接收到信息总长度
    //开启线程接收数据 (将Socket作为值传入)
    private void ReceiveMessage(object clientSocket)
    {
        Socket myClientSocket = (Socket)clientSocket; //2.转换传入的客户端Socket
        while (true)
        {
            try
            {
                result = new byte[1024]; //接收前要清空 否则写入时会保留后面的数值
                //接收数据  
                int receiveNumber = myClientSocket.Receive(result); //3.将客户端得到的byte值写入

                //没有接收信息总长度
                if (!isEndData)
                {
                    //接收
                    dataLength = int.Parse(Encoding.UTF8.GetString(result));
                    subpackageData = new byte[0]; //初始化缓冲区
                    Debug.Log("1.接收到信息的总长度: " + dataLength);
                    isEndData = true;
                }
                else
                {
                    //有数据 并且 接收到信息长度后执行
                    if (receiveNumber > 0 && isEndData)
                    {
                        //1.整合数据
                        byte[] newResult = new byte[(subpackageData.Length + receiveNumber)];
                        Array.Copy(subpackageData, 0, newResult, 0, subpackageData.Length); //将上一次分包数据赋值给容器
                        Array.Copy(result, 0, newResult, subpackageData.Length, receiveNumber); //将当前收到的数据赋值给容器

                        subpackageData = newResult;
                        Debug.Log("整合后数据长度: " + subpackageData.Length);


                        //整合的数据超出 或等于数据
                        if (subpackageData.Length >= dataLength)
                        {
                            isEndData = false; //设置没有接收到信息总长度
                            //数据解析
                            string data = Encoding.UTF8.GetString(subpackageData);
                            Debug.Log(data);
                            DisposeData(data);

                            myClientSocket.Send(Encoding.UTF8.GetBytes("文件转换成功" + DateTime.Now));
                        }
                    }
                    else
                    {
                        Debug.Log("client: " + ((IPEndPoint)myClientSocket.RemoteEndPoint).Address.ToString() + "断开连接");
                        threadDic[((IPEndPoint)myClientSocket.RemoteEndPoint).Address.ToString()].Abort(); //清除线程
                    }
                }

            }
            catch (Exception ex)
            {
                //myClientSocket.Shutdown(SocketShutdown.Both); //出现错误 关闭Socket
                Debug.Log(" 错误信息" + ex); //打印错误信息
                break;
            }
        }
    }

    #region 数据处理
    private void DisposeData(string data)
    {
        //UTF8Encoding m_utf8 = new UTF8Encoding(false); //这是不带有BOM的UTF-8 使用带有BOM的UTF-8转换Json容易失败
        jsonData = JsonMapper.ToObject<JsonData>(data);

        string savePath = saveFilePath + jsonData.flieName;
        Debug.Log(savePath);
        //上传附件 判断是否存在
        if (File.Exists(savePath))
        {
            //文件存在 重命名新文件
            savePath = saveFilePath + "(Colon)"+jsonData.flieName;
        }

        Bytes2File(jsonData.fileData, savePath); //将byte数据转换为文件
    }
    #endregion

    #region 将byte数组转换为文件并保存到指定地址
    /// <summary>
    /// 将byte数组转换为文件并保存到指定地址
    /// </summary>
    /// <param name="buff">byte数组</param>
    /// <param name="savepath">保存地址</param>
    public static void Bytes2File(byte[] buff, string savepath)
    {
        if (File.Exists(savepath))
        {
            File.Delete(savepath);
        }

        FileStream fs = new FileStream(savepath, FileMode.CreateNew);
        BinaryWriter bw = new BinaryWriter(fs);
        bw.Write(buff, 0, buff.Length);
        bw.Close();
        fs.Close();
    }
    #endregion

    void OnApplicationQuit()
    {
        //结束线程必须关闭 否则下次开启会出现错误 (如果出现的话 只能重启unity了)
        myThread.Abort();

        //关闭开启的线程
        foreach (string item in threadDic.Keys)
        {
            Debug.Log(item);//de.Key对应于key/value键值对key
            //item.Value.GetType()
            threadDic[item].Abort();
        }
    }

}

三、客户端最终代码

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

//Json数据
[Serializable]
public class JsonData
{
    public string operationType = string.Empty; //服务器事件类型(选择服务器要执行的方法)
    public string clientIP = string.Empty;      //客户端本地ip
    public string flieName = string.Empty;      //文件名称(文件名称)
    public byte[] fileData = new byte[] { };    //文件byte[]数据
}

public class SocketClient : MonoBehaviour
{
    private string myip;
    private int myPort;   //端口  
    JsonData jsonData;    //整合的json数据

    public Button sendBtn;//发送
    public InputField path;//信息
    Socket clientSocket;

    byte[] sendData; //要发送的数据
    private static byte[] result = new byte[1024]; //接收服务器的数据

    string filePath;
    void Start()
    {
        myip = ConfigFile.LoadString("Server");
        myPort = int.Parse(ConfigFile.LoadString("port"));

        //按钮监听
        sendBtn.onClick.AddListener(delegate {

            //判断 路径不为空 并且 文件存在
            if (!path.text.Equals(string.Empty)&& File.Exists(path.text)) 
            {
               
                //判断 文件是否存在\反斜杠
                filePath = path.text.Contains("\\") ? path.text.Replace('\\', '/') : path.text;
                Debug.Log(filePath);
                try
                {
                    //获取文件名称 类型
                    FieldSplit(filePath, '/');
                    jsonData = new JsonData()
                    {
                        operationType = "上传附件",
                        clientIP = GetLocalIP.GetIP(GetLocalIP.ADDRESSFAM.IPv4),
                        flieName = splitData[splitData.Length - 1],
                        fileData = GetFileData(filePath) //将文件转换为byte[]
                    };

                    Debug.Log(jsonData.flieName);

                    sendData = Encoding.UTF8.GetBytes(JsonMapper.ToJson(jsonData)); //将json数据转换为byte

                    //1.先发送 数据长度
                    clientSocket.Send(Encoding.UTF8.GetBytes(sendData.Length.ToString()));
                    Debug.Log("byte数量:" + sendData.Length.ToString());
                    //2.发送内容
                    clientSocket.Send(sendData);//传送信息
                }
                catch (Exception ex)
                {
                    Debug.Log("发送失败:" + ex);
                }

            }

        });

        //监听连接的客户端连接
        ListeningClient();

    }

    #region 获取文件名称 类型
    string[] splitData; 
    private void FieldSplit(string field,char _value)
    {
        splitData = field.Split(_value);
    }

    #endregion

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }
    }

    #region 监听连接的客户端连接
    private void ListeningClient() 
    {
        //要连接的服务器IP地址  
        IPAddress ip = IPAddress.Parse(myip);//本地IP地址
        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            clientSocket.Connect(new IPEndPoint(ip, myPort)); //配置服务器IP与端口 ,并且尝试连接
        }
        catch (Exception ex)
        {
            Debug.Log("Connect lose" + ex); //打印连接失败
            return;
        }

        int receiveLength = clientSocket.Receive(result);//接收服务器回复消息,成功则说明已经接通
        Debug.Log("服务器消息长度:" + receiveLength);

        if (receiveLength > 1)
        {
            Debug.Log("接收服务器消息:" + Encoding.UTF8.GetString(result));
        }
    }

    #endregion


    #region 将文件转换成byte[] 数组

    /// <summary>
    /// 将文件转换成byte[] 数组
    /// </summary>
    /// <param name="fileUrl">文件路径文件名称</param>
    /// <returns>byte[]</returns>
    protected byte[] GetFileData(string fileUrl)
    {
        FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read);
        lock (fs)
        {
            try
            {
                byte[] buffur = new byte[fs.Length];
                fs.Read(buffur, 0, (int)fs.Length);

                return buffur;
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                if (fs != null)
                {
                    //关闭资源
                    fs.Close();
                }
            }
        }
    }
    #endregion
}

四、其他脚本:

1.获取本地ip

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

public class GetLocalIP 
{
    #region 获取本地IP地址
    public enum ADDRESSFAM
    {
        IPv4, IPv6
    }

    /// <summary>
    /// 获取本机IP
    /// </summary>
    /// <param name="Addfam">要获取的IP类型</param>
    /// <returns></returns>
    public static string GetIP(ADDRESSFAM Addfam)
    {
        if (Addfam == ADDRESSFAM.IPv6 && !Socket.OSSupportsIPv6)
        {
            return null;
        }

        string output = "";

        foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
        {
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
            NetworkInterfaceType _type1 = NetworkInterfaceType.Wireless80211;
            NetworkInterfaceType _type2 = NetworkInterfaceType.Ethernet;

            if ((item.NetworkInterfaceType == _type1 || item.NetworkInterfaceType == _type2) && item.OperationalStatus == OperationalStatus.Up)
#endif 
            {
                foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
                {
                    //IPv4
                    if (Addfam == ADDRESSFAM.IPv4)
                    {
                        if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
                        {
                            output = ip.Address.ToString();
                        }
                    }

                    //IPv6
                    else if (Addfam == ADDRESSFAM.IPv6)
                    {
                        if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6)
                        {
                            output = ip.Address.ToString();
                        }
                    }
                }
            }
        }
        return output;
    }

    #endregion
}

2.获取xml

using System.Xml.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class ConfigFile
{
    static string path = Application.dataPath + "/StreamingAssets/ConfigFile.xml";

    public static string LoadString(string str)
    {
        XDocument document = XDocument.Load(path);
        //获取到XML的根元素进行操作
        XElement root = document.Root;
        XElement ele = root.Element(str);
        return ele.Value;
    }
}

3.ConfigFile.xml 配置文件 放到StreamingAssets文件夹下

<?xml version="1.0" encoding="utf-8"?>
<Info>

  <!--服务器ip配置-->
  <Server>127.0.0.1</Server>
  <port>9999</port>
</Info>

4.json插件

链接:https://pan.baidu.com/s/14SgSgrK14zdGURfXVjeyjg 
提取码:syq1

最终效果:

客户端:

 服务器:

 上传成功  我们看一下内容是否有问题

客户端发送的文件:

 服务器接收的文件:

内容没问题 成功。。。 

猜你喜欢

转载自blog.csdn.net/qq_42345116/article/details/122249153