(源码下载地址,http://download.csdn.net/source/406996)
要实现断点续传,必须要得到上次的下载信息。这里使用的是最简单的单线程断点续传,因此需要的信息也非常少,在SysConfig.ini中保存了下面三个信息:LastDownFile--最后下载的文件在服务器上的文件名,LastDownFilePath--最后下载的文件在本地保存的路径,LastDownFileLength--最后下载的文件的文件长度。(每次下载文件时,如果要下载的文件和最后一次下载的一样,就进行断点续传,否则重新下载。)
每当点击“下载文件”按钮,程序先判断左边的列表框中是否选择了文件:
if
(listBox1.SelectedItem
==
null
)
return
;
然后判断是否最后下载过的文件 如果是,则判断未下载完成的文件是否存在,存在则继续下载:
string
fileName
=
listBox1.SelectedItem.ToString();
if
(fileName
==
_sysConfig.GetIniString(
"
LastDownFile
"
,
""
))
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
if (File.Exists(_sysConfig.GetIniString("LastDownFilePath", "")))
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
DownFile(fileName,
_sysConfig.GetIniString("LastDownFilePath", ""),
Convert.ToInt64(_sysConfig.GetIniString("LastDownFileLength", "0")));
return;
}
}
其中下载的过程提交到了函数DownFile中,这个后面再说。
如果不是上一次下载的,则需要重新下载,首先从服务器得到文件的长度:
long
fileLength
=
GetFileLength(fileName);
if
(fileLength
==
0
)
return
;
如果文件长度为0,则退出下载,返回文件长度的函数是GetFileLength:
private
long
GetFileLength(
string
fileName)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
TcpClient tcp = null;
NetworkStream stream = null;
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
建立连接,与发送请求文件长度的命令#region 建立连接,与发送请求文件长度的命令
tcp = new TcpClient();
tcp.Connect(_server, _port);
stream = tcp.GetStream();
TCommand command = new TCommand(CommandStyleEnum.cGetFileLength);
command.AppendArg(fileName);
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
接收数据#region 接收数据
byte[] recData = new byte[1024];
int recLen = stream.Read(recData, 0, recData.Length);
command = new TCommand(recData, recLen);
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
转换文件长度#region 转换文件长度
if (command.commandStyle != CommandStyleEnum.cGetFileLengthReturn)
return 0;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (command.argList.Count == 0)
return 0;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
long fileLength = 0;
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
fileLength = Convert.ToInt64((string)command.argList[0]);
}
catch (Exception ex)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show(ex.Message);
return 0;
}
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
return fileLength;
}
catch (Exception ex)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show(ex.Message);
return 0;
}
finally
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
释放#region 释放
if (stream != null)
stream.Close();
if (tcp != null)
tcp.Close();
#endregion
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
分三个步骤:与服务器建立连接并发送请求文件长度的命令;接收数据;转换数据成文件长度。
得到文件长度后便建立文件:
string
destFileName
=
CreateNewFile(fileName);
if
(destFileName
==
""
)
return
;
建立文件的过程是由函数CreateNewFile来完成的:
private
string
CreateNewFile(
string
sourceFileName)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
saveFileDialog1.FileName = sourceFileName;
if (saveFileDialog1.ShowDialog() == DialogResult.Cancel)
return "";
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
FileStream s = null;
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
s = new FileStream(saveFileDialog1.FileName, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
s.SetLength(0);
}
finally
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
if (s != null)
s.Close();
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
return saveFileDialog1.FileName;
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
最后,控制权也转交到了DownFile函数中:
DownFile(fileName, saveFileDialog1.FileName, fileLength);
DownFile的代码如下:
private
void
DownFile(
string
fileName,
string
path,
long
fileLength)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
保存最后下载的信息#region 保存最后下载的信息
_sysConfig.SetIniString("LastDownFile", fileName);
_sysConfig.SetIniString("LastDownFilePath", path);
_sysConfig.SetIniString("LastDownFileLength", fileLength.ToString());
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
初始化进度条#region 初始化进度条
p.Maximum = 100;
p.Minimum = 0;
p.Value = 0;
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
启动下载线程#region 启动下载线程
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
TcpClient tcp = new TcpClient();
tcp.Connect(_server, _port);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
TclientConnection con = new TclientConnection(tcp, fileName, path, fileLength, p);
Thread t = new Thread(new ThreadStart(con.GetFile));
t.IsBackground = true;
t.Start();
}
catch (Exception ex)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show(ex.Message);
}
#endregion
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
它实际上的下载过程是由TclientConnection类的GetFile函数来实现的:
public
void
GetFile()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
定义变量#region 定义变量
FileStream s;
long currentSize;
int p = 0;
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
获取当前下载进度#region 获取当前下载进度
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
s = new FileStream(_path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
currentSize = s.Length;
s.Position = currentSize;
p = Convert.ToInt32(((double)currentSize / (double)_fileLength) * 100);
dd a = delegate()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
_p.Value = p;
};
_p.Invoke(a);
}
catch (Exception ex)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show(ex.Message);
return;
}
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
判断文件是否已经下载完毕#region 判断文件是否已经下载完毕
if (currentSize == _fileLength)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show("文件已经下载完毕!");
s.Close();
return;
}
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
开始下载#region 开始下载
NetworkStream stream = _tcp.GetStream();
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
TCommand command = new TCommand(CommandStyleEnum.cGetFile);
command.AppendArg(_fileName);
command.AppendArg(currentSize.ToString());
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
while (true)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
byte[] recData = new byte[1024];
int recLen = stream.Read(recData, 0, 1024);
if (recLen == 0)//断开
return;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
s.Write(recData, 0, recLen);
currentSize += recLen;
p = Convert.ToInt32(((double)currentSize / (double)_fileLength) * 100);
if (p != _p.Value)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
dd a = delegate()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
_p.Value = p;
Application.DoEvents();
};
_p.Invoke(a);
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (currentSize == _fileLength)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show("下载完毕!");
return;
}
}
}
catch (Exception ex)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
MessageBox.Show(ex.Message);
}
finally
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
释放#region 释放
stream.Close();
_tcp.Close();
s.Close();
#endregion
}
#endregion
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
GetFile函数先根据本地文件的大小与实际文件的大小得到下载进度,并在进度条上显示出来;然后判断是否下载完毕;如果没有下载完毕,则向服务器发送请求文件的命令,命令的参数是文件名和已接收数据的大小。最后,它接收从服务器返回的数据,写入文件流中。
在客户端的GetFileLength和GetFile函数中,都是先知道了服务器返回的数据的格式,才好进行接收的。
下面看服务端的实现。
首先,服务端要在TserverConnection类的ExtractRecStr中增加两个判断,将请求文件大小、请求文件的命令交给其他函数来处理:
private
void
ExtractRecStr(TCommand command, NetworkStream stream)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
switch (command.commandStyle)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
case CommandStyleEnum.cList:
OnGetFileList(stream);
break;
case CommandStyleEnum.cGetFileLength://请求文件大小
OnGetFileLength(command, stream);
break;
case CommandStyleEnum.cGetFile://请求文件
OnGetFile(command, stream);
break;
default:
break;
}
}
看向客户端返回文件大小的函数OnGetFileLength:
private
void
OnGetFileLength(TCommand command, NetworkStream stream)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
long fileLength = GetData.GetFileLength((string)command.argList[0]);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
TCommand tempCommand = null;
if (fileLength == 0)
tempCommand = new TCommand(CommandStyleEnum.cGetFileLengthReturnNone);
else
tempCommand = new TCommand(CommandStyleEnum.cGetFileLengthReturn);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
tempCommand.AppendArg(fileLength.ToString());
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
byte[] data = tempCommand.ToBytes();
stream.Write(data, 0, data.Length);
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
它从TCommand类对象的参数argList中取出文件名,然后用类GetData的函数GetFileLength返回文件大小;再根据文件大小生成返回命令,并发送到客户端。
再看函数OnGetFile:
private
void
OnGetFile(TCommand command, NetworkStream stream)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
根据客户端已下载的字节数来定位文件流#region 根据客户端已下载的字节数来定位文件流
long currentSize = Convert.ToInt64((string)command.argList[1]);
long fileLength;
long needLength;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
FileStream s = GetData.GetFileStream((string)command.argList[0]);
if (s == null)
return;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
fileLength = s.Length;
if (currentSize >= fileLength)
return;
s.Position = currentSize;//如果已经下载了100字节,则position应该设置为100,即从100开始传输
#endregion
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
发送数据#region 发送数据
while (true)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
needLength = fileLength - s.Position;
if (needLength == 0)
break;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
if (needLength > 1024)
needLength = 1024;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
byte[] data = new byte[1024];
int len = s.Read(data, 0, 1024);
if (len == 0)
break;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
stream.Write(data, 0, len);
}
#endregion
}
catch (Exception)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
}
finally
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
s.Close();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
它首先从TCommand的对象中得到客户端已接收的字节数,然后将文件流定位到已发送数据的末尾,最后将剩下的文件以1024长度的字节数组不断地发送到客户端。
Over。
ie.2008-04-09