简单实现TCP下的大文件高效传输

在TCP下进行大文件传输不象小文件那样直接打包个BUFFER发送出去,因为文件比较大所以不可能把文件读到一个BUFFER发送出去.主要有些文件的大小可能是1G,2G或更大,分配这么大的BUFFER对内存来说显然是不现实的事情;针对服务端的设计来说就更需要严紧些,BUFFER大小的限制也是变得很重要.下面介绍使用Beetle简单地实现大文件在TCP的传输应用.

协议制定

既然需要把文件分块来处理,那在TCP传输的过程需要制定一些协议来规范数据有效性,数据协议主要有三个:告诉服务器需要上传文件,文件块上传和返回每个环节处理的结果.

1)上传文件指令

public class Upload:ObjectMessage
{
public string FileMD5
{
get;
set;
} public string FileName
{
get;
set;
} public long FileSize
{
get;
set;
} public override void FromProtocolData(HttpData httpbase)
{
FileName = httpbase[CONSTVALUE.HEADER_NAME];
FileMD5 = httpbase[CONSTVALUE.HEADER_MD5];
FileSize = long.Parse(httpbase[CONSTVALUE.HEADER_FILESIZE]);
} protected override void OnDisposed()
{ } protected override void OnToProtocolData(HttpData httpbase)
{
httpbase.Command = CONSTVALUE.COMMAND_UPLOAD;
httpbase[CONSTVALUE.HEADER_MD5] = FileMD5;
httpbase[CONSTVALUE.HEADER_NAME] = FileName;
httpbase[CONSTVALUE.HEADER_FILESIZE] = FileSize.ToString();
}
}

2)上传文件块指令

public class UploadData:ObjectMessage
{ public string FileMD5
{
get;
set;
} public Beetle.ByteArraySegment Data
{
get;
set;
} public override void FromProtocolData(HttpData httpbase)
{
FileMD5 = httpbase[CONSTVALUE.HEADER_MD5];
Data = httpbase.Content;
} protected override void OnDisposed()
{
if (Data != null)
{
FileTransferPackage.BufferPool.Push(Data);
Data = null;
}
} protected override void OnToProtocolData(HttpData httpbase)
{
httpbase.Command = CONSTVALUE.COMMAND_UPLOAD_DATA;
httpbase[CONSTVALUE.HEADER_MD5] = FileMD5;
httpbase.Content = Data;
}
}

3)返回值指令

public class Result :ObjectMessage
{
public string FileMD5
{
get;
set;
} public bool Error
{
get;
set;
} public string ErrorDetail
{
get;
set;
} public override void FromProtocolData(HttpData httpbase)
{
ErrorDetail = httpbase[CONSTVALUE.HEADER_STATUS_DETAIL];
Error = httpbase[CONSTVALUE.HEADER_STATUS] == CONSTVALUE.VALUE_SUCCESS;
FileMD5 = httpbase[CONSTVALUE.HEADER_MD5];
} protected override void OnDisposed()
{ } protected override void OnToProtocolData(HttpData httpbase)
{
httpbase.Command = CONSTVALUE.COMMAND_RESULT;
if (Error)
{
httpbase[CONSTVALUE.HEADER_STATUS] = CONSTVALUE.VALUE_SUCCESS;
}
else
{
httpbase[CONSTVALUE.HEADER_STATUS] = CONSTVALUE.VALUE_ERROR;
}
httpbase[CONSTVALUE.HEADER_STATUS_DETAIL] = ErrorDetail;
httpbase[CONSTVALUE.HEADER_MD5] = FileMD5;
}
}

ObjectMessage是Beetle一个简化HTTP协议的扩展对象,它提供自定义Header和Body等功能.

文件读写器

既然需要处理文件块,那提供一些简单的文件块读取和写入方法是比较重要的.它不仅从设计解决功能的偶合度,还可以方便今后的利用.

1)UploadReader

public class UploadReader : IDisposable
{
public UploadReader(string file)
{ mStream = System.IO.File.OpenRead(file);
StringBuilder sb = new StringBuilder();
MD5 md5Hasher = MD5.Create();
foreach (Byte b in md5Hasher.ComputeHash(mStream))
sb.Append(b.ToString("x2").ToLower());
FileMD5 = sb.ToString();
mStream.Position = 0;
FileSize = mStream.Length;
FileName = System.IO.Path.GetFileName(file); } private System.IO.FileStream mStream = null; public string FileName
{
get;
set;
} public long LastReadLength
{
get;
set;
} public long ReadLength
{
get;
set;
} public long FileSize
{
get;
set;
} public string FileMD5
{
get;
set;
} public bool Completed
{
get
{
return mStream != null && ReadLength == mStream.Length;
}
} public void Close()
{
if (mStream != null)
{
mStream.Close();
mStream.Dispose();
}
} public void Reset()
{
mStream.Position = 0;
LastReadLength = 0;
ReadLength = 0;
} public void Read(ByteArraySegment segment)
{
int loads = mStream.Read(segment.Array, 0, FileTransferPackage.BUFFER_SIZE);
segment.SetInfo(0, loads);
ReadLength += loads;
} public void Dispose()
{
mStream.Dispose();
} public override string ToString()
{
string value= string.Format("{0}(MD5:{4})\r\n\r\n[{1}/{2}({3}/秒)]",FileName,ReadLength,FileSize,ReadLength-LastReadLength,FileMD5);
if (!Completed)
{
LastReadLength = ReadLength;
}
return value;
}
}

UploadReader的功能主要是把文件流读取到指定大小的Buffer中,并提供方法获取当前的读取情况

2)UploadWriter

public class UploadWriter
{
public UploadWriter(string rootPath, string filename,string fileMD5,long size)
{
mFullName = rootPath + filename;
FileName = filename;
FileMD5 = fileMD5;
Size = size;
} private string mFullName; private System.IO.FileStream mStream; public System.IO.FileStream Stream
{
get
{
if (mStream == null)
{
mStream = System.IO.File.Create(mFullName+".up");
}
return mStream;
}
} public long WriteLength
{
get;
set;
} public long LastWriteLength
{
get;
set;
} public long Size
{
get;
set;
} public string FileName
{
get;
set;
} public string FileMD5
{
get;
set;
} public bool Write(ByteArraySegment segment)
{
Stream.Write(segment.Array, 0, segment.Count);
WriteLength += segment.Count;
Stream.Flush();
if (WriteLength == Size)
{
Stream.Close();
if (System.IO.File.Exists(mFullName))
System.IO.File.Delete(mFullName);
System.IO.File.Move(mFullName + ".up", mFullName);
return true;
}
return false;
}
}

UploadWriter的功能主要是把文件写入到临时文件中,写入完成后再更改相应的名称,为了方便查询同样也提供了一些写入情况信息.

服务端代码

如果有了解过Beetle的服务端制定的话,那服务端的实现是非常简单的,只需要写一个对象承继ServerBase并实现数据接收方法处理即可以,接收的数据会会自动转换成之前定义的消息对象,而服务端内部处理的细节是完全不用关心.

protected override void OnMessageReceive(Beetle.PacketRecieveMessagerArgs e)
{
base.OnMessageReceive(e);
if (e.Message is Protocol.Upload)
{
OnUpload(e.Channel, e.Message as Protocol.Upload);
}
else if (e.Message is Protocol.UploadData)
{
OnUploadData(e.Channel, e.Message as Protocol.UploadData);
}
} private Protocol.Result GetErrorResult(string detail)
{
Protocol.Result result = new Protocol.Result();
result.Error = true;
result.ErrorDetail = detail;
return result;
} private void OnUpload(Beetle.TcpChannel channel, Protocol.Upload e)
{
Protocol.Result result;
if (mTask[e.FileMD5] != null)
{
result = GetErrorResult( "该文件正在上传任务中!");
channel.Send(result);
return;
}
UploadWriter writer = new UploadWriter(mRootPath, e.FileName, e.FileMD5, e.FileSize);
lock (mTask)
{
mTask[e.FileMD5] = writer;
}
result = new Protocol.Result();
channel.Send(result);
} private void OnUploadData(Beetle.TcpChannel channel, Protocol.UploadData e)
{
using (e)
{
Protocol.Result result;
UploadWriter writer = (UploadWriter)mTask[e.FileMD5];
if (writer == null)
{
result = GetErrorResult("上传任务不存在!");
channel.Send(result);
return;
}
if (writer.Write(e.Data))
{
lock (mTask)
{
mTask.Remove(e.FileMD5);
}
}
result = new Protocol.Result();
result.FileMD5 = writer.FileMD5;
channel.Send(result);
}
}

当接收到客户求上传请求后会建立对应MD5的文件写入器,后面文件块的上传写入相关对象即可.

客户端代码

Beetle对于Client的支持也是非常简单方便,只需要定义一个TcpChannel直接发送定义的对象消息并获取服务器端返回的消息即可.

 private void OnUpload(object state)
2 {
3 Lib.UploadReader reader = (Lib.UploadReader)state;
4 try
5 {
6 IsUpload = true;
7 Lib.Protocol.Upload upload = new Lib.Protocol.Upload();
8 upload.FileMD5 = reader.FileMD5;
9 upload.FileName = reader.FileName;
10 upload.FileSize = reader.FileSize;
11 Lib.Protocol.Result result = mClient.Send<Lib.Protocol.Result>(upload);
12 if (result.Error)
13 {
14 mLastError = result.ErrorDetail;
15 return;
16 }
17 while (!reader.Completed)
18 {
19 mLastError = "文件上传中...";
20 Lib.Protocol.UploadData data = new Lib.Protocol.UploadData();
21 data.Data = Lib.FileTransferPackage.BufferPool.Pop();
22 data.FileMD5 = reader.FileMD5;
23 reader.Read(data.Data);
24 result = mClient.Send<Lib.Protocol.Result>(data);
25 if (result.Error)
26 {
27 mLastError = result.ErrorDetail;
28 return;
29 }
30 }
31 mLastError = "文件上传完成!";
32
33 }
34 catch (Exception e_)
35 {
36 mLastError = e_.Message;
37 }
38 mReader.Reset();
39 IsUpload = false;
40
41 }

整个过程只需要一个方法却可完成,首先把需要上传的文件信息发送到服务器,当服务器确认后不停地把文件块信息输送到服务端即可.

使用测试

下载代码

FileTransfer.Lib.rar (644.64 kb)

简单实现TCP下的大文件高效传输的更多相关文章

  1. Linux培训教程 linux系统下分割大文件的方法

    在linux中分割大文件,比如一个5gb日志文件,需要把它分成多个小文件,分割后以利于普通的文本编辑器读取. 有时,需要传输20gb的大文件,Linux培训 教程件到另一台服务器,也需要把它分割成多个 ...

  2. Linux下查找大文件以及目录

    转自:http://www.cnblogs.com/kerrycode/p/4391859.html 在Windows系统中,我们可以使用TreeSize工具查找一些大文件或文件夹,非常的方便高效,在 ...

  3. Linux C 下的大文件操作

    这里说的大文件指大小超过4G的文件. 在32位环境下,linux默认打开.读.写超过4G的文件会返回错误.定义如下宏可以突破这个限制,对read/write和fread/fwrite同时有效. 注意它 ...

  4. tcp上传大文件举例、udp实现qq聊天、socketserver模块实现并发

    为什么会出现粘包现象(day31提到过,这里再举个例子) """首先只有在TCP协议中才会出现粘包现象,因为TCP协议是流式协议它的特点是将数据量小并且时间间隔比较短的数 ...

  5. Linux下查找大文件,大目录的方法

    查找大文件 //列举出当前目录所有大于800M的文件 find . -type f -size +800M 1 2 第一个方法只用到了一个命令find,它能够帮我们做一些文件查找的操作.它常用的参数有 ...

  6. centos 7下查找大文件、大目录和常见文件查找操作

    根据园子 潇湘隐者的文章 <Linux如何查找大文件或目录总结>结合实际运维需要整理出常用命令 目标文件和目录查找主要使用 find 命令 结合 xargs (给命令传递参数的一个过滤器, ...

  7. LINUX下查找大文件及大的文件夹

    原帖地址:https://www.cnblogs.com/iyoume2008/p/6105590.html 今天正好碰到这样的问题,在博客园中看到有以上地址的一篇文章,照着上面的操作解决了问题,但是 ...

  8. 基于TCP协议的大文件传输(粘包问题处理)

    基于TCP的大文件上传服务端实现 # 服务端 # -*- coding: utf-8 -*- from socket import * import json, struct server = soc ...

  9. linux 下处理大文件

    .head tail more .先把大文件进行分割 split split 参数: -a, --suffix-length=N 指定输出文件名的后缀,默认为2个 -b, --bytes=SIZE 指 ...

随机推荐

  1. Django----模板

    为了将html页面和python代码分离开来,有必要介绍一下模板的作用,Django中自带很多模板. 以下一个html模板文件: <html> <head><title& ...

  2. 从用python做zoj1011发生Non-zero Exit Code错误说起

    近期做了浙大oj的第1011道题,遇见一件奇怪的事.这道题我用c++和php做,提交后都正确.可是用全然同样的逻辑改写成python代码提交后却产生了Non-zero Exit Code的判题结果.p ...

  3. 多线程学习之五超时模式Timer

    timed[超时模式]案例:一个线程提供下载数据,另一个线程执行下载,如果有5秒钟以上,提供下载的线程没有提供数据,下载线程因超时异常,停止下载线程运行. 超时异常类 /** * */ package ...

  4. 手机web下拉加载

    //需要 zepto.js支持 var page=0;//当前页 var pages=1;//总页数 var ajax=!1;//是否加载中 Zepto(function($){ $(window). ...

  5. Installshield停止操作系统进程的代码 --IS6及以上版本适用

    原文:Installshield停止操作系统进程的代码 --IS6及以上版本适用 setup.rul的代码 Code;end;///////////////////////////////////// ...

  6. Nancy和MVC的简单对比

    Nancy和MVC的简单对比 在上一篇的.NET轻量级MVC框架:Nancy入门教程(一)——初识Nancy中,简单介绍了Nancy,并写了一个Hello,world.看到大家的评论,都在问Nancy ...

  7. leetcode第四题--Add Two Numbers

    Problem: You are given two linked lists representing two non-negative numbers. The digits are stored ...

  8. python购物淫秽数据分析(2)

    淘宝大数据的游戏,我重新提高自己的思维方式, 插件和代码前前后后写在六个版本,但最好的结果其实是我的第一次2第二码.这让我很惊讶, 但它也说明了一个问题.当你更熟悉的语言,当一方,你缺少的是其他的知识 ...

  9. spring bean范围

    总结: 实例代码具体解释: 文件夹结构 Car.java package com.coslay.beans.autowire; public class Car { private String br ...

  10. c#自带压缩类实现数据库表导出到CSV压缩文件

    c#自带压缩类实现数据库表导出到CSV压缩文件的方法 在导出大量CSV数据的时候,常常体积较大,采用C#自带的压缩类,可以方便的实现该功能,并且压缩比例很高,该方法在我的开源工具DataPie中已经经 ...