You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
12 KiB
362 lines
12 KiB
1 month ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using System.Security.Cryptography;
|
||
|
using Google.Protobuf.WellKnownTypes;
|
||
|
using Sog;
|
||
|
|
||
|
namespace FileTransDataObject
|
||
|
{
|
||
|
// todo 边接收边保存, 可以做断点续传, 否则一次性传送上G的大文件太占内存了
|
||
|
public class FileRecvMgr
|
||
|
{
|
||
|
public static readonly int TIME_OUT = 30000;
|
||
|
|
||
|
// 请求文件内容
|
||
|
public delegate void SendFileContentReq(FileRecvNode recvNode, FileRecvData fileData);
|
||
|
|
||
|
public SendFileContentReq _SendFileContentReq;
|
||
|
|
||
|
// 文件传送结果通知
|
||
|
public delegate void SendFileRecvStateReq(FileRecvNode recvNode);
|
||
|
|
||
|
public SendFileRecvStateReq _SendFileRecvStateReq;
|
||
|
|
||
|
|
||
|
// 需要拉取文件数据的节点信息
|
||
|
public Dictionary<string, FileRecvNode> recvHosts;
|
||
|
|
||
|
private int finishHostNum;
|
||
|
|
||
|
|
||
|
public FileRecvMgr()
|
||
|
{
|
||
|
recvHosts = new Dictionary<string, FileRecvNode>();
|
||
|
}
|
||
|
|
||
|
public void Clear()
|
||
|
{
|
||
|
recvHosts.Clear();
|
||
|
finishHostNum = 0;
|
||
|
}
|
||
|
|
||
|
public bool IsFinish()
|
||
|
{
|
||
|
if (finishHostNum == recvHosts.Count)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
finishHostNum = recvHosts.Values.Count(h => h.IsFinish);
|
||
|
return finishHostNum == recvHosts.Count;
|
||
|
}
|
||
|
|
||
|
// 网络正常时RecvFile由res msg触发, 发生网络中断后才由Tick负责retry, 所以tick频率不用太高
|
||
|
public void Tick(long nowMs)
|
||
|
{
|
||
|
foreach (FileRecvNode recvNode in recvHosts.Values)
|
||
|
{
|
||
|
if (recvNode.state == 2)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// 连续多条消息发出后没有收到应答, 超时结束
|
||
|
if (recvNode.lastReqTime >= recvNode.lastResTime + TIME_OUT)
|
||
|
{
|
||
|
recvNode.state = 2;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (nowMs >= recvNode.lastReqTime + 3000)
|
||
|
{
|
||
|
RecvFile(recvNode);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public FileRecvNode GetRecvNode(string name)
|
||
|
{
|
||
|
recvHosts.TryGetValue(name, out FileRecvNode node);
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
public void RemoveRecvNode(string name)
|
||
|
{
|
||
|
recvHosts.Remove(name);
|
||
|
}
|
||
|
|
||
|
private bool IsFileExist(string filePath, string fileMd5)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (! File.Exists(filePath))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
using (var md5 = MD5.Create())
|
||
|
{
|
||
|
using (var stream = File.OpenRead(filePath))
|
||
|
{
|
||
|
var fMd5 = BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
|
||
|
if (fMd5 == fileMd5)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
TraceLog.Exception(e);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
private void RecvFile(FileRecvNode recvNode)
|
||
|
{
|
||
|
if (recvNode.state == 0)
|
||
|
{
|
||
|
FileRecvData fileData = recvNode.fileList.FirstOrDefault(f => f.recvState == 0);
|
||
|
if (fileData != null)
|
||
|
{
|
||
|
recvNode.lastReqTime = AppTime.ServerAppTime.GetTime();
|
||
|
_SendFileContentReq(recvNode, fileData);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// 没有待接收文件, 接收结束
|
||
|
recvNode.state = 1;
|
||
|
}
|
||
|
|
||
|
if (recvNode.state == 1)
|
||
|
{
|
||
|
RecvEnd(recvNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void RecvEnd(FileRecvNode recvNode)
|
||
|
{
|
||
|
recvNode.lastReqTime = AppTime.ServerAppTime.GetTime();
|
||
|
_SendFileRecvStateReq(recvNode);
|
||
|
}
|
||
|
|
||
|
// 某一个文件接收完成写入磁盘
|
||
|
private int WriteFile(FileRecvData fileData)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
using (MD5 md5 = MD5.Create())
|
||
|
{
|
||
|
//如果目录不存在则创建
|
||
|
if (Directory.Exists(fileData.filePath) == false)
|
||
|
{
|
||
|
Directory.CreateDirectory(fileData.filePath);
|
||
|
}
|
||
|
|
||
|
var file = fileData.filePath + OSUtils.GetFilePathSeparator() + fileData.fileName;
|
||
|
File.WriteAllBytes(file, fileData.recvBuffer);
|
||
|
File.SetLastWriteTimeUtc(file, fileData.fileTime);
|
||
|
|
||
|
// 计算文件MD5
|
||
|
using (FileStream stream = File.OpenRead(file))
|
||
|
{
|
||
|
string fMd5 = BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "")
|
||
|
.ToLowerInvariant();
|
||
|
if (fMd5 == fileData.fileMd5)
|
||
|
{
|
||
|
TraceLog.Debug("FileRecvMgr.WriteFile file {0} md5 {1} recvSize {2} fileSize {3} save succ",
|
||
|
fileData.fileName, fileData.fileMd5, fileData.recvSize, fileData.fileSize);
|
||
|
|
||
|
fileData.recvState = 1;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
TraceLog.Exception(e);
|
||
|
}
|
||
|
|
||
|
TraceLog.Error("FileRecvMgr.WriteFile file {0} md5 {1} recvSize {2} fileSize {3} save fail",
|
||
|
fileData.fileName, fileData.fileMd5, fileData.recvSize, fileData.fileSize);
|
||
|
|
||
|
fileData.recvState = -1;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
public int OnTransFileNotify(SMTransFileNotify notify)
|
||
|
{
|
||
|
var recvNode = GetRecvNode(notify.HostName);
|
||
|
if (recvNode != null)
|
||
|
{
|
||
|
RemoveRecvNode(notify.HostName);
|
||
|
}
|
||
|
|
||
|
// 每次收到新通知时都重新生成接收节点
|
||
|
recvNode = new FileRecvNode
|
||
|
{
|
||
|
senderHost = notify.HostName,
|
||
|
transSeq = notify.TransSeq,
|
||
|
lastResTime = AppTime.ServerAppTime.GetTime()
|
||
|
};
|
||
|
recvHosts.Add(notify.HostName, recvNode);
|
||
|
|
||
|
foreach (FileAttr attr in notify.FileList)
|
||
|
{
|
||
|
var file = new FileRecvData
|
||
|
{
|
||
|
fileName = attr.FileName,
|
||
|
filePath = attr.FilePath,
|
||
|
fileMd5 = attr.FileMd5,
|
||
|
fileSize = attr.FileSize,
|
||
|
fileTime = DateTime.FromFileTimeUtc(attr.FileTime)
|
||
|
};
|
||
|
|
||
|
var filePath = attr.FilePath + "/" + attr.FileName;
|
||
|
if (IsFileExist(filePath, attr.FileMd5))
|
||
|
{
|
||
|
TraceLog.Debug("FileRecvMgr.OnTransFileNotify file {0} md5 {1} already exist", filePath,
|
||
|
attr.FileMd5);
|
||
|
file.recvState = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
file.recvBuffer = new byte[file.fileSize];
|
||
|
}
|
||
|
|
||
|
recvNode.fileList.Add(file);
|
||
|
}
|
||
|
|
||
|
RecvFile(recvNode);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// 20001; // 文件传送结束
|
||
|
// 20002; // 文件不存在
|
||
|
// 20003; // 偏移值不合法
|
||
|
public int OnFileContentRes(SMFileContentRes res)
|
||
|
{
|
||
|
FileRecvNode recvNode = GetRecvNode(res.HostName);
|
||
|
// 没有向该host请求过文件
|
||
|
if (recvNode == null)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes host {0} no recv node", res.HostName);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
recvNode.lastResTime = AppTime.ServerAppTime.GetTime();
|
||
|
|
||
|
// 不是同一轮请求
|
||
|
if (recvNode.transSeq != res.TransSeq)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes transSeq local {0} res {1} not equal"
|
||
|
, recvNode.transSeq, res.TransSeq);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// 没有请求该文件
|
||
|
FileRecvData fileData = recvNode.fileList.Find(f => f.fileName == res.FileName && f.fileMd5 == res.FileMd5);
|
||
|
if (fileData == null)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes file {0} md5 {1} not exist", res.FileName, res.FileMd5);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// 文件已完成, 或失败
|
||
|
if (fileData.recvState != 0)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes file {0} md5 {1} recvSize {2} already finish",
|
||
|
fileData.fileName, fileData.fileMd5, fileData.recvSize);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// 请求文件出错, 接收失败
|
||
|
if (res.Ret != 0)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes host {0} ret {1}", recvNode.senderHost, res.Ret);
|
||
|
|
||
|
fileData.recvState = -1;
|
||
|
return res.Ret;
|
||
|
}
|
||
|
|
||
|
// 收到的文件偏移和请求不一致, 重复发包? 不用管, tick时会retry
|
||
|
if (res.ContentOffset != fileData.ReqFileOffset)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes file {0} md5 {1} offset not equal res {2} req {3}",
|
||
|
res.FileName, res.FileMd5, res.ContentOffset, fileData.ReqFileOffset);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// 数据出错, 接收失败
|
||
|
if (fileData.recvSize + res.Content.Length > fileData.recvBuffer.Length)
|
||
|
{
|
||
|
TraceLog.Error("FileRecvMgr.OnFileContentRes file {0} md5 {1} not enough buffer, need {2} total {3}",
|
||
|
res.FileName, res.FileMd5, fileData.recvSize + res.Content.Length, fileData.recvBuffer.Length);
|
||
|
|
||
|
fileData.recvState = -1;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
res.Content.CopyTo(fileData.recvBuffer, fileData.recvSize);
|
||
|
fileData.recvSize += res.Content.Length;
|
||
|
|
||
|
TraceLog.Debug("FileRecvMgr.OnFileContentRes file {0} md5 {1} data length {2} recv size {3}",
|
||
|
res.FileName, res.FileMd5, res.Content.Length, fileData.recvSize);
|
||
|
|
||
|
if (fileData.recvSize == fileData.fileSize)
|
||
|
{
|
||
|
WriteFile(fileData);
|
||
|
}
|
||
|
|
||
|
// 继续请求数据或上报结果
|
||
|
RecvFile(recvNode);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
public void OnFileRecvStateRes(SMFileRecvStateRes res)
|
||
|
{
|
||
|
var recvNode = GetRecvNode(res.HostName);
|
||
|
if (recvNode != null && recvNode.transSeq == res.TransSeq)
|
||
|
{
|
||
|
recvNode.state = 2;
|
||
|
recvNode.lastResTime = AppTime.ServerAppTime.GetTime();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CancelTrans(string hostName)
|
||
|
{
|
||
|
var recvNode = GetRecvNode(hostName);
|
||
|
if (recvNode == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
recvNode.lastResTime = AppTime.ServerAppTime.GetTime();
|
||
|
|
||
|
int oldState = recvNode.state;
|
||
|
if (recvNode.state == 0)
|
||
|
{
|
||
|
recvNode.state = 1;
|
||
|
}
|
||
|
|
||
|
TraceLog.Debug("FileRecvMgr.CancelTrans host {0} old state {1} new state {2}"
|
||
|
, recvNode.senderHost, oldState, recvNode.state);
|
||
|
|
||
|
if (recvNode.state == 1)
|
||
|
{
|
||
|
RecvEnd(recvNode);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|