一、制作思路
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
{
//接收数据
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
最终效果:
客户端:
?服务器:
?上传成功? 我们看一下内容是否有问题
客户端发送的文件:
?服务器接收的文件:
内容没问题 成功。。。?
|