利用ScktSrvr打造多功能Socket服务器.doc

上传人:scccc 文档编号:14032262 上传时间:2022-01-31 格式:DOC 页数:26 大小:117KB
返回 下载 相关 举报
利用ScktSrvr打造多功能Socket服务器.doc_第1页
第1页 / 共26页
利用ScktSrvr打造多功能Socket服务器.doc_第2页
第2页 / 共26页
利用ScktSrvr打造多功能Socket服务器.doc_第3页
第3页 / 共26页
亲,该文档总共26页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《利用ScktSrvr打造多功能Socket服务器.doc》由会员分享,可在线阅读,更多相关《利用ScktSrvr打造多功能Socket服务器.doc(26页珍藏版)》请在三一文库上搜索。

1、利用 ScktSrvr 打造多功能 Socket 服务器利用 ScktSrvr 打造多功能 Socket 服务器时间: 2009-1-14 来源: 作者:本站 编辑: admin 访 问次数: 37 【关闭】Socket 服务端编程中最重要的也是最难处理的工作便是客户请求的 处理和数据的接收和发送,如果每一个 Socket 服务器应用程序的开 发都要从头到尾处理这些事情的话, 人将会很累,也会浪费大量时间。 试想,如果有一个通用的程序把客户请求处理和数据的接收、 发送都 处理好了,程序员只需要在不同的应用中对接收到的数据进行不同的 解析并生成返回的数据包,再由这个通用程序将数据包传回客户端,

2、这样,程序设计的工作将会轻松许多。用 Delphi 进行过三层数据库应用开发的程序员一定对 Borland 公司的 Borland Socket Server(ScktSrvr.exe) 不陌生。这是一个典 型的 Socket 服务器程序,认真读过该软件的源程序的人一定会赞叹 其程序编写的高明。其程序风格堪称典范。 但它是专用于配合 Borland 的MIDAS进行多层应用开发的。它能不能让我们实现上面的设想,以便我们应用到不同的应用中去呢?随我来吧,你会有收获的。首先,让我们搞清楚它的工作方式和过程, 以便看能不能用它完 成我们的心愿,当然改动不能太大,否则我没耐心也没有能力去做。从主窗体的

3、代码开始: 不论是以系统服务方式启动程序或直接运 行程序,当程序运行时,都会执行主窗体初始化方法:TSocketForm.Initialize(FromService: Boolean);该方法代码简单易读, 为节省篇幅在此不列出它的源代码。 该方 法从注册表键“ HKEY_LOCAL_MACHSINOEFTWAREBorlandSocket Server ”中读取端口信息,每读到一个端口,则:创建一个TSocketDispatcher 的实例,并调用该实例的 ReadSettings 方法读 取注册表数据来初始化该实例,然后激活该实例。TSocketDispatcher 继承自 TServe

4、rSocket ,是服务端 Socket , 当激活时便进入监听状态,监听客户端连接。当有客户端连接时,触 发 TSocketDispatcher 实例的 GetThread 事件过程:procedure TSocketDispatcher.GetThread(Sender: TObject;ClientSocket: TServerClientWinSocket;var SocketThread: TServerClientThread);beginSocketThread := TSocketDispatcherThread.Create(False, ClientSocket,Inter

5、ceptGUID, Timeout, SocketForm.RegisteredAction.Checked, SocketForm.AllowXML.Checked); end;该事件过程为每一个客户端连接创建一个 TSocketDispatcherThread 类的服务线程为该客户端服务,其核心过 程就是 TSocketDispatcherThread 的 ClientExecute 方法。对该方法 的分析可以知道,它主要工作有两个:一是创建一个传送器对象 (TSocketTransport) 负责与客户端进行数据传输, 二是创建一个数据 块解析器对象( TDataBlockInterpr

6、eter )负责解析传送器对象接收 到的客户端请求数据包。procedure TSocketDispatcherThread.ClientExecute;varData: IDataBlock;msg: TMsg;Obj: ISendDataBlock;Event: THandle;WaitTime: DWord;beginColnitialize(nil); /初始化 COM寸象库trySynchronize(AddClient); / 显示客户信息FTransport := CreateServerTransport; /创建传送器寸象 ,注意 FTransport 和下面的 Flnter

7、preter 是线程寸象的属性而不是局 部变量tryEvent := FTransport.GetWaitEvent;PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE); /建立线程消息队列Getlnterface(lSendDataBlock, Obj); /获得TSocketDispatcherThread 线程寸象的 lSendDataBlock 接口if FRegisteredOnly then/ 创建数据块解析器寸象,注意 lSendDataBlock 接口实例 Obj 作为参数传入了 TDataBlocklnterpreter 的

8、Create 方法中Flnterpreter := TDataBlocklnterpreter.Create(Obj,SSockets) elseFlnterpreter := TDataBlocklnterpreter.Create(Obj,);tryObj := nil;if FTimeout = 0 thenWaitTime := lNFlNlTE elseWaitTime := 60000;while not Terminated and FTransport.Connected dotrycase MsgWaitForMultipleObjects(1, Event, False,W

9、aitTime, QS_ALLEVENTS) ofWAIT_OBJECT_0: beginWSAResetEvent(Event);Data := FTransport.Receive(False, 0); / 传 送器对象接收客户端数据if Assigned(Data) then / 接收 成功beginFLastActivity := Now;FInterpreter.InterpretData(Data); / 数 据块解析器对象对数据进行解析Data := nil;FLastActivity := Now;end;end;WAIT_OBJECT_0 + 1:while PeekMess

10、age(msg, 0, 0, 0, PM_REMOVE) do DispatchMessage(msg); WAIT_TIMEOUT:if (FTimeout 0) and (Now - FLastActivity) FTimeout) thenFTransport.Connected := False;end;exceptFTransport.Connected := False;end;finallyFInterpreter.Free; / 释放数据块解析器对象 FInterpreter := nil;end;finally释放传送器对象关闭COM寸象库删除显示的客户信息FTranspor

11、t := nil;/end;finallyCoUninitialize; /Synchronize(RemoveClient); / end;end;在代码中我们没有看到如何向客户端传回数据的过程, 这项工作 是由数据块解析器寸象、传送器寸象和接口 ISendDataBlock ( TSocketDispatcherThread 实现了该接口)共同协调完成的。从以 上代码我们注意到,线程对象的ISendDataBlock接口 (Obj变量)被 作为参数传入了 TDataBlockInterpreter 的 Create 方法中,实际上 也就是线程对象被传递到了数据块解析器对象中,后面我们将看到

12、, 数据块解析器完成数据解析后, 会创建一个新的数据块 (TDataBlock ) 对象来打包要返回到客户端的数据,然后调用 ISendDataBlock 接口 的Send方法(实际上是TSocketDispatcherThread 的Send方法)将 数据发送到客户端,而TSocketDispatcherThread 的Send方法最终 调用传送器对象 ( TSocketDispatcherThread 的 FTransport )的 Send 方法进行实际的数据传输。看下面的代码我们就清楚这一点: TSocketDispatcherThread.ISendDataBlock functio

13、n TSocketDispatcherThread.Send(const Data: IDataBlock;WaitForResult: Boolean): IDataBlock; begin/ 用传送器对象回传数据,其中 Data 是由数据块解析器创建的数据块对象,以接口类型参数的方式传到该函数FTransport.Send(Data);/ 当数据块解析器需要进行连续的数据回传 (如数据太大, 一次不 能不能回传所有数据)时,/ 它向 WaitForResult 参数传入 True ,SocketDispatcherThread 就会/ 在一次发送数据之后检索并解析客户端的回应, 决定是否继

14、续回 传数据。if WaitForResult thenwhile True dobeginResult := FTransport.Receive(True, 0); / 检索客户端回 应if Result = nil then break;if (Result.Signature and ResultSig) = ResultSig thenbreak elseFInterpreter.InterpretData(Result); / 解析客户端回 应end;end;从上面的简单分析我们知道, 在一次 C/S 会话过程中用到了几个 对象,分别是:传送器 (TSocketTransport)

15、 对象,数据块解析器 (TDataBlockInterpreter )对象,数据块( TDataBlock )对象,还 有就是 ISendDataBlock 接口,它由 TSocketDispatcherThread 实现。 而数据处理主要在前两者, 它们分工很明确, 而这两者的协调就是通 过后两者实现。对象间的明确分工和有序合作给我们改造提供了条件。 再看离我 们的设想有多远。 1、客户请求的处理: TSocketDispatcher 已经为 我们做得很好了,这方面我们基本不需要改动。 2、数据的接收:就 看传送器能不能接收不同类型的数据了, 若不能,再看方不方便派生 和使用新的传送器类。

16、3、发送数据:用 TSocketDispatcherThread 的 Send 方法就完成了,我们只需在解析请求后生成返回的数据块对 象,传递给该方法就可以了。 4、解析数据:不同的应用中对数据的解析肯定是不同的,只有用新的解析器类去实现,主要看在TSocketDispatcherThread 的 ClientExecute 方法中能否应用不同的 解析器类。从接收数据开始。数据接收由传送器 (TSocketTransport) 对象完成,该类在 Sconnect 单元中(请先将 Sconnect 单元做一个备份),我们看它的 接收( Receive )方法:function TSocketTr

17、ansport.Receive(WaitForInput: Boolean;Context: Integer): IDataBlock;varRetLen, Sig, StreamLen: Integer;P: Pointer;FDSet: TFDSet;TimeVal: PTimeVal;RetVal: Integer;beginResult := nil;TimeVal := nil;FD_ZERO(FDSet);FD_SET(FSocket.SocketHandle, FDSet);if not WaitForInput thenbeginNew(TimeVal);TimeVal.tv_

18、sec := 0;TimeVal.tv_usec := 1;end;RetVal := select(0, FDSet, nil, nil, TimeVal);if Assigned(TimeVal) thenFreeMem(TimeVal);if RetVal = SOCKET_ERROR thenraiseESocketConnectionError.Create(SysErrorMessage(WSAGetLastErr or);if (RetVal = 0) then Exit;/ 以上代码与 Socket 原理密切相关,功能是实现数据接收控制, 本人理解还不是很透,也不需要改动它。/

19、 以下代码才开始接收数据RetLen := FSocket.ReceiveBuf(Sig, SizeOf(Sig); / 检索数 据签名if RetLen SizeOf(Sig) thenraiseESocketConnectionError.CreateRes(SSocketReadError); / 出 错CheckSignature(Sig); / 检查数据标志,若不合法则产生异常 RetLen := FSocket.ReceiveBuf(StreamLen, SizeOf(StreamLen);/ 检索数据长度if RetLen = 0 thenraiseESocketConnecti

20、onError.CreateRes(SSocketReadError); / 出 错if RetLen SizeOf(StreamLen) then raiseESocketConnectionError.CreateRes(SSocketReadError); /出错Result := TDataBlock.Create as IDataBlock; /创建数据块对象Result.Size := StreamLen; / 设置数据块对象的 Size ,即数据 长度Result.Signature := Sig; /设置数据块对象的数据标志P := Result.Memory; / 取得数据块

21、对象的内存指针Inc(Integer(P), Result.BytesReserved); / 跳过保留字节数 while StreamLen 0 do / 接收 StreamLen 字节的数据并写入数 据块对象的数据域beginRetLe n := FSocket.ReceiveBuf(P StreamLe n);if RetLen = 0 thenraiseESocketConnectionError.CreateRes(SSocketReadError);if RetLen 0 thenbeginDec(StreamLen, RetLen);Inc(Integer(P), RetLen)

22、;end;end;if StreamLen 0 thenraise ESocketConnectionError.CreateRes(SInvalidDataPacket); / 出错InterceptIncoming(Result); /如果采用了加密、 压缩等处理过数据,在此将其还原end;分析到此,我们得先了解一下数据块对象,它并不复杂,因此在 此不对其代码进行分析,只简单说明它的结构。其实从MIDAS应用的 客户端传来的请求就是一个数据块, 上述接收过程将其接收后还原成 一个数据块对象。 注意不要混淆数据块和数据块对象, 前者是数据流, 后者是一个对象, 封装了数据块和对数据块操作的方

23、法。 数据块的前 8 个字节(两个整数)为保留字节( BytesReserved=8 ),分别是数据 块签名(Sig nature )和实际数据长度(Size),紧接着才是实际的数 据,其长度由 Size 域指定。数据块签名取值于一些预定义的常量, 这些常量定义在SConnect单元中,如下:const Action Signatures CallSig= $DA00; / Call signatureResultSig = $DB00; / Result signature asError = $01; / Specify an exception was raised asInvoke =

24、 $02; / Specify a call to InvokeasGetID = $03; / Specify a call to GetIdsOfNames asCreateObject = $04; / Specify a com object to create asFreeObject = $05; / Specify a dispatch to free asGetServers = $10; / Get classname list asGetGUID = $11; / Get GUID for ClassName asGetAppServers = $12; / Get App

25、Server classname list asSoapCommand = $14; / Soap command asMask = $FF; / Mask for action从传送器的接收方法可看出, 如果接收到的数据签名不合法, 将 引发异常,后续数据就不再接收。再看下面对签名的检查:procedure CheckSignature(Sig: Integer);beginif (Sig and $FF00 CallSig) and(Sig and $FF00 ResultSig) thenraise Exception.CreateRes(SInvalidDataPacket);end;

26、签名的高字节必须为 CallSig 或 ResultSig ,满足这个条件就可 通过接收检查这一关, 后续数据就可正常接收。 签名的低字节由解析 器解析,以实现不同的数据处理。对数据签名的检查使得 Scktsrvr.exe 的应用范围局限于 MIDAS 应用。如果我们要做成通用 Socket服务器,比如做一个 WW服务器 或做一个HTTP弋理服务器,客户端(浏览器)发送来的请求(Http 请求根本就不符合数据块的结构) 是通不过检查的, 连请求都无法接 收,更谈不上处理了。因此这是首先要改造的部分。为了使服务器保留MIDAS的功能,又能用于其他Socket应用, 我把数据传输分为MIDAS数据

27、传输和自定义数据传输,如果是前者, 接收方法自然不需变动,如果是后者,则跳过两个保留字节的接收, 直接接收数据写到数据块对象中,至于数据解析,前面说过,是必须 用新的解析器类的,我们在新的解析器中处理。改造很简单:1、给传送器类添加一个 IsCustomTrans 属性:TSocketTransport = class(TInterfacedObject, ITransport) privateFIsCustomTrans: Boolean; = My Code = publicproperty IsCustomTrans: Boolean read FIsCustomTrans write

28、FIsCustomTrans; = My Code = end;2、改写 TSocketTransport 的 Receive 方法:function TSocketTransport.Receive(WaitForInput: Boolean;Context: Integer): IDataBlock;varRetLen, Sig, StreamLen: Integer;P: Pointer;FDSet: TFDSet;TimeVal: PTimeVal;RetVal: Integer;beginif (RetVal = 0) then Exit;if not IsCustomTrans t

29、hen = My Code = beginRetLen := FSocket.ReceiveBuf(Sig, SizeOf(Sig);if RetLen SizeOf(StreamLen) thenraiseESocketConnectionError.CreateRes(SSocketReadError);endelseStreamLen:=FSocket.ReceiveLength; = My Code = Result := TDataBlock.Create as IDataBlock;if not IsCustomTrans then = My Code = Result.Signa

30、ture := Sig;end;2、TSocketTransport的Send方法用于实际回传数据,也需改写: function TSocketTransport.Send(const Data: IDataBlock): Integer;varP: Pointer;beginResult := 0;InterceptOutgoing(Data);P := Data.Memory;if IsCustomTrans then = My Code = FSocket.Se ndBuf(PByteArray(P)八Data.BytesReserved,Data.Size) = My Code = 不

31、发送保留字节 elseFSocket.Se ndBuf(P Data.Size + Data.BytesReserved); end;到此,发送和接收的处理就改造完了,只用了几行代码,是不是很简 单?接下来要处理的是数据解析。MIDAS的数据解析器类为TDataBlockInterpreter ,它继承于 TCustomDataBlockInterpreter 。这两个类也在 Sconnect 单元中,定 义如下:TCustomDataBlockInterpreter = classprotectedprocedure AddDispatch(Value: TDataDispatch); vi

32、rtual;abstract;procedure RemoveDispatch(Value: TDataDispatch); virtual;abstract; Sending Calls procedure CallFreeObject(DispatchIndex: Integer);virtual; abstract;function CallGetIDsOfNames(DispatchIndex: Integer; constIID: TGUID; Names: Pointer; NameCount, LocaleID: Integer;DispIDs: Pointer): HResul

33、t; virtual; stdcall; abstract;function CallInvoke(DispatchIndex, DispID: Integer; const IID: TGUID; LocaleID: Integer;Flags: Word; var Params; VarResult, ExcepInfo, ArgErr:Pointer): HResult; virtual; stdcall; abstract;function CallGetServerList: OleVariant; virtual;abstract; Receiving Calls function

34、 InternalCreateObject(const ClassID: TGUID):OleVariant; virtual; abstract;function CreateObject(const Name: string): OleVariant;virtual; abstract;function StoreObject(const Value: OleVariant): Integer;virtual; abstract;function LockObject(ID: Integer): IDispatch; virtual;abstract;procedure UnlockObj

35、ect(ID: Integer; const Disp:IDispatch); virtual; abstract;procedure ReleaseObject(ID: Integer); virtual; abstract;function CanCreateObject(const ClassID: TGUID): Boolean; virtual; abstract;function CallCreateObject(Name: string): OleVariant;virtual; abstract;publicprocedure InterpretData(const Data:

36、 IDataBlock); virtual; abstract;end; TBinary. TDataBlockInterpreter = class(TCustomDataBlockInterpreter) privateFDispatchList: TList;FDispList: OleVariant;FSendDataBlock: ISendDataBlock;FCheckRegValue: string;function GetVariantPointer(const Value: OleVariant):Pointer;procedure CopyDataByRef(const S

37、ource: TVarData; var Dest:TVarData);function ReadArray(VType: Integer; const Data:IDataBlock): OleVariant;procedure WriteArray(const Value: OleVariant; const Data:IDataBlock);function ReadVariant(out Flags: TVarFlags; const Data:IDataBlock): OleVariant;procedure WriteVariant(const Value: OleVariant;

38、 constData: IDataBlock);procedure DoException(const Data: IDataBlock); protectedprocedure AddDispatch(Value: TDataDispatch); override;procedure RemoveDispatch(Value: TDataDispatch); override;function InternalCreateObject(const ClassID: TGUID):OleVariant; override;function CreateObject(const Name: st

39、ring): OleVariant; override;function StoreObject(const Value: OleVariant): Integer; override;function LockObject(ID: Integer): IDispatch; override;procedure UnlockObject(ID: Integer; const Disp: IDispatch); override;procedure ReleaseObject(ID: Integer); override;function CanCreateObject(const ClassI

40、D: TGUID): Boolean; override;Sending Callsprocedure CallFreeObject(DispatchIndex: Integer); override;function CallGetIDsOfNames(DispatchIndex: Integer; const IID: TGUID; Names: Pointer;NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; override;constfunction CallInvoke(DispatchIndex, DispID:

41、Integer; IID: TGUID; LocaleID: Integer;Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; override;function CallGetServerList: OleVariant; override;Receiving Callsprocedure DoCreateObject(const Data: IDataBlock); procedure DoFreeObject(const Data: IDataBlock); procedure DoGetI

42、DsOfNames(const Data: IDataBlock); procedure DoInvoke(const Data: IDataBlock);function DoCustomAction(Action: Integer; const Data: IDataBlock): Boolean; virtual;procedure DoGetAppServerList(const Data: IDataBlock); procedure DoGetServerList(const Data: IDataBlock);publicconstructor Create(SendDataBl

43、ock: ISendDataBlock; CheckRegValue: string);destructor Destroy; override;function CallCreateObject(Name: string): OleVariant; override;procedure InterpretData(const Data: IDataBlock); override;end;TCustomDataBlockInterpreter 类完全是一个抽象类,它的方法 全是虚拟、抽象方法。 TDataBlockInterpreter 继承于它,实现了它 的所有方法。TDataBlockI

44、nterpreter 如何解析数据块我们就不去理它了,因 为我们不用动它,我们要做的是自己的解析器类。如果有兴趣的话, 网上搜索一下“读一读 Scktsrvr.exe 的源程序”。要创建我们自己的解析器类,很自然想到的就是从 TCustomDataBlockInterpreter 继承,象 TDataBlockInterpreter 类 一样一个个实现它的虚拟方法。但是且慢,先考虑一下,实现这一大 堆的方法对我们有用吗?这些方法主要是用于响应MIDAS客户的数据库访问请求的。 虽然我们可以因为用不上而在方法的实现中置之不 理,但是拷贝这一大堆方法到新类中并生成一大串无用的空方法就是 一件烦人的

45、事情,有些函数类方法还必须得写一行无用的返回值行, 浪费时间。因此,我决定为 TCustomDataBlockInterpreter 创建一个 祖先类。解析器类的主要方法就是:procedure InterpretData(const Data: IDataBlock);这一个方法从 TCustomDataBlockInterpreter 类移到新的解析 器祖先类中,新的解析器祖先类定义和实现如下:typeTBaseDataBlockInterpreter = class protectedFDispatchList: TList;FSendDataBlock: ISendDataBlock;

46、publicconstructor Create(SendDataBlock: ISendDataBlock;CheckRegValue: string);destructor Destroy; override;procedure InterpretData(const Data: IDataBlock); virtual; abstract;function DisconnectOnComplete: Boolean; virtual; end;implementationconstructor TBaseDataBlockInterpreter.Create(SendDataBlock:

47、 ISendDataBlock;CheckRegValue: string);begininherited Create;FDispatchList := TList.Create; FSendDataBlock:=SendDataBlock;/CheckRegValue 未用,保留该参数只是使该方法与 TDataBlockInterpreter 参数一致 end;destructor TBaseDataBlockInterpreter.Destroy;vari: Integer;beginfor i := FDispatchList.Count - 1 downto 0 do TDataDispatch(FDispatchListi).FInterpret

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 社会民生


经营许可证编号:宁ICP备18001539号-1