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 recvHosts; private int finishHostNum; public FileRecvMgr() { recvHosts = new Dictionary(); } 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); } } } }