简单实现tcp/ip下的文件断点续传
其實在tcp/ip協議中傳輸文件可以保證傳輸的有效性,但有一個問題文件傳了一部分連接意外斷開了怎樣;那這種情況只能在重新連接后繼續傳輸,由于文件那部分已經傳了那部分沒有完成并不是tcp/ip的范圍,所以需要自己來制定協議達到到這個目的。實現這個續傳的協議制定其實也是非常簡單,通過協議把文件按塊來劃分,每完成一個塊就打上一個標記;即使是連接斷了通過標記狀態就知道還需要傳那些內容。下面通過beetle來實現一個簡單斷點續傳的程序(包括服務端和客戶端)。
在實現之前先整理一下流程思路,首先提交一個發送請求信息包括(文件名,塊大小,塊的數量等),等對方確認后就進行文件塊發送,對方接收塊寫入后返回一個標記,然后再繼續發直到所有發送完成。思路明確后就制定協了:
文件傳輸申請信息
public class Post:MessageBase
{
public string FileName;
public long Size;
public int PackageSize;
public int Packages;
public Post()
{
FileID = Guid.NewGuid().ToString("N");
}
}
public class PostResponse : MessageBase
{
public string Status;
}
FileID這個值是用來協同工作的,兩端根據這個ID來找到具體操作的文件和相關信息;Response提供了一個Status屬性,可以用來提供一個錯誤的描述,如果無有任何值的情況說明對方允許這個行為.
文件塊傳輸信息
public class PostPackage:MessageBase
{
public byte[] Data;
public int Index;
}
public class PostPackageResponse : MessageBase
{
public int Index;
public string Status;
}
文件塊傳輸也是一個請求,一個應答;分別帶的信息就是塊數據信息和塊的位置,同樣也是根據Status信息來標記塊的處理是否成功。
結構定義完成了,那就進行邏輯處理部分;不過為了調用更方便還需要封裝一些東西,如根據塊大小來劃分文件塊的數目,獲取某一文件塊的內容和寫入文件某一些的內容等功能。
public static int GetFilePackages(long filesize)
{
int count;
if (filesize % PackageSize > 0)
{
count = Convert.ToInt32(filesize / PackageSize) + 1;
}
else
{
count = Convert.ToInt32(filesize / PackageSize);
}
return count;
}
public static byte[] FileRead(string filename, int index, int size)
{
using (Smark.Core.ObjectEnter oe = new Smark.Core.ObjectEnter(filename))
{
byte[] resutl = null;
long length = (long)index * (long)size + size;
using (System.IO.FileStream stream = System.IO.File.OpenRead(filename))
{
if (length > stream.Length)
{
resutl = new byte[stream.Length - ((long)index * (long)size)];
}
else
{
resutl = new byte[size];
}
stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin);
stream.Read(resutl, 0, resutl.Length);
}
return resutl;
}
}
public static void FileWrite(string filename, int index, int size, byte[] data)
{
using (Smark.Core.ObjectEnter oe = new Smark.Core.ObjectEnter(filename))
{
using (System.IO.FileStream stream = System.IO.File.OpenWrite(filename))
{
stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin);
stream.Write(data, 0, data.Length);
stream.Flush();
}
}
}
準備工作完成了,就開始寫接收端的代碼了。之前的文章已經介紹了Beetle如果創建一個服務和綁定分包機制,在這里就不多說了;看下接收的邏輯是怎樣處理了.
接收傳文件請求
public void Post(ChannelAdapter adapter, Beetle.FileTransfer.Post e)
{
string file = txtFolder.Text + e.FileName;
PostResponse response = new PostResponse();
response.FileID = e.FileID;
response.ID = e.ID;
try
{
if (FileTransferUtils.CreateFile(file, e.Size))
{
Logics.FileItem item = new Logics.FileItem();
item.FileID = e.FileID;
item.FileName = file;
item.Packages = e.Packages;
item.PackageSize = e.PackageSize;
item.Completed = 0;
item.Size = e.Size;
Logics.Access.Update(item);
AddItem(item);
}
else
{
response.Status = "不能創建文件!";
}
}
catch (Exception e_)
{
response.Status = e_.Message;
}
adapter.Send(response);
}
接收請求后根據信息創建臨時文件,創建成功就把文件相關信息保存到數據庫中,如果失敗或處理異常就設置相關Status信息返回.
接收文件塊請求
public void PostPackage(ChannelAdapter adapter, Beetle.FileTransfer.PostPackage e)
{
PostPackageResponse response = new PostPackageResponse();
response.FileID = e.FileID;
response.ID = e.ID;
try
{
Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID);
if (item != null)
{
FileTransferUtils.FileWrite(
item.Item.FileName + ".up", e.Index, item.Item.PackageSize, e.Data);
item.Completed(e.Index);
response.Index = e.Index;
if (item.Status == Logics.FileItemStatus.Completed)
FileTransferUtils.Rename(item.Item.FileName);
}
else
{
response.Status = "不存在上傳信息!";
}
}
catch (Exception e_)
{
response.Status = e_.Message;
}
adapter.Send(response);
}
接收塊請求后處理也很簡單,根據FileID獲取相關信息,然后把數據寫入到文件對應的位置中;當所有塊都已經完成后把臨時文件名改會來就行了。如果處理異常很簡單通過設置到Status成員中告訴請求方。
以下就是請求端的代碼了,其代碼比接收端更加簡單了
public void PostResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostResponse e)
{
mResponse = e;
mResetEvent.Set();
}
public void PostPackageResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostPackageResponse e)
{
Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID);
if (item != null)
{
if (string.IsNullOrEmpty(e.Status))
{
item.Completed(e.Index);
PostPacakge(item);
}
else
item.Status = Logics.FileItemStatus.Default;
}
}
private void PostPacakge(Logics.FileListItem item)
{
if (mChannel != null && mChannel.Socket != null && item.Status == Logics.FileItemStatus.Working
&& item.Item.Completed != item.Item.Packages)
{
PostPackage post = new PostPackage();
post.FileID = item.Item.FileID;
post.Index = item.Item.Completed;
post.Data = FileTransferUtils.FileRead(item.Item.FileName,
item.Item.Completed, item.Item.PackageSize);
mAdapter.Send(post);
}
}
請求端要做的工作就是發送文件傳輸請求,等回應后就處理PostPacakge進行文件塊發送,接收到當前文件塊處理成功后就發送下一塊直接完成。
到這里斷點續傳的功能代碼就已經完成,兩邊的程序已經可以工作。不過對于一些使用者來說希望程序更友好的表現工作情況,這個時候還得對UI下一點功夫,如看到當前傳輸的狀態和每個文件進度情況等。
以上效果看起來很不錯,那接下來就把它實現吧,程序使用ListBox來顯示傳輸文件信息,要達到以上效果需要簡單地重寫一下OnDrawItem達到我們需要的。在講述代碼之前介紹一個圖標網站http://www.iconfinder.com/,畢竟好的圖標可以讓程序生色不少。下面看下這個重寫的代碼:
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
StringFormat ListSF;
Point imgpoint = new Point(e.Bounds.X + 2, e.Bounds.Y + 1);
ListSF = StringFormat.GenericDefault;
ListSF.LineAlignment = StringAlignment.Center;
ListSF.FormatFlags = StringFormatFlags.LineLimit | StringFormatFlags.NoWrap;
ListSF.Trimming = StringTrimming.EllipsisCharacter;
Rectangle labelrect = new Rectangle(e.Bounds.X + 44, e.Bounds.Y, e.Bounds.Width - 44, e.Bounds.Height);
if (Site == null || Site.DesignMode == false)
{
if (e.Index >= 0)
{
FileListItem item = (FileListItem)Items[e.Index];
LinearGradientBrush brush;
brush =
new LinearGradientBrush(e.Bounds, Color.FromArgb(208, 231, 253),
Color.FromArgb(10, 94, 177), LinearGradientMode.Horizontal);
double pent = (double)item.Item.Completed / (double)item.Item.Packages;
using (brush)
{
e.Graphics.FillRectangle(brush, e.Bounds.X + 40, e.Bounds.Y + 2, Convert.ToInt32((e.Bounds.Width - 40) * pent), e.Bounds.Height - 4);
}
if (item.Status == FileItemStatus.Working)
{
mImgList.Draw(e.Graphics, imgpoint, 1);
}
else if (item.Status == FileItemStatus.Completed)
{
mImgList.Draw(e.Graphics, imgpoint, 2);
}
else
{
mImgList.Draw(e.Graphics, imgpoint, 0);
}
e.Graphics.DrawString(item.ToString(),new Font("Ariel", 9), new SolidBrush(Color.Black),labelrect, ListSF);
}
}
}
重繪代碼就是根據當前文件的進度內容來計算出填沖的寬度,還有根據當前文件狀態繪制不同的圖標,是不是比較簡單:)
整個功能完成了看下總體的效果怎樣:
下載完整代碼
FileTransfer.rar (649.79 kb)
如果需要Smark名稱空間的代碼可以到http://smark.codeplex.com/
訪問Beetlex的Github
總結
以上是生活随笔為你收集整理的简单实现tcp/ip下的文件断点续传的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 意难平是什么意思(你知道什么叫意难平吗?
- 下一篇: 断掌纹图片(断掌的宝宝智力真的有问题吗)