断点续传、多线程上载【转:http://www.cnblogs.com/dlwang2002/archive/2008/09/12/1290017.html】
现在已经有很多断点续传、多线程下载的软件了,比如网际快车等等。下面设计的程序是“断点续传、多线程上载”。
缘起:客户每天都有大量文件上传服务器。这些文件很多,并且体积挺大,FTP有时候会出一些问题,导致传递失败,要重新上传。
基本解决方案:
1:把文件分割成块,每次只是传递一个文件块。
2:一个文件可以起多个发送任务(线程),同时发送。
3:记录文件发送状态,在网络出现问题时(或者客户端意外终止),知道上次发送文件大小和位置指针。再重新链接以后,继续发送。
对象和线程
这里面涉及到一个显示窗体form1,有timer可以随时更新发送状态;一个上传类Uploader(对应于一个文件);Task对象(也就是一个文件);FileThunk对象(每一个任务,对应于一个线程);WebService接受文件类。
发送状态需要记录在数据库。测试状态下,数据记录在xml文件。基本格式如下:
Codetasks>
<task name="contact.txt" percentage="" totalSeconds="" localFile="" remoteFile="" fileSize="">
<thread name="thread1" begin="" end="" lastTime=""></thread>
</task>
</tasks>
<task name="contact.txt" percentage="" totalSeconds="" localFile="" remoteFile="" fileSize="">
<thread name="thread1" begin="" end="" lastTime=""></thread>
</task>
</tasks>
数据表结构也基本类似这样。
核心代码:
1:form1. 这个主要是显示。主要函数是 添加任务(Task);更新任务状态
浏览文件,创建任务 private void btn_browse_Click(object sender, EventArgs e)
2 {
3 //browse to select files
4 OpenFileDialog ofd = new OpenFileDialog();
5 ofd.Multiselect = true;
6 DialogResult dr= ofd.ShowDialog();
7
8 _taskList = new ArrayList();
9 for (int i = 0; i < ofd.FileNames.Length; i++)
10 {
11 Task t = new Task();
12 t.FileChunkCount = Convert.ToInt32(this.txt_threadsPerFile.Text);
13 t.LocalFile = ofd.FileNames[i];
14 t.RemoteFile = Path.GetFileName(t.LocalFile);
15 t.Name = Path.GetFileName(t.LocalFile);
16 //t.Percentage=0;
17 t.TotalSeconds=0;
18
19 t.Init(); //init, split the file to upload
20
21 _taskList.Add(t);
22
23 }
24 //show in UI
25 this.dataGridView1.DataSource = GetUpdatedStatusAsDT(_taskList,0);
26 }
2 {
3 //browse to select files
4 OpenFileDialog ofd = new OpenFileDialog();
5 ofd.Multiselect = true;
6 DialogResult dr= ofd.ShowDialog();
7
8 _taskList = new ArrayList();
9 for (int i = 0; i < ofd.FileNames.Length; i++)
10 {
11 Task t = new Task();
12 t.FileChunkCount = Convert.ToInt32(this.txt_threadsPerFile.Text);
13 t.LocalFile = ofd.FileNames[i];
14 t.RemoteFile = Path.GetFileName(t.LocalFile);
15 t.Name = Path.GetFileName(t.LocalFile);
16 //t.Percentage=0;
17 t.TotalSeconds=0;
18
19 t.Init(); //init, split the file to upload
20
21 _taskList.Add(t);
22
23 }
24 //show in UI
25 this.dataGridView1.DataSource = GetUpdatedStatusAsDT(_taskList,0);
26 }
显示和更新状态,返回DataTable private DataTable GetUpdatedStatusAsDT(ArrayList list, int seconds)
2 {
3 DataTable dt = new DataTable();
4 dt.Columns.Add("Name");
5 dt.Columns.Add("FileSize");
6 dt.Columns.Add("Percentage");
7 dt.Columns.Add("TotalSeconds");
8 dt.Columns.Add("LocalFile");
9 dt.Columns.Add("RemoteFile");
10
11 for (int i = 0; i < list.Count; i++)
12 {
13 //update task status to db
14 Task t = (list[i] as Task);
15 t.TotalSeconds += seconds;//running time
16 t.Save();//save the status to db, so next time can load the status to continue.
17
18 DataRow dr = dt.NewRow();
19 dr["Name"] = t.Name;
20 dr["FileSize"] = t.FileSize;
21 dr["Percentage"] = t.Percentage;
22 dr["TotalSeconds"] = t.TotalSeconds;
23 dr["LocalFile"] = t.LocalFile;
24 dr["RemoteFile"] = t.RemoteFile;
25
26 dt.Rows.Add(dr);
27 }
28 return dt;
29 }
2 {
3 DataTable dt = new DataTable();
4 dt.Columns.Add("Name");
5 dt.Columns.Add("FileSize");
6 dt.Columns.Add("Percentage");
7 dt.Columns.Add("TotalSeconds");
8 dt.Columns.Add("LocalFile");
9 dt.Columns.Add("RemoteFile");
10
11 for (int i = 0; i < list.Count; i++)
12 {
13 //update task status to db
14 Task t = (list[i] as Task);
15 t.TotalSeconds += seconds;//running time
16 t.Save();//save the status to db, so next time can load the status to continue.
17
18 DataRow dr = dt.NewRow();
19 dr["Name"] = t.Name;
20 dr["FileSize"] = t.FileSize;
21 dr["Percentage"] = t.Percentage;
22 dr["TotalSeconds"] = t.TotalSeconds;
23 dr["LocalFile"] = t.LocalFile;
24 dr["RemoteFile"] = t.RemoteFile;
25
26 dt.Rows.Add(dr);
27 }
28 return dt;
29 }
执行任务 private void btn_run_Click(object sender, EventArgs e)
2 {
3 //timmer to show the status
4 Timer timer = new Timer();
5 timer.Tick += new EventHandler(timer_Tick);
6 timer.Interval = 2 * 1000; //every 2 seconds
7 timer.Enabled = true;
8 timer.Start();
9 //
10 //continue
11 //find task from datasource (DB or xml) and continue
12 //ArrayList tasks = Task.LoadTasks();
13 //
14 ArrayList tasks = _taskList; // get the task list.
15 //
16 for (int i = 0; i < tasks.Count; i++)
17 {
18 Uploader uld = new Uploader(tasks[i] as Task);
19 uld.Strat();
20 }
21 }
22
2 {
3 //timmer to show the status
4 Timer timer = new Timer();
5 timer.Tick += new EventHandler(timer_Tick);
6 timer.Interval = 2 * 1000; //every 2 seconds
7 timer.Enabled = true;
8 timer.Start();
9 //
10 //continue
11 //find task from datasource (DB or xml) and continue
12 //ArrayList tasks = Task.LoadTasks();
13 //
14 ArrayList tasks = _taskList; // get the task list.
15 //
16 for (int i = 0; i < tasks.Count; i++)
17 {
18 Uploader uld = new Uploader(tasks[i] as Task);
19 uld.Strat();
20 }
21 }
22
2:Uploader的核心代码:主要是启动线程。
Code public void Strat()
2 {
3 //_dataReceiver.Open(this._task.RemoteFile);//init webservice
4
5 #region test
6 //if (!File.Exists(_task.RemoteFile))
7 //{
8 // FileInfo fi = new FileInfo(_task.LocalFile);
9
10 // FileStream fst = new FileStream(_task.RemoteFile, FileMode.CreateNew);
11
12 // BinaryWriter w = new BinaryWriter(fst);
13 // byte[] ab = new byte[fi.Length];
14 // w.Write(ab);
15 // w.Close();
16 // fst.Close();
17 //}
18 //
19 #endregion
20
21 //main code. find each fileChunk, running in seperate thread
22 for (int i = 0; i < _task.FileChunks.Count; i++)
23 {
24 FileChunk fc = (_task.FileChunks[i] as FileChunk);
25 Thread thread = new Thread(new ThreadStart(fc.Upload));
26 fc.RunningThread = thread;//asign thread to the fileChucks, to stop the task
27 thread.Name = _task.Name + "_" + i.ToString();
28 thread.Start();
29 }
30 }
2 {
3 //_dataReceiver.Open(this._task.RemoteFile);//init webservice
4
5 #region test
6 //if (!File.Exists(_task.RemoteFile))
7 //{
8 // FileInfo fi = new FileInfo(_task.LocalFile);
9
10 // FileStream fst = new FileStream(_task.RemoteFile, FileMode.CreateNew);
11
12 // BinaryWriter w = new BinaryWriter(fst);
13 // byte[] ab = new byte[fi.Length];
14 // w.Write(ab);
15 // w.Close();
16 // fst.Close();
17 //}
18 //
19 #endregion
20
21 //main code. find each fileChunk, running in seperate thread
22 for (int i = 0; i < _task.FileChunks.Count; i++)
23 {
24 FileChunk fc = (_task.FileChunks[i] as FileChunk);
25 Thread thread = new Thread(new ThreadStart(fc.Upload));
26 fc.RunningThread = thread;//asign thread to the fileChucks, to stop the task
27 thread.Name = _task.Name + "_" + i.ToString();
28 thread.Start();
29 }
30 }
3:Task类的主要代码:主要是初始化任务,其他诸如和对数据的保存/提取
Code public void Init()
2 {
3 //create new
4 this.FileChunks = new ArrayList();
5
6 FileInfo fi = new FileInfo(LocalFile);
7 FileSize = fi.Length;
8
9 long block = FileSize / this.FileChunkCount;
10 long index = 0;
11 for (int i = 0; i < this.FileChunkCount; i++)
12 {
13 FileChunk fc = new FileChunk(this);
14 fc.Begin = index;
15 if (i==this.FileChunkCount-1)
16 block = FileSize - index;
17 index += block;
18 fc.End = index;
19 fc.LastTime = DateTime.Now;
20
21 this.FileChunks.Add(fc);
22 }
23 //
24
25 }
2 {
3 //create new
4 this.FileChunks = new ArrayList();
5
6 FileInfo fi = new FileInfo(LocalFile);
7 FileSize = fi.Length;
8
9 long block = FileSize / this.FileChunkCount;
10 long index = 0;
11 for (int i = 0; i < this.FileChunkCount; i++)
12 {
13 FileChunk fc = new FileChunk(this);
14 fc.Begin = index;
15 if (i==this.FileChunkCount-1)
16 block = FileSize - index;
17 index += block;
18 fc.End = index;
19 fc.LastTime = DateTime.Now;
20
21 this.FileChunks.Add(fc);
22 }
23 //
24
25 }
4:FileChunk的核心代码:主要是如何上传文件
Code /// <summary>
2 /// main function
3 /// </summary>
4 public void Upload()
5 {
6 Stream fs = File.OpenRead(_task.LocalFile);
7
8 long len = 2 * 64 * 1024;// 2 * 64 * 1024;
9
10 while (Begin < End)
11 {
12 if (End - Begin < len)
13 len = End - Begin;
14 byte[] data = new byte[len];
15 fs.Position = Begin;
16 fs.Read(data, 0, (int)len);
17
18 #region local test
19 //Stream fs2 = File.OpenWrite(_task.RemoteFile);
20 //fs2.Position = Begin;
21 //fs2.Write(data, 0, (int)len);
22 //fs2.Flush();
23 //fs2.Close();
24 //
25 #endregion
26
27 //call webservice to receive the data. this can be a socket also.
28 string res = this._task.DataReceiver.Receive(_task.RemoteFile, Begin, len, data);
29 if (res == "ok")
30 {
31 //
32 Begin += len;
33 _task.Save();
34 }
35 else
36 {
37 //wait for next while,
38 }
39
40 }
41 }
2 /// main function
3 /// </summary>
4 public void Upload()
5 {
6 Stream fs = File.OpenRead(_task.LocalFile);
7
8 long len = 2 * 64 * 1024;// 2 * 64 * 1024;
9
10 while (Begin < End)
11 {
12 if (End - Begin < len)
13 len = End - Begin;
14 byte[] data = new byte[len];
15 fs.Position = Begin;
16 fs.Read(data, 0, (int)len);
17
18 #region local test
19 //Stream fs2 = File.OpenWrite(_task.RemoteFile);
20 //fs2.Position = Begin;
21 //fs2.Write(data, 0, (int)len);
22 //fs2.Flush();
23 //fs2.Close();
24 //
25 #endregion
26
27 //call webservice to receive the data. this can be a socket also.
28 string res = this._task.DataReceiver.Receive(_task.RemoteFile, Begin, len, data);
29 if (res == "ok")
30 {
31 //
32 Begin += len;
33 _task.Save();
34 }
35 else
36 {
37 //wait for next while,
38 }
39
40 }
41 }
5:WebService的核心代码
Codepublic string Receive(string file, long begin, long len, byte[] data)
{
_fileName = AppDomain.CurrentDomain.BaseDirectory + """tmp""" + file;
try
{
_fs = File.OpenWrite(_fileName);
_fs.Position = begin;
_fs.Write(data, 0, (int)len);
_fs.Flush();
_fs.Close();
_fs = null;
return "ok";
}
catch (Exception ex)
{
string s = ex.Message;
return "retry";
}
}
{
_fileName = AppDomain.CurrentDomain.BaseDirectory + """tmp""" + file;
try
{
_fs = File.OpenWrite(_fileName);
_fs.Position = begin;
_fs.Write(data, 0, (int)len);
_fs.Flush();
_fs.Close();
_fs = null;
return "ok";
}
catch (Exception ex)
{
string s = ex.Message;
return "retry";
}
}
小结:
技术难度不大;现实实用;上传速度尚可。
程序运行截图一个