关于文件的上传、下载!那位兄弟可以给出完整的代码呢 ? ( 积分: 200 )

  • 主题发起人 主题发起人 oiget
  • 开始时间 开始时间
O

oiget

Unregistered / Unconfirmed
GUEST, unregistred user!
文档:
Socket通信在Windows 中是排队的形式由操作系统处理,而且接收方和发送方相互协同工作,否则就会造成数据丢失。因此,不能用类似于for 语句的循环来实现对多组数据的发送,更不能用循环语句来接收数据。比如,你可以用for 语句来实型若干文件的复制,这很普遍也很正常,但在 Socket编程以及大多数网络应用编程中都是行不通的,因为网络通信的基本方式是请求和应答。另外,和所有的通信编程一样,Socket编程也遵循数据分包传送这一基本规则。也就是说,在 Socket编程中,每次发送和接收一个包,以保证数据传输的安全性和稳定性,同时也不至于过多地占用系统资源。

对于ClientSocket组件,从字面上就可以看出,它用于请求方。也就是说,它的动作是主动地建立连接。显然,ServerSocket组件用于响应方,它的动作是侦听以及被动接受连接。

组件ClientSocket的属性是相对静态的,它和ServerSocket之间只是连接和断开的关系。并且仅当ServerSocket对其接受才表示建立连接。

组件ServerSocket的属性是动态的。伴随着一个新的ClientSocket与之建立连接的同时,就会产生一个新的Socket与该ClientSocket对应,保持单独的连接,进行单独的通信。因此,在同一个 ServerSocket中,可以与多个ClientSocket保持同时连接和各自独立的通信。ServerSocket的属性 Socket.ActiveConnections用于表示客户端连接的数量;属性Socket.Connections[Index] 则用于访问单个与ClientSocket连接的Socket。

正是这样的结构,才使得WinSocket 技术能够稳定实现一个服务程序向多个客户端提供服务。

在独立的ClientSocket中,属性Socket.Data 是一个指针,缺省值是nil ;在ServerSocket的每个独立的Socket.Connections[Index]中, 属性Data也是一个指针,缺省值是nil 。因此,可以通过该指针建立并保存各自独立的相关信息,用于实现各自独立的通信。而在ClientSocket的事件 OnRead中,调用方法传递的Socket值就是响应该事件的对象属性ClientSocket.Socket 。同样,在 ServerSocket的事件OnClientRead中,调用方法传递的参数Socket就是对应于当前发送数据客户端的唯一的Socket连接,即 ServerSocket.Socket.Connections[Index]。这样,就能够对不同的连接分得清清楚楚明明白白。

首先介绍实例程序的设计思想。上传文件的过程是这样的(这里的C和S分别代表客户端和服务器端):

C:请求上传文件;
S:准备就绪,可以接收;
C:需要上传的文件信息;
S:收到文件信息:
C:第一个包;
S:收到第一个包;创建文件,开始写数据;
C:中间的包;
S:收到中间的包;继续写数据;
C:发送最后一个包,关闭文件;
S:收到最后一个包;写数据,关闭文件。
下载文件的过程是这样的:

C:请求下载文件;
S:准备就绪,可以下载;
C:需要下载的文件信息(文件名);
S:反馈文件信息(文件大小);
C:准备就绪,可以接收数据;
S:第一个包;
C:收到第一个包;创建文件,开始写数据;
S:中间的包;
C:收到中间的包;继续写数据;
S:发送最后一个包,关闭文件;
C:收到最后一个包;写数据,关闭文件;下载成功;
S:下载成功。
其中,发送中间的包和收到中间的包根据包的数量可以重复。不难看出,上面的两个过程是典型的“你一句我一句”的应答方式。

下面是客户端应用程序和服务器端应用程序的结构。客户端应用程序包括:

Client.DPR
uClient.PAS(.DFM)(一个ClientSocket组件、一个按钮、一个标签、一个进度条)
uClientMain.PAS(.DFM)(用于选择文件的一组控件和一个Edit控件、三个按钮)
uSocketCommon.PAS
服务器端应用程序包括:

Server.DPR
uServer.PAS(.DFM)(一个ServerSocket组件、一个Memo控件、两个按钮)
uSocketCommon.PAS
其中,单元uSocketCommon 中包括了Socket编程的主要代码,是客户端应用程序和服务器端应用程序都需要的。

结合本例,可以对Delphi中的WinSocket编程作如下总结:

数据收发是通过会话建立和撤消的;
客户端是主动连接,服务程序是被动连接;
每次收发的数据包,其容量是有限的,应当在设计时充分考虑;
一个ClientSocket只能建立一个与ServerSocket的连接;
一个ServerSocket可以建立多个与ClientSocket的连接;
每一对连接都有唯一用于该连接的一对(两个)Socket,可以通过Data属性进行标记区分;
不要对无效的数据包进行响应,否则可能会导致服务程序死锁;
可以在传送的包中包含身份验证信息以确认是有效的数据。
 
刚才在网上看到了有个uSocketCommon.PAS,不知道是否就是该uSocketCommon.PAS呢 ?!
但别的代码就没见到了 !
{------------------- uSocketCommon.PAS -------------------}

{ 下面是客户端和服务器端都要用到的公共单元uSocketCommon.PAS。}
{ 该单元是客户端应用程序和服务器端应用程序的核心部分,应用 }
{ 程序中的常量、类型、过程、函数等都在本单元中声明。 }

unit uSocketCommon;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
FileCtrl, StdCtrls, ScktComp, ComCtrls;

const

{ 下面是一组应用程序中用到的字符串常量 }
sConnectedOK = '建立连接';
sConnecting = '正在连接...';
sDisconnectedOK = '断开连接';
sSaving = '正在发送...';
sLoading = '正在接收...';
sConnectError = '连接失败!请检查相关设置';
sMessageBoxCaption = '消息';

sPercentFmt = '已经完成 %D%%';{ 用于显示数据传输完成的百分比 }

{ 下面是动词列表:
前缀是vc的动词表由客户端发出;前缀是vs的动词由服务器端发出。 }

vcNone = 8000;{ 无效的动词 }
vsNone = 8000;{ 无效的动词 }

vcCancel = 9001;{ 取消操作 }
vsEchoCancel = 9002;{ 响应取消 }
vcFail = 9003;{ 操作失败 }
vsEchoFail = 9004;{ 响应失败 }
vsFail = 9005;{ 操作失败 }

vcSave = 1001;{ 请求发送,即保存到服务器中 }
vsReadyToSave = 1002;{ 接收和保存文件准备就绪 }
vcSaveInfo = 1003;{ 要保存文件的信息 }
vsSaveInfoOK = 1004;{ 正确收到保存文件的信息 }
vcFirstBuf = 1005;{ 发送第一个包 }
vsFirstBufOK = 1006;{ 正确收到第一个包 }
vcCommonBuf = 1007;{ 发送中间的包 }
vsCommonBufOK = 1008;{ 正确收到中间的包 }
vcLastBuf = 1009;{ 发送最后一个包 }
vsSaveOK = 1010;{ 正确接收完毕即保存成功 }

vcLoad = 2001;{ 请求接收,即从服务器装入 }
vsReadyToLoad = 2002;{ 向客户端发送文件准备就绪 }
vcLoadInfo = 2003;{ 需要接收文件的信息-文件名 }
vsLoadInfoOK = 2004;{ 正确收到并返回文件大小 }
vcReadyToLoad = 2005;{ 接收文件准备就绪 }
vsFirstBuf = 2006;{ 发送第一个包 }
vcFirstBufOK = 2007;{ 正确收到第一个包 }
vsCommonBuf = 2008;{ 发送中间的包 }
vcCommonBufOK = 2009;{ 正确收到中间的包 }
vsLastBuf = 2010;{ 发送最后一个包 }
vcLoadOK = 2011;{ 正确接收完毕即接收成功 }
vsLoadOK = 2012;{ 接收成功 }

ServerSocketPort = $ABCD;{ 端口号,可任意设置,尽可能不要与其它端口号重复 }

DataLen = 4096; { 数据包的最大尺寸是4K字节 }
LeadLen = 20; { 引导包的固定尺寸是20字节 }
{ 引导包中包括 16 个字节的身份识别代码和 4 个字节动词代码 }
SendLen = LeadLen + DataLen; { 发送包或接收包的最到尺寸是 (4096 + 20) 字节 }

type

TDataBuf = array [0..DataLen - 1] of Char; { 数据包缓存 }
TLeadBuf = array [0..LeadLen - 1] of Char; { 引导包缓存 }
TSendBuf = array [0..SendLen - 1] of Char; { 发送包或接收包缓存 }

TFileOfChar = file of Char; { 字符文件,用于接收方保存文件 }

TSocketMode = (smSave, smLoad, smNone); { 用于表示Socket当前的状态 }

TSocketData = record { 用于保存与当前Socket相关的信息 }
OnLine: Boolean; { 连接状态 }
Mode: TSocketMode; { 工作状态 }
SrcFileName: string; { 源文件名 }
DstFileName: string; { 目标文件名 }
FS: TFileStream; { 文件流 }
FSEnabled: Boolean; { 文件流状态 }
F: TFileOfChar; { 文件 }
FEnabled: Boolean; { 文件状态 }
FileSize: Integer; { 文件尺寸 }
LeftSize: Integer; { 剩余尺寸 }
ProgressBar: TProgressBar; { 进度条 }
ALabel: TLabel; { 进度标签 }
end;

PSocketData = ^TSocketData; { 类型指针,类型为TSocketData类型 }

TSocketVerb = Integer; { 动词类型 }

{ 判定动词是否含有终止意图 }
function IsTerminateVerb(AVerb: TSocketVerb): Boolean;

{ 报告错误 }
procedure ShowError(AHandle: THandle; S: string);

{ 传输日志 }
procedure Log(S: string; AMemo: TMemo);

{ 重置相关数据 }
procedure ResetSocketData(var P: PSocketData);

{ 新建Socket的相关数据,返回指针 }
function NewSocketData: PSocketData;

{ 将得到的包分解成为引导包和数据包 }
procedure ExtractBuf(Buf: TSendBuf; BufSize: Integer;
var LBuf: TLeadBuf; var DBuf: TDataBuf);

{ 分解引导包得到引导包中包含的动词 }
function ExtractVerb(LBuf: TLeadBuf): TSocketVerb;

{ 根据指定的动词初始化需要发送的包 }
procedure MakeVerbBuf(AVerb: TSocketVerb; var Buf: TSendBuf;
var SendSize: Integer);

{ 将数据包写入要发送的包中 }
procedure MakeSendBuf(DataBuf: TDataBuf; Count: Integer;
var SendBuf: TSendBuf; var SendSize: Integer);

{ 服务器端响应和处理客户端动词,这里的处理是服务器端程序的核心部分 }
procedure ServerEchoForVerb(AVerb: TSocketVerb; DataBuf: TDataBuf; DL: Integer;
var SendBuf: TSendBuf; var SendSize: Integer;
AServerSocket: TCustomWinSocket);

{ 客户端响应和处理服务器端动词,这里的处理是客户端程序的核心部分 }
procedure ClientEchoForVerb(AVerb: TSocketVerb; DataBuf: TDataBuf; DL: Integer;
var SendBuf: TSendBuf; var SendSize: Integer;
AClientSocket: TClientSocket);

implementation

const
IDString = 'SOCKET_TEST_DEMO'; { 引导包中的身份识别串,16个字符 }
LogTimeFormat = 'YYYY-MM-DD HH:MM:SS ';{ 用于登记传输日志的时间格式 }

DataSubDir = 'Data';{ 服务器端接收文件保存到应用程序所在目录的子目录Data中 }
TempSubDir = 'Temp';{ 客户端接收文件保存到应用程序所在目录的子目录Temp中 }

var
DataDir: string;{ 服务器端保存文件的路径 }
TempDir: string;{ 客户端保存文件的路径 }
 
后退
顶部