(源码下载地址,http://download.youkuaiyun.com/source/406996)
要实现断点续传,必须要得到上次的下载信息。这里使用的是最简单的单线程断点续传,因此需要的信息也非常少,在SysConfig.ini中保存了下面三个信息:LastDownFile--最后下载的文件在服务器上的文件名,LastDownFilePath--最后下载的文件在本地保存的路径,LastDownFileLength--最后下载的文件的文件长度。(每次下载文件时,如果要下载的文件和最后一次下载的一样,就进行断点续传,否则重新下载。)
每当点击“下载文件”按钮,程序先判断左边的列表框中是否选择了文件:
if
(listBox1.SelectedItem
==
null
)
return
;
然后判断是否最后下载过的文件 如果是,则判断未下载完成的文件是否存在,存在则继续下载:
string
fileName
=
listBox1.SelectedItem.ToString();
if
(fileName
==
_sysConfig.GetIniString(
"
LastDownFile
"
,
""
))

...
{
if (File.Exists(_sysConfig.GetIniString("LastDownFilePath", "")))

...{
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)

...
{
TcpClient tcp = null;
NetworkStream stream = null;
try

...{

建立连接,与发送请求文件长度的命令#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


接收数据#region 接收数据
byte[] recData = new byte[1024];
int recLen = stream.Read(recData, 0, recData.Length);
command = new TCommand(recData, recLen);
#endregion


转换文件长度#region 转换文件长度
if (command.commandStyle != CommandStyleEnum.cGetFileLengthReturn)
return 0;

if (command.argList.Count == 0)
return 0;

long fileLength = 0;
try

...{
fileLength = Convert.ToInt64((string)command.argList[0]);
}
catch (Exception ex)

...{
MessageBox.Show(ex.Message);
return 0;
}
#endregion

return fileLength;
}
catch (Exception ex)

...{
MessageBox.Show(ex.Message);
return 0;
}
finally

...{

释放#region 释放
if (stream != null)
stream.Close();
if (tcp != null)
tcp.Close();
#endregion
}
}

分三个步骤:与服务器建立连接并发送请求文件长度的命令;接收数据;转换数据成文件长度。
得到文件长度后便建立文件:
string
destFileName
=
CreateNewFile(fileName);
if
(destFileName
==
""
)
return
;
建立文件的过程是由函数CreateNewFile来完成的:
private
string
CreateNewFile(
string
sourceFileName)

...
{
saveFileDialog1.FileName = sourceFileName;
if (saveFileDialog1.ShowDialog() == DialogResult.Cancel)
return "";

FileStream s = null;
try

...{
s = new FileStream(saveFileDialog1.FileName, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
s.SetLength(0);
}
finally

...{
if (s != null)
s.Close();
}

return saveFileDialog1.FileName;
}

最后,控制权也转交到了DownFile函数中:
DownFile(fileName, saveFileDialog1.FileName, fileLength);
DownFile的代码如下:
private
void
DownFile(
string
fileName,
string
path,
long
fileLength)

...
{

保存最后下载的信息#region 保存最后下载的信息
_sysConfig.SetIniString("LastDownFile", fileName);
_sysConfig.SetIniString("LastDownFilePath", path);
_sysConfig.SetIniString("LastDownFileLength", fileLength.ToString());
#endregion


初始化进度条#region 初始化进度条
p.Maximum = 100;
p.Minimum = 0;
p.Value = 0;
#endregion


启动下载线程#region 启动下载线程
try

...{
TcpClient tcp = new TcpClient();
tcp.Connect(_server, _port);

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)

...{
MessageBox.Show(ex.Message);
}
#endregion
}

它实际上的下载过程是由TclientConnection类的GetFile函数来实现的:
public
void
GetFile()

...
{

定义变量#region 定义变量
FileStream s;
long currentSize;
int p = 0;
#endregion


获取当前下载进度#region 获取当前下载进度
try

...{
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()

...{
_p.Value = p;
};
_p.Invoke(a);
}
catch (Exception ex)

...{
MessageBox.Show(ex.Message);
return;
}
#endregion


判断文件是否已经下载完毕#region 判断文件是否已经下载完毕
if (currentSize == _fileLength)

...{
MessageBox.Show("文件已经下载完毕!");
s.Close();
return;
}
#endregion


开始下载#region 开始下载
NetworkStream stream = _tcp.GetStream();
try

...{
TCommand command = new TCommand(CommandStyleEnum.cGetFile);
command.AppendArg(_fileName);
command.AppendArg(currentSize.ToString());
byte[] data = command.ToBytes();
stream.Write(data, 0, data.Length);

while (true)

...{
byte[] recData = new byte[1024];
int recLen = stream.Read(recData, 0, 1024);
if (recLen == 0)//断开
return;

s.Write(recData, 0, recLen);
currentSize += recLen;
p = Convert.ToInt32(((double)currentSize / (double)_fileLength) * 100);
if (p != _p.Value)

...{
dd a = delegate()

...{
_p.Value = p;
Application.DoEvents();
};
_p.Invoke(a);
}

if (currentSize == _fileLength)

...{
MessageBox.Show("下载完毕!");
return;
}
}
}
catch (Exception ex)

...{
MessageBox.Show(ex.Message);
}
finally

...{

释放#region 释放
stream.Close();
_tcp.Close();
s.Close();
#endregion
}
#endregion
}

GetFile函数先根据本地文件的大小与实际文件的大小得到下载进度,并在进度条上显示出来;然后判断是否下载完毕;如果没有下载完毕,则向服务器发送请求文件的命令,命令的参数是文件名和已接收数据的大小。最后,它接收从服务器返回的数据,写入文件流中。
在客户端的GetFileLength和GetFile函数中,都是先知道了服务器返回的数据的格式,才好进行接收的。
下面看服务端的实现。
首先,服务端要在TserverConnection类的ExtractRecStr中增加两个判断,将请求文件大小、请求文件的命令交给其他函数来处理:
private
void
ExtractRecStr(TCommand command, NetworkStream stream)

...
{
switch (command.commandStyle)

...{
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)

...
{
long fileLength = GetData.GetFileLength((string)command.argList[0]);

TCommand tempCommand = null;
if (fileLength == 0)
tempCommand = new TCommand(CommandStyleEnum.cGetFileLengthReturnNone);
else
tempCommand = new TCommand(CommandStyleEnum.cGetFileLengthReturn);

tempCommand.AppendArg(fileLength.ToString());

byte[] data = tempCommand.ToBytes();
stream.Write(data, 0, data.Length);
}

它从TCommand类对象的参数argList中取出文件名,然后用类GetData的函数GetFileLength返回文件大小;再根据文件大小生成返回命令,并发送到客户端。
再看函数OnGetFile:
private
void
OnGetFile(TCommand command, NetworkStream stream)

...
{

根据客户端已下载的字节数来定位文件流#region 根据客户端已下载的字节数来定位文件流
long currentSize = Convert.ToInt64((string)command.argList[1]);
long fileLength;
long needLength;

FileStream s = GetData.GetFileStream((string)command.argList[0]);
if (s == null)
return;

fileLength = s.Length;
if (currentSize >= fileLength)
return;
s.Position = currentSize;//如果已经下载了100字节,则position应该设置为100,即从100开始传输
#endregion

try

...{

发送数据#region 发送数据
while (true)

...{
needLength = fileLength - s.Position;
if (needLength == 0)
break;

if (needLength > 1024)
needLength = 1024;

byte[] data = new byte[1024];
int len = s.Read(data, 0, 1024);
if (len == 0)
break;

stream.Write(data, 0, len);
}
#endregion
}
catch (Exception)

...{
}
finally

...{
s.Close();
}
}

它首先从TCommand的对象中得到客户端已接收的字节数,然后将文件流定位到已发送数据的末尾,最后将剩下的文件以1024长度的字节数组不断地发送到客户端。
Over。
ie.2008-04-09