1、实例五 网络文件传输【程序说明】P2P(点对点) 的流行产生了大批网络传输软件,这里我们要介绍的就是自己写一个简单的P2P 文件传输,一方发送文件,一方接受,直到传输完整个文件。程序运行效果如图所示。服务器端:图 8.5.1客户端:图 8.5.2由服务器端负责发送文件,客户端接受。【编程思路】利用 TCP 协议连接双方,服务器端建立文件流读入待发送文件进入监听状态,客户端发送信号开始传输,服务器根据客户端发送的当前文件流传输位置按接收缓冲区大小一块一块的发送给客户端,客户端接受后再保存到接收文件流,直到整个文件流发送完毕,这里服务器端使用 TIdTCPServer 组件,客户端对应的使用 TI
2、dTCPClient 组件。【编程步骤】1.启动 Delphi7,建立一个标准的 Application,首先我们来做服务器端, 。2.按图放置如下组件:图 8.5.3将项目保存 Server 目录下,取名为 Server.dpr,单元取名为 U_Server.pas。3.然后我们再来看看客户端,这里由于我们实际上是做了两个程序(服务器端和客户端) ,因而引入一个新的概念:项目组(Project Group),使用项目组我们很方便的同时调试两个以上的程序,也因为如此,我们上面才需要更改项目名,单元名以区分服务端和客户端,使用项目组功能首先我们找到 DELPHI 的 IDE 菜单的 View 项
3、,打开 Project Manager(Ctrl+Alt+F11)即可看到一个项目组管理窗口,其中已经有我们刚才建立的 Server.exe 了,现在我们 New 新建一个标准的 Application 项目,按照下图放置组件:图 8.5.4将项目保存在 Client 目录下,取名 Client.dpr,单元名 U_Client.pas,现在我们可以看到,项目组窗口中多了一个 Client.exe 项目,其中项目名黑色加粗表示当前激活的项目。以上组件除了 Tlabel 组件、Tbutton 组件修改标题和 StatusBar1 修改 SimplePanel 为 True 外全部使用默认属性,属
4、性列表我们这里就省略了。4.编写代码:首先来看服务器端,浏览文件将文件名传给 Edit1:procedure Tfrm_Server.Button1Click(Sender: TObject);beginif OpenDialog1.Execute thenEdit1.Text := OpenDialog1.FileName;end;然后进入传输状态:procedure Tfrm_Server.Button2Click(Sender: TObject);beginif not FileExists(Edit1.Text) then /检测文件是否存在beginShowmessage(文件不存在
5、,请选择文件! );exit;end;/建立文件流AFileStream := TFileStream.Create(Edit1.Text, fmOpenRead);ProgressBar1.Max := AFileStream.Size; /初始化进度条的最大值ProgressBar1.Position := 0;ButtonBegin; /VCL 开始状态设置/服务器准备好连接IdTCPServer1.DefaultPort := StrToIntDef(Edit2.Text, 9925);if not IdTCPServer1.Active then IdTCPServer1.Activ
6、e := True;end;其中 IdTCPServer1.Active := True 即让服务器端听入监听状态,结束后取消此状态,实际上我们也可以程序一运行就让他开始监听,在进入监听状态前我们首先要设置服务器监听使用的端口:IdTCPServer1.DefaultPort := StrToIntDef(Edit2.Text, 9925);这句就设置了端口为 Edit2 的值,转换文本到数字失败就使用默认 9925 端口。ButtonBegin 是我们自己写的一个过程,目的是改变一些按钮的可操作状态,例如传输过程中不允许在选择文件等:procedure Tfrm_Server.ButtonB
7、egin;begin /VCL 开始状态设置Button1.Enabled := False; /不可浏览Button2.Enabled := False; /不可发送Button3.Enabled := True; /可以取消Button4.Enabled := False; /不可退出end;这种方法在组件很多,需要限定很多组件的状态时可以使程序可读性更好,也方便多次调用,同样后面用到的 ButtonEnd 过程也是一样,在取消按钮代码中:procedure Tfrm_Server.Button3Click(Sender: TObject);beginStatusBar1.SimpleTe
8、xt := 传输取消.;AFileStream.Free; /释放文件流ButtonEnd; /VCL 结束状态设置end;程序的重点就是如何进行传输了,IdTCPServer1 的 OnExecute 事件捕获到任何一个连接到服务器的进程 Athread 就激发此事件,这里我将在此代码注释中详细解释传输过程:procedure Tfrm_Server.IdTCPServer1Execute(AThread: TIdPeerThread);varcmd: string; /接收到客户端的字符串信息ASize: Integer; /需要传输的流大小beginwith AThread.Connec
9、tion do /已经连街上的一个进程begincmd := UpperCase(ReadLn); /客户端发送的命令字符串if cmd = BEGIN then /开始传输begin/告诉远程传输文件的大小和文件名WriteLn(Format(%d|%s, AFileStream.Size, ExtractFileName(Edit1.Text);StatusBar1.SimpleText := 准备传输.;Exit;end;if cmd = END thenbegin /传输完成Button3.Click;StatusBar1.SimpleText := 传输完成.;Exit;end;if
10、 cmd = CANCEL thenbegin /传输取消StatusBar1.SimpleText := 传输取消.;/保持传输状态Exit;end;/按照指定位置传输文件AFileStream.Seek(StrToInT(cmd), soFromBeginning); /转到文件流传输的位置ASize := Min(AFileStream.Size - AFileStream.Position, RecvBufferSize);/计算需要发送的大小,Min()函数在 Math 单元OpenWriteBuffer; /准备发送缓冲WriteStream(AFileStream, false,
11、 false, ASize);/注意这个函数的参数。CloseWriteBuffer; /结束发送缓冲StatusBar1.SimpleText := Format(当前传输位置%s/大小%d, cmd, AFileStream.Size);ProgressBar1.Position := ProgressBar1.Position + ASize;end;end;其中主要是 WriteStream()函数的使用注意参数:原型 WriteStream (AStream: TStream;const AAll: Boolean = True;const: Boolean = False;cons
12、t: Integer = 0); virtual;参数 AAll 表示是否一次全部发送;参数 AwriteByteCount 表示是否在发送的信息中包含大小信息;参数 ASize 表示发送的大小,这里可以用 F1 查看 Delphi 帮助获取帮助信息,也可以按住 Ctrl 键鼠标点击 WriteStream 直接察看他的实现代码获取参考信息,而上面代码中的Withdo 语法也是经常用到的,例如:with button1 dobegincaption:=电脑报;end;他表示在这个语法 beginend 中的代码如果有属性如 A:=C 属于 withdo 中的组件 B,则相当于 B.A:=C;上
13、面的例子实际上就是 Button1.Caption:=电脑报 。当然要深入理解上面的代码最好配合一下客户端的接收按钮的代码:procedure Tfrm_Client.Button1Click(Sender: TObject);varcmd: string;ASize, TotalSize: Int64;AFileStream: TFileStream;beginIdTCPClient1.Host := Edit1.Text; /连接主机IdTCPClient1.Port := StrToIntDef(Edit2.Text, 9925); /端口IdTCPClient1.Connect; /连
14、接tryIdTCPClient1.WriteLn(BEGIN); /提示服务器开始接收cmd := IdTCPClient1.ReadLn;/以“|”符号分离文件名SaveDialog1.FileName := Copy(cmd, Pos(|, cmd) + 1, Length(cmd);if not SaveDialog1.Execute thenbeginIdTCPClient1.WriteLn(CANCEL); /告诉服务器取消IdTCPClient1.Disconnect; /断开连接exit;end;TotalSize := StrToInt(Copy(cmd, 0, Pos(|,
15、cmd) - 1); /分离文件大小/建立文件流准备接收AFileStream := TFileStream.Create(SaveDialog1.FileName, fmCreate);try /循环开始接受repeatIdTCPClient1.WriteLn(IntToStr(AFileStream.Size);/发送当前传输的位置ASize := Min(TotalSize - AFileStream.Size, IdTCPClient1.RecvBufferSize);/选择剩余大小和缓冲区大小小的一个作为传输的大小IdTCPClient1.ReadStream(AFileStream
16、, ASize); /接收流StatusBar1.SimpleText := Format(当前传输位置%d/大小%d, AFileStream.Size, TotalSize);Application.ProcessMessages;until AFileStream.Size = TotalSize; /大小一致了表示结束finallyAFileStream.Free; /释放文件流end;IdTCPClient1.WriteLn(END); /提示服务器传输完成StatusBar1.SimpleText := 传输完成.;exceptStatusBar1.SimpleText := 连接
17、服务器失败或者对方已经中断传输!;end;IdTCPClient1.Disconnect;end;5.主要代码这里就介绍完了,F9 分别运行一下服务器端和客户端,编译好 exe 文件后运行两个程序再传输一个文件看看效果。【程序小结】本程序主要的功能由 TIdTCPServer 和 TIdTCPClient 组件完成,通过本例我们主要掌握文件流的分段传输,C/S 模式的通信, withdo 结构的用法,项目组的使用等。这里只是一个演示,因而我们只做了单向传输,也就是只能服务器端发文件给客户端,程序稍加修改即可做成双向的,留给读者思考吧。【作者后话】TIdTCPServer 和 TIdTCPCli
18、ent 组件本身就具有自动分割大文件流传输的功能,察看TIdTCPConnection.WriteStream 的代码可以看到它的分段方式和我们上面介绍的方法相似,假设我们的服务端发送代码改为:with AThread.Connection dobeginOpenWriteBuffer;WriteStream(AFileStream, true, false, AFileStream.Size);/注意上面的参数,参数二发送全部数据,参数三不发送大小,参数四发送的大小为文件流大小CloseWriteBuffer;Disconnect;end;接收端接收代码改为:IdTCPClient1.ReadStream(AFileStream, -1, true); /读入全部直到结束服务端Disconnect 的。这种接收方式不需要提前知道文件流大小,它将自动一直接收数据直到断开连接,使用这种方法代码将更简单。