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.
 
 
 
 
 
 

361 lines
12 KiB

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);
}
}
}
}