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.
896 lines
34 KiB
896 lines
34 KiB
4 years ago
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using UnityEngine;
|
||
|
using AX.Network.Common;
|
||
|
using AX.Network.Protocols;
|
||
|
using AX.Serialization;
|
||
|
|
||
|
namespace AX.NetworkSystem
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示传输的文件信息。
|
||
|
/// </summary>
|
||
|
public struct TransferedFileInfo
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示传输的文件名。只包含文件名和扩展名,不包含路径。
|
||
|
/// </summary>
|
||
|
/// <remarks>文件名必须保证唯一。</remarks>
|
||
|
public string Filename;
|
||
|
/// <summary>
|
||
|
/// 表示传输的文件大小,最大支持 2G。
|
||
|
/// </summary>
|
||
|
public int FileSize;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件附加信息。
|
||
|
/// </summary>
|
||
|
public ArraySegment<byte> Tag;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示传输的文件块信息。
|
||
|
/// </summary>
|
||
|
public struct TransferedChunkInfo
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示文件块对应的文件。
|
||
|
/// </summary>
|
||
|
public string Filename;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件块总数。
|
||
|
/// </summary>
|
||
|
public int ChunkCount;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件块的 ID。
|
||
|
/// </summary>
|
||
|
public int ChunkId;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件块划分的标准。
|
||
|
/// </summary>
|
||
|
public int ChunkSize;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件块的数据。
|
||
|
/// </summary>
|
||
|
public ArraySegment<byte> Data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示传输文件块时的回复。
|
||
|
/// </summary>
|
||
|
public struct TransferedChunkInfoReply
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示文件名。
|
||
|
/// </summary>
|
||
|
public string Filename;
|
||
|
/// <summary>
|
||
|
/// 表示文件块 Id。
|
||
|
/// </summary>
|
||
|
public int ChunkId;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示传输文件块时的请求。
|
||
|
/// </summary>
|
||
|
public struct TransferedChunkInfoRequest
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示文件名。
|
||
|
/// </summary>
|
||
|
public string Filename;
|
||
|
/// <summary>
|
||
|
/// 表示文件块 Id。
|
||
|
/// </summary>
|
||
|
public int ChunkId;
|
||
|
/// <summary>
|
||
|
/// 表示文件块划分的标准。
|
||
|
/// </summary>
|
||
|
public int ChunkSize;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示错误消息。
|
||
|
/// </summary>
|
||
|
public struct ErrorInfo
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示出错的文件名。
|
||
|
/// </summary>
|
||
|
public string Filename;
|
||
|
/// <summary>
|
||
|
/// 表示错误码。
|
||
|
/// </summary>
|
||
|
public int ErrorCode;
|
||
|
/// <summary>
|
||
|
/// 表示错误详细信息。
|
||
|
/// </summary>
|
||
|
public string ErrorMessage;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件传输过程中的状态。
|
||
|
/// </summary>
|
||
|
internal class TransferedFileStatus
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 表示传输文件会话。
|
||
|
/// </summary>
|
||
|
public IAppSession<BinaryProtocol, BinaryMessage> AppSession;
|
||
|
/// <summary>
|
||
|
/// 表示要传输的文件路径。相对路径和绝对路径均可。
|
||
|
/// </summary>
|
||
|
public string FullName;
|
||
|
/// <summary>
|
||
|
/// 表示文件名。只包含文件名和扩展名,不包含路径。
|
||
|
/// </summary>
|
||
|
public string Filename;
|
||
|
/// <summary>
|
||
|
/// 表示文件总共划分了多少块。
|
||
|
/// </summary>
|
||
|
public int TotalChunkCount;
|
||
|
/// <summary>
|
||
|
/// 表示当前传输到哪块了。
|
||
|
/// </summary>
|
||
|
public int CurrentChunkIndex;
|
||
|
/// <summary>
|
||
|
/// 表示当前已经传输了多少块了。
|
||
|
/// </summary>
|
||
|
public int TransferedChunkCount;
|
||
|
/// <summary>
|
||
|
/// 表示文件传输过程中的进度变化。
|
||
|
/// </summary>
|
||
|
public Action<string, float> ProgressChanged;
|
||
|
/// <summary>
|
||
|
/// 表示文件传输完毕事件。
|
||
|
/// </summary>
|
||
|
public Action<string> Completed;
|
||
|
/// <summary>
|
||
|
/// 表示文件传输时发生错误,当前只针对下载。
|
||
|
/// </summary>
|
||
|
public Action<ErrorInfo> Error;
|
||
|
}
|
||
|
|
||
|
public static class FileTransfer
|
||
|
{
|
||
|
internal class FileSyncStatus
|
||
|
{
|
||
|
public BitArray BitArray;
|
||
|
public ArraySegment<byte> Tag;
|
||
|
public int TotalChunkCount;
|
||
|
public int TransferedChunkCount;
|
||
|
}
|
||
|
|
||
|
private const string DOWNLOAD_FILE_INFO_REQUEST = "DOWNLOAD_FILE_INFO_REQUEST";
|
||
|
private const string DOWNLOAD_CHUNK_INFO_REQUEST = "DOWNLOAD_CHUNK_INFO_REQUEST";
|
||
|
|
||
|
private const string DOWNLOAD_FILE_INFO_REPLY = "DOWNLOAD_FILE_INFO_REPLY";
|
||
|
private const string DOWNLOAD_CHUNK_INFO_REPLY = "DOWNLOAD_CHUNK_INFO_REPLY";
|
||
|
|
||
|
private const string DOWNLOAD_ERROR = "DOWNLOAD_ERROR";
|
||
|
|
||
|
private const string UPLOAD_FILE_INFO_REQUEST = "UPLOAD_FILE_INFO_REQUEST";
|
||
|
private const string UPLOAD_CHUNK_INFO_REQUEST = "UPLOAD_CHUNK_INFO_REQUEST";
|
||
|
|
||
|
private const string UPLOAD_FILE_INFO_REPLY = "UPLOAD_FILE_INFO_REPLY";
|
||
|
private const string UPLOAD_CHUNK_INFO_REPLY = "UPLOAD_CHUNK_INFO_REPLY";
|
||
|
|
||
|
private const string FORWARD_FILE_INFO_REQUEST = "FORWARD_FILE_INFO_REQUEST";
|
||
|
private const string FORWARD_CHUNK_INFO_REQUEST = "FORWARD_CHUNK_INFO_REQUEST";
|
||
|
|
||
|
private const string FORWARD_FILE_INFO_REPLY = "FORWARD_FILE_INFO_REPLY";
|
||
|
private const string FORWARD_CHUNK_INFO_REPLY = "FORWARD_CHUNK_INFO_REPLY";
|
||
|
|
||
|
private const string FORWARD_FILE_INFO_SYNC = "FORWARD_FILE_INFO_SYNC";
|
||
|
private const string FORWARD_CHUNK_INFO_SYNC = "FORWARD_CHUNK_INFO_SYNC";
|
||
|
|
||
|
private const string PULL_CHUNK_INFO_REQUEST = "PULL_CHUNK_INFO_REQUEST";
|
||
|
private const string PULL_CHUNK_INFO_REPLY = "PULL_CHUNK_INFO_REQUEST";
|
||
|
|
||
|
private const string PULL_FILE_INFO_REQUEST = "PULL_FILE_INFO_REQUEST";
|
||
|
private const string PULL_FILE_INFO_REPLY = "PULL_FILE_INFO_REPLY";
|
||
|
|
||
|
private const string SEND_FILE_SYNC = "SEND_FILE_SYNC";
|
||
|
|
||
|
private static string BaseTempFolder = Application.dataPath + Path.DirectorySeparatorChar + "Temp" + Path.DirectorySeparatorChar;
|
||
|
private static string BaseUploadFolder = Application.dataPath + Path.DirectorySeparatorChar + "Upload" + Path.DirectorySeparatorChar;
|
||
|
private static string TempFolder = BaseTempFolder;
|
||
|
private static string UploadFolder = BaseUploadFolder;
|
||
|
private static string DirectorySeparator = new string(new char[] { Path.DirectorySeparatorChar });
|
||
|
|
||
|
private const int ChunkSize = 65536 - 4096;
|
||
|
private const int TransferedChunkCount = 1024 * 1024 / ChunkSize;
|
||
|
|
||
|
private static readonly BufferPool BufferPool = new BufferPool(1, ChunkSize);
|
||
|
|
||
|
private static Dictionary<string, TransferedFileStatus> UploadContainer = new Dictionary<string, TransferedFileStatus>();
|
||
|
private static Dictionary<string, TransferedFileStatus> DownloadContainer = new Dictionary<string, TransferedFileStatus>();
|
||
|
private static Dictionary<string, TransferedFileStatus> ForwardContainer = new Dictionary<string, TransferedFileStatus>();
|
||
|
private static Dictionary<string, FileSyncStatus> FileSyncContainer = new Dictionary<string, FileSyncStatus>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// 表示文件传输初始化。
|
||
|
/// </summary>
|
||
|
public static void Initialize()
|
||
|
{
|
||
|
NetworkMessageDispatcher.AddListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(DOWNLOAD_ERROR, OnDownloadError);
|
||
|
NetworkMessageDispatcher.AddListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(FORWARD_FILE_INFO_REPLY, OnForwardFileInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(FORWARD_CHUNK_INFO_REPLY, OnForwardChunkInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(FORWARD_FILE_INFO_SYNC, OnForwardFileInfoSync);
|
||
|
NetworkMessageDispatcher.AddListener(FORWARD_CHUNK_INFO_SYNC, OnForwardChunkInfoSync);
|
||
|
NetworkMessageDispatcher.AddListener(PULL_FILE_INFO_REPLY, OnPullFileInfoReply);
|
||
|
NetworkMessageDispatcher.AddListener(PULL_CHUNK_INFO_REPLY, OnPullChunkInfoReply);
|
||
|
}
|
||
|
|
||
|
public static void Dispose()
|
||
|
{
|
||
|
NetworkMessageDispatcher.RemoveListener(DOWNLOAD_FILE_INFO_REPLY, OnDownloadFileInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(DOWNLOAD_CHUNK_INFO_REPLY, OnDownloadChunkInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(DOWNLOAD_ERROR, OnDownloadError);
|
||
|
NetworkMessageDispatcher.RemoveListener(UPLOAD_FILE_INFO_REPLY, OnUploadFileInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(UPLOAD_CHUNK_INFO_REPLY, OnUploadChunkInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(FORWARD_FILE_INFO_REPLY, OnForwardFileInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(FORWARD_CHUNK_INFO_REPLY, OnForwardChunkInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(FORWARD_FILE_INFO_SYNC, OnForwardFileInfoSync);
|
||
|
NetworkMessageDispatcher.RemoveListener(FORWARD_CHUNK_INFO_SYNC, OnForwardChunkInfoSync);
|
||
|
NetworkMessageDispatcher.RemoveListener(PULL_FILE_INFO_REPLY, OnPullFileInfoReply);
|
||
|
NetworkMessageDispatcher.RemoveListener(PULL_CHUNK_INFO_REPLY, OnPullChunkInfoReply);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 设置文件下载时的临时目录。
|
||
|
/// </summary>
|
||
|
/// <param name="temp">用户指定的临时目录</param>
|
||
|
/// <remarks>注意:最好在登录成功之后进行设置。用户指定的临时完整路径是 Application.dataPath/Temp/temp/。</remarks>
|
||
|
public static void SetTempPath(string temp)
|
||
|
{
|
||
|
if (temp.EndsWith(DirectorySeparator))
|
||
|
TempFolder = BaseTempFolder + temp;
|
||
|
else
|
||
|
TempFolder = BaseTempFolder + temp + DirectorySeparator;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 获得文件下载时的临时目录。
|
||
|
/// </summary>
|
||
|
public static string GetTempPath()
|
||
|
{
|
||
|
return TempFolder;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 设置文件上传目录。
|
||
|
/// </summary>
|
||
|
/// <param name="upload">用户指定的文件上传目录</param>
|
||
|
/// <remarks>注意:最好在登录成功之后进行设置。</remarks>
|
||
|
public static void SetUploadPath(string upload)
|
||
|
{
|
||
|
if (upload.EndsWith(DirectorySeparator))
|
||
|
UploadFolder = upload;
|
||
|
else
|
||
|
UploadFolder = upload + DirectorySeparator;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 获得文件上传目录。
|
||
|
/// </summary>
|
||
|
public static string GetUploadPath()
|
||
|
{
|
||
|
return UploadFolder;
|
||
|
}
|
||
|
|
||
|
private static void OnPullFileInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
var info = message.Body.Deserialize<TransferedFileInfo>();
|
||
|
var status = default(FileSyncStatus);
|
||
|
|
||
|
if (!FileSyncContainer.TryGetValue(info.Filename, out status))
|
||
|
{
|
||
|
status = new FileSyncStatus();
|
||
|
status.Tag = info.Tag;
|
||
|
|
||
|
FileSyncContainer.Add(info.Filename, status);
|
||
|
}
|
||
|
else
|
||
|
status.Tag = info.Tag;
|
||
|
}
|
||
|
|
||
|
private static void OnPullChunkInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
var status = default(FileSyncStatus);
|
||
|
var info = message.Body.Deserialize<TransferedChunkInfo>();
|
||
|
|
||
|
using (var stream = new FileStream(TempFolder + info.Filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||
|
{
|
||
|
var position = info.ChunkId * info.ChunkSize;
|
||
|
stream.Seek(position, SeekOrigin.Begin);
|
||
|
stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count);
|
||
|
stream.Flush();
|
||
|
}
|
||
|
|
||
|
status.BitArray[info.ChunkId] = true;
|
||
|
++status.TransferedChunkCount;
|
||
|
|
||
|
|
||
|
// 确实同步完成
|
||
|
if (status.TransferedChunkCount == info.ChunkCount)
|
||
|
{
|
||
|
FileSyncContainer.Remove(info.Filename);
|
||
|
ForwardContainer.Remove(info.Filename);
|
||
|
|
||
|
var tag = status.Tag.Deserialize<KeyValuePair<string, ArraySegment<byte>>>();
|
||
|
NetworkMessageDispatcher.SendMessage(tag.Key, tag.Value, TempFolder + info.Filename);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// 拉取未完成的文件块
|
||
|
var chunkId = GetChunkId(status.BitArray);
|
||
|
|
||
|
if (chunkId > 0)
|
||
|
{
|
||
|
var request = new TransferedChunkInfoRequest();
|
||
|
|
||
|
request.Filename = info.Filename;
|
||
|
request.ChunkId = chunkId;
|
||
|
request.ChunkSize = ChunkSize;
|
||
|
|
||
|
var session = ForwardContainer[info.Filename].AppSession;
|
||
|
session.SendRequestAsync(PULL_CHUNK_INFO_REQUEST, request);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnForwardChunkInfoSync(BinaryMessage message)
|
||
|
{
|
||
|
var info = message.Body.Deserialize<TransferedChunkInfo>();
|
||
|
var status = default(FileSyncStatus);
|
||
|
|
||
|
if (!FileSyncContainer.TryGetValue(info.Filename, out status))
|
||
|
{
|
||
|
status = new FileSyncStatus();
|
||
|
FileSyncContainer.Add(info.Filename, status);
|
||
|
|
||
|
// 拉取文件信息
|
||
|
var session = ForwardContainer[info.Filename].AppSession;
|
||
|
session.SendRequestAsync(PULL_FILE_INFO_REQUEST, info.Filename);
|
||
|
}
|
||
|
|
||
|
if (status.BitArray == null)
|
||
|
{
|
||
|
status.BitArray = new BitArray(info.ChunkCount);
|
||
|
status.TotalChunkCount = info.ChunkCount;
|
||
|
}
|
||
|
|
||
|
using (var stream = new FileStream(TempFolder + info.Filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||
|
{
|
||
|
var position = info.ChunkId * info.ChunkSize;
|
||
|
stream.Seek(position, SeekOrigin.Begin);
|
||
|
stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count);
|
||
|
stream.Flush();
|
||
|
}
|
||
|
|
||
|
status.BitArray[info.ChunkId] = true;
|
||
|
status.TransferedChunkCount += 1;
|
||
|
|
||
|
// 同步到了最后一个文件块
|
||
|
if (info.ChunkId == info.ChunkCount - 1)
|
||
|
{
|
||
|
// 确实同步完成
|
||
|
if (status.TransferedChunkCount == info.ChunkCount)
|
||
|
{
|
||
|
FileSyncContainer.Remove(info.Filename);
|
||
|
ForwardContainer.Remove(info.Filename);
|
||
|
|
||
|
var tag = status.Tag.Deserialize<KeyValuePair<string, ArraySegment<byte>>>();
|
||
|
NetworkMessageDispatcher.SendMessage(tag.Key, tag.Value, TempFolder + info.Filename);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// 拉取未完成的文件块
|
||
|
var chunkId = GetChunkId(status.BitArray);
|
||
|
|
||
|
if (chunkId > 0)
|
||
|
{
|
||
|
var request = new TransferedChunkInfoRequest();
|
||
|
|
||
|
request.Filename = info.Filename;
|
||
|
request.ChunkId = chunkId;
|
||
|
request.ChunkSize = ChunkSize;
|
||
|
|
||
|
var session = ForwardContainer[info.Filename].AppSession;
|
||
|
session.SendRequestAsync(PULL_CHUNK_INFO_REQUEST, request);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnForwardFileInfoSync(BinaryMessage message)
|
||
|
{
|
||
|
var info = message.Body.Deserialize<TransferedFileInfo>();
|
||
|
var fullname = TempFolder + info.Filename;
|
||
|
|
||
|
if (!Directory.Exists(TempFolder))
|
||
|
Directory.CreateDirectory(TempFolder);
|
||
|
|
||
|
if (!File.Exists(fullname))
|
||
|
{
|
||
|
using (var stream = new FileStream(fullname, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||
|
{
|
||
|
stream.SetLength(info.FileSize);
|
||
|
stream.Flush();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var status = default(FileSyncStatus);
|
||
|
|
||
|
if (!FileSyncContainer.TryGetValue(info.Filename, out status))
|
||
|
{
|
||
|
status = new FileSyncStatus();
|
||
|
status.Tag = info.Tag;
|
||
|
|
||
|
FileSyncContainer.Add(info.Filename, status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnForwardChunkInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
// 传输文件块
|
||
|
var reply = message.Body.Deserialize<TransferedChunkInfoReply>();
|
||
|
var status = default(TransferedFileStatus);
|
||
|
|
||
|
if (!ForwardContainer.TryGetValue(reply.Filename, out status))
|
||
|
return;
|
||
|
|
||
|
++status.TransferedChunkCount;
|
||
|
|
||
|
// 表示文件上传成功
|
||
|
if (status.TransferedChunkCount == status.TotalChunkCount)
|
||
|
{
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, 100.0f);
|
||
|
|
||
|
if (status.Completed != null)
|
||
|
status.Completed(status.FullName);
|
||
|
|
||
|
//ForwardContainer.Remove(status.Filename);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f;
|
||
|
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, progress);
|
||
|
|
||
|
++status.CurrentChunkIndex;
|
||
|
|
||
|
// 继续上传文件
|
||
|
if (status.CurrentChunkIndex < status.TotalChunkCount)
|
||
|
ForwardFileChunkAsync(status, status.CurrentChunkIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnForwardFileInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
var pair = message.Body.Deserialize<KeyValuePair<string, ArraySegment<byte>>>();
|
||
|
var filename = pair.Key;
|
||
|
var body = pair.Value.Deserialize<KeyValuePair<string, ArraySegment<byte>>>();
|
||
|
|
||
|
// 通知客户端收到回复了
|
||
|
NetworkMessageDispatcher.SendMessage(body.Key, body.Value, filename);
|
||
|
|
||
|
// 开始真正上传文件
|
||
|
var status = ForwardContainer[filename];
|
||
|
|
||
|
var count = Math.Min(status.TotalChunkCount, TransferedChunkCount);
|
||
|
|
||
|
status.CurrentChunkIndex = count - 1;
|
||
|
|
||
|
for (var i = 0; i < count; ++i)
|
||
|
ForwardFileChunkAsync(status, i);
|
||
|
}
|
||
|
|
||
|
private static void OnUploadChunkInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
// 传输文件块
|
||
|
var reply = message.Body.Deserialize<TransferedChunkInfoReply>();
|
||
|
var status = default(TransferedFileStatus);
|
||
|
|
||
|
if (!UploadContainer.TryGetValue(reply.Filename, out status))
|
||
|
return;
|
||
|
|
||
|
++status.TransferedChunkCount;
|
||
|
|
||
|
// 表示文件上传成功
|
||
|
if (status.TransferedChunkCount == status.TotalChunkCount)
|
||
|
{
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, 100.0f);
|
||
|
|
||
|
if (status.Completed != null)
|
||
|
status.Completed(status.FullName);
|
||
|
|
||
|
UploadContainer.Remove(status.Filename);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f;
|
||
|
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, progress);
|
||
|
|
||
|
++status.CurrentChunkIndex;
|
||
|
|
||
|
// 继续上传文件
|
||
|
if (status.CurrentChunkIndex < status.TotalChunkCount)
|
||
|
UploadFileChunkAsync(status, status.CurrentChunkIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnUploadFileInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
// 开始真正上传文件
|
||
|
var filename = message.Body.Deserialize<string>();
|
||
|
var status = UploadContainer[filename];
|
||
|
var count = Math.Min(status.TotalChunkCount, TransferedChunkCount);
|
||
|
|
||
|
status.CurrentChunkIndex = count - 1;
|
||
|
|
||
|
for (var i = 0; i < count; ++i)
|
||
|
UploadFileChunkAsync(status, i);
|
||
|
}
|
||
|
|
||
|
private static void OnDownloadError(BinaryMessage message)
|
||
|
{
|
||
|
var error = message.Body.Deserialize<ErrorInfo>();
|
||
|
var status = DownloadContainer[error.Filename];
|
||
|
|
||
|
if (status.Error != null)
|
||
|
status.Error(error);
|
||
|
|
||
|
DownloadContainer.Remove(error.Filename);
|
||
|
}
|
||
|
|
||
|
private static void OnDownloadChunkInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
var info = message.Body.Deserialize<TransferedChunkInfo>();
|
||
|
|
||
|
var status = DownloadContainer[info.Filename];
|
||
|
|
||
|
using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||
|
{
|
||
|
stream.Seek(info.ChunkId * info.ChunkSize, SeekOrigin.Begin);
|
||
|
stream.Write(info.Data.Array, info.Data.Offset, info.Data.Count);
|
||
|
stream.Flush();
|
||
|
}
|
||
|
|
||
|
status.TotalChunkCount = info.ChunkCount;
|
||
|
++status.TransferedChunkCount;
|
||
|
|
||
|
// 表示文件下载成功
|
||
|
if (status.TransferedChunkCount == status.TotalChunkCount)
|
||
|
{
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, 100.0f);
|
||
|
|
||
|
if (status.Completed != null)
|
||
|
status.Completed(status.FullName);
|
||
|
|
||
|
DownloadContainer.Remove(status.Filename);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var progress = ((float)status.TransferedChunkCount / status.TotalChunkCount) * 100.0f;
|
||
|
|
||
|
if (status.ProgressChanged != null)
|
||
|
status.ProgressChanged(status.FullName, progress);
|
||
|
|
||
|
// 继续下载文件
|
||
|
if (status.CurrentChunkIndex == 0)
|
||
|
{
|
||
|
var count = Math.Min(info.ChunkCount, TransferedChunkCount);
|
||
|
|
||
|
for (var i = 1; i < count; ++i)
|
||
|
DownloadFileChunkAsync(status, i);
|
||
|
|
||
|
status.CurrentChunkIndex += count;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (status.CurrentChunkIndex < status.TotalChunkCount)
|
||
|
{
|
||
|
DownloadFileChunkAsync(status, status.CurrentChunkIndex);
|
||
|
++status.CurrentChunkIndex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void OnDownloadFileInfoReply(BinaryMessage message)
|
||
|
{
|
||
|
// 开始真正下载文件
|
||
|
var info = message.Body.Deserialize<TransferedFileInfo>();
|
||
|
var status = DownloadContainer[info.Filename];
|
||
|
|
||
|
using (var stream = new FileStream(status.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||
|
{
|
||
|
stream.SetLength(info.FileSize);
|
||
|
stream.Flush();
|
||
|
}
|
||
|
|
||
|
status.CurrentChunkIndex = 0;
|
||
|
|
||
|
// 下载第一块
|
||
|
DownloadFileChunkAsync(status, 0);
|
||
|
}
|
||
|
|
||
|
private static void UploadFileChunkAsync(TransferedFileStatus status, int chunkId)
|
||
|
{
|
||
|
var buffer = BufferPool.Acquire();
|
||
|
|
||
|
using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||
|
{
|
||
|
stream.Seek(chunkId * ChunkSize, SeekOrigin.Begin);
|
||
|
var readCount = stream.Read(buffer, 0, ChunkSize);
|
||
|
var data = new ArraySegment<byte>(buffer, 0, readCount);
|
||
|
|
||
|
var info = new TransferedChunkInfo();
|
||
|
|
||
|
info.Filename = status.Filename;
|
||
|
info.ChunkCount = status.TotalChunkCount;
|
||
|
info.ChunkId = chunkId;
|
||
|
info.ChunkSize = ChunkSize;
|
||
|
info.Data = data;
|
||
|
|
||
|
status.AppSession.SendRequestAsync(UPLOAD_CHUNK_INFO_REQUEST, info);
|
||
|
}
|
||
|
|
||
|
BufferPool.Release(buffer);
|
||
|
}
|
||
|
|
||
|
private static void ForwardFileChunkAsync(TransferedFileStatus status, int chunkId)
|
||
|
{
|
||
|
var buffer = BufferPool.Acquire();
|
||
|
|
||
|
using (var stream = new FileStream(status.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||
|
{
|
||
|
stream.Seek(chunkId * ChunkSize, SeekOrigin.Begin);
|
||
|
var readCount = stream.Read(buffer, 0, ChunkSize);
|
||
|
var data = new ArraySegment<byte>(buffer, 0, readCount);
|
||
|
|
||
|
var info = new TransferedChunkInfo();
|
||
|
|
||
|
info.Filename = status.Filename;
|
||
|
info.ChunkCount = status.TotalChunkCount;
|
||
|
info.ChunkId = chunkId;
|
||
|
info.ChunkSize = ChunkSize;
|
||
|
info.Data = data;
|
||
|
|
||
|
status.AppSession.SendRequestAsync(FORWARD_CHUNK_INFO_REQUEST, info);
|
||
|
}
|
||
|
|
||
|
BufferPool.Release(buffer);
|
||
|
}
|
||
|
|
||
|
private static void DownloadFileChunkAsync(TransferedFileStatus status, int chunkId)
|
||
|
{
|
||
|
var request = new TransferedChunkInfoRequest();
|
||
|
|
||
|
request.Filename = status.Filename;
|
||
|
request.ChunkId = chunkId;
|
||
|
|
||
|
status.AppSession.SendRequestAsync(DOWNLOAD_CHUNK_INFO_REQUEST, request);
|
||
|
}
|
||
|
|
||
|
// 获得未完成的文件块索引
|
||
|
private static int GetChunkId(BitArray array)
|
||
|
{
|
||
|
for (var i = 0; i < array.Length; ++i)
|
||
|
if (!array[i])
|
||
|
return i;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename)
|
||
|
{
|
||
|
UploadFileAsync(session, filename, null, null);
|
||
|
}
|
||
|
|
||
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress)
|
||
|
{
|
||
|
UploadFileAsync(session, filename, progress, null);
|
||
|
}
|
||
|
|
||
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string> completed)
|
||
|
{
|
||
|
UploadFileAsync(session, filename, null, completed);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 异步上传文件。
|
||
|
/// </summary>
|
||
|
public static void UploadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress, Action<string> completed)
|
||
|
{
|
||
|
var fullname = UploadFolder + filename;
|
||
|
|
||
|
if (!File.Exists(fullname))
|
||
|
throw new ArgumentException("filename");
|
||
|
|
||
|
var fileinfo = new FileInfo(fullname);
|
||
|
|
||
|
var fileSize = (int)fileinfo.Length;
|
||
|
var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1;
|
||
|
|
||
|
var info = new TransferedFileInfo();
|
||
|
|
||
|
info.Filename = filename;
|
||
|
info.FileSize = fileSize;
|
||
|
|
||
|
var status = new TransferedFileStatus();
|
||
|
|
||
|
status.AppSession = session;
|
||
|
status.FullName = fullname;
|
||
|
status.Filename = filename;
|
||
|
status.TotalChunkCount = chunkCount;
|
||
|
status.ProgressChanged = progress;
|
||
|
status.Completed = completed;
|
||
|
|
||
|
UploadContainer.Add(status.Filename, status);
|
||
|
|
||
|
// 发送上传文件请求
|
||
|
session.SendRequestAsync(UPLOAD_FILE_INFO_REQUEST, info);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, string filename)
|
||
|
{
|
||
|
SendFileAsync(session, header, filename, (Action<string, float>)null);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename)
|
||
|
{
|
||
|
SendFileAsync(session, header, body, filename, null, null);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, string filename, Action<string, float> progress)
|
||
|
{
|
||
|
SendFileAsync(session, header, filename, progress, null);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename, Action<string, float> progress)
|
||
|
{
|
||
|
SendFileAsync(session, header, body, filename, progress, null);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, string filename, Action<string, float> progress, Action<string> completed)
|
||
|
{
|
||
|
var pair = new KeyValuePair<string, ArraySegment<byte>>(header, default(ArraySegment<byte>));
|
||
|
|
||
|
SendFileAsync(session, filename, pair, progress, completed);
|
||
|
}
|
||
|
|
||
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string header, T body, string filename, Action<string, float> progress, Action<string> completed)
|
||
|
{
|
||
|
var buffer = BufferPool.Acquire();
|
||
|
var segment = body.Serialize();
|
||
|
|
||
|
Buffer.BlockCopy(segment.Array, segment.Offset, buffer, 0, segment.Count);
|
||
|
|
||
|
var rawBody = new ArraySegment<byte>(buffer, 0, segment.Count);
|
||
|
var pair = new KeyValuePair<string, ArraySegment<byte>>(header, rawBody);
|
||
|
|
||
|
SendFileAsync(session, filename, pair, progress, completed);
|
||
|
|
||
|
BufferPool.Release(buffer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 异步转发文件。
|
||
|
/// </summary>
|
||
|
/// <remarks>注意,调用此方法传输的文件应该都是小文件(不大于 1M 的文件)。传输大文件可能会比较低效。</remarks>
|
||
|
public static void SendFileAsync<T>(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, T tag, Action<string, float> progress, Action<string> completed)
|
||
|
{
|
||
|
if (!File.Exists(filename))
|
||
|
throw new ArgumentException("filename");
|
||
|
|
||
|
var fileinfo = new FileInfo(filename);
|
||
|
|
||
|
var fileSize = (int)fileinfo.Length;
|
||
|
var chunkCount = fileSize % ChunkSize == 0 ? fileSize / ChunkSize : fileSize / ChunkSize + 1;
|
||
|
|
||
|
var info = new TransferedFileInfo();
|
||
|
|
||
|
info.Filename = fileinfo.Name;
|
||
|
info.FileSize = fileSize;
|
||
|
|
||
|
var buffer = BufferPool.Acquire();
|
||
|
var segment = tag.Serialize();
|
||
|
|
||
|
Buffer.BlockCopy(segment.Array, segment.Offset, buffer, 0, segment.Count);
|
||
|
|
||
|
info.Tag = new ArraySegment<byte>(buffer, 0, segment.Count);
|
||
|
|
||
|
var status = new TransferedFileStatus();
|
||
|
|
||
|
status.AppSession = session;
|
||
|
status.FullName = filename;
|
||
|
status.Filename = fileinfo.Name;
|
||
|
status.TotalChunkCount = chunkCount;
|
||
|
status.ProgressChanged = progress;
|
||
|
status.Completed = completed;
|
||
|
|
||
|
ForwardContainer.Add(status.Filename, status);
|
||
|
|
||
|
// 发送转发文件请求
|
||
|
session.SendRequestAsync(FORWARD_FILE_INFO_REQUEST, info);
|
||
|
|
||
|
BufferPool.Release(buffer);
|
||
|
}
|
||
|
|
||
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename)
|
||
|
{
|
||
|
DownloadFileAsync(session, filename, null, null);
|
||
|
}
|
||
|
|
||
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string> completed)
|
||
|
{
|
||
|
DownloadFileAsync(session, filename, null, completed);
|
||
|
}
|
||
|
|
||
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string filename, Action<string, float> progress)
|
||
|
{
|
||
|
DownloadFileAsync(session, filename, progress, null);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 异步下载文件。
|
||
|
/// </summary>
|
||
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, Action<string, float> progress, Action<string> completed)
|
||
|
{
|
||
|
DownloadFileAsync(session, fullname, progress, completed, null);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 异步下载文件。
|
||
|
/// </summary>
|
||
|
public static void DownloadFileAsync(this IAppSession<BinaryProtocol, BinaryMessage> session, string fullname, Action<string, float> progress, Action<string> completed, Action<ErrorInfo> error)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(fullname))
|
||
|
throw new ArgumentException("fullname 不能为空!");
|
||
|
|
||
|
var actualName = TempFolder + fullname;
|
||
|
var directory = Path.GetDirectoryName(TempFolder + fullname);
|
||
|
|
||
|
if (!Directory.Exists(directory))
|
||
|
Directory.CreateDirectory(directory);
|
||
|
|
||
|
var status = new TransferedFileStatus();
|
||
|
|
||
|
status.AppSession = session;
|
||
|
status.FullName = actualName;
|
||
|
status.Filename = fullname;
|
||
|
status.ProgressChanged = progress;
|
||
|
status.Completed = completed;
|
||
|
status.Error = error;
|
||
|
|
||
|
DownloadContainer.Add(status.Filename, status);
|
||
|
|
||
|
var info = new TransferedFileInfo();
|
||
|
|
||
|
info.Filename = fullname;
|
||
|
info.FileSize = 0;
|
||
|
|
||
|
// 发送下载文件请求
|
||
|
session.SendRequestAsync(DOWNLOAD_FILE_INFO_REQUEST, info);
|
||
|
}
|
||
|
}
|
||
|
}
|