【说明】
这个例子答应大家很久了,一直没有时间弄,现在正式结合MyBean插件可以很方便的在客户端共享操作连接,执行数据库的各项工作,屏蔽了底层的通信解码器编码等工作,直接传递Variant,给了开发者足够的领活和自由。
【服务端使用技术】
diocp3:担当底层的通信任务。
qworker/iocpTask:担当业务逻辑的处理工作,diocp3接受数据解码后用qworker/iocpTask将数据包投递出来,这样不用占用通信线程。
qmsgpack:负责将传递的将variant数据打包到流,从流中解码成variant
dataModule:对应连接的对象,方便进行开发。
【客户端】
myBean:制作基于MyBean框架的插件,可以在MyBean的框架模块中直接使用。
RawTcpClient:用于和服务端进行通信,阻塞的tcp客户端,类似IdTcpClient的精简版本,操作容易。
【DEMO使用】
说 明:客户端依赖diocp_bean.dll插件与服务器进行数据交换,DIOCP_DBDEMO.dll是演示窗体插件的宿主。
存放路径:MyBeansamplesdiocp-DBDEMO
启动服务:SERVER_EXEdiocp3Server.exe <点击start按钮启动服务>
服务器是基于DIOCP3的。可以在DIOCP3项目中找到源码[diocp3samplessocket-CoderDataModuleDEMO]
启动客户端:打开 [simpleConsole.exe], 依次点击下面的按钮就可以看到效果了
【DEMO说明】
服务端处理:
服务端基于DIOCP3通信,该DEMO做了简单的封装,使用户可以专注于逻辑的实现,和客户端交互使用variant类型进行。
dmMain,是和客户端连接想对应的一个对象[和客户端的连接是一对一的关系]。
我在里面稍微做了下逻辑处理的演示:
function TdmMain.Execute(pvCmdIndex: Integer; var vData: OleVariant): Boolean; begin case pvCmdIndex of 0: begin // 返回服务端时间给客户端 vData := Now(); Result := true; end; 1: // 查询数据 begin // vData 认为是传入的SQL语句 // 执行后, vData为查询的数据,可以用于对ClientData.Data的赋值 qryMain.Close; qryMain.SQL.Clear; qryMain.SQL.Add(vData); qryMain.Open; vData := dspMain.Data; Result := true; qryMain.Close; end; 2: begin // vData 为执行的语句 conMain.BeginTrans; try qryMain.Close; qryMain.SQL.Clear; qryMain.SQL.Add(vData); qryMain.ExecSQL; conMain.CommitTrans; VarClear(vData); Result := true; except conMain.RollbackTrans; raise; end; end; end; end;
vData,是客户端传递过来的参数,也是返回给客户端的数据。vData是OleVariant可以容纳任何的数据<配合qmsgPack可以达到任何的数据格式要求>, 如果还达不到你的格式要求,你可以在MyClientContext的dataReceived函数做些修改,下面代码的处理过程依次是:
解压收到的数据->QmsgPack解包->取出客户端传入的参数->调用dmMain.Execute处理逻辑->qmsgPack编码数据->压缩数据->回传数据到客户端
procedure TMyClientContext.dataReceived(const pvDataObject: TObject); var lvMsgPack:TQMsgPack; lvStream :TStream; lvStream2:TMemoryStream; vData:OleVariant; lvResult:Boolean; begin lvMsgPack := TQMsgPack.Create; try try if FdmMain = nil then FdmMain := TdmMain.Create(nil); lvStream := TStream(pvDataObject); lvStream.Position := 0; // upZip TZipTools.unCompressStreamEX(lvStream); lvStream.Position := 0; // unpack lvMsgPack.LoadFromStream(lvStream); // get param vData := lvMsgPack.ForcePath('cmd.data').AsVariant; // invoke dataModule function lvResult := FdmMain.Execute(lvMsgPack.ForcePath('cmd.index').AsInteger, vData); // write result info lvMsgPack.Clear; lvMsgPack.ForcePath('__result.result').AsBoolean := lvResult; lvMsgPack.ForcePath('__result.data').AsVariant := vData; except on E:Exception do begin lvMsgPack.Clear; lvMsgPack.ForcePath('__result.result').AsBoolean := false; lvMsgPack.ForcePath('__result.msg').AsString := e.Message; end; end; lvStream.Size := 0; lvMsgPack.SaveToStream(lvStream); lvStream.Position := 0; // zipStream TZipTools.compressStreamEX(lvStream); lvStream.Position := 0; // send to client self.writeObject(lvStream); finally lvMsgPack.Free; end; end;
客户端处理:
编译的 DLL 放到和EXE同一个目录下面就可以进行自动加载。
客户端diocp_bean工程中
library diocp_bean; uses SysUtils, Classes, mybean.core.beanFactoryForNoVcl, uRemoteServerDIOCPImpl in 'ServiceuRemoteServerDIOCPImpl.pas'; {$R *.res} begin beanFactory.RegisterBean('diocpRemoteSvr', TRemoteServerDIOCPImpl).Singleton := true; end.
注册的diocpRemoteSvr插件实现了IRemoteServer接口和IRemoteServerConnector接口,而且该插件为单件模式。其他任何地方调用都只会产生一个连接实例。
type IRemoteServer = interface(IInterface) ['{20B5F070-461C-41F4-AA0C-E500A36E18E4}'] /// <summary> /// 执行远程动作 /// </summary> function Execute(pvCmdIndex: Integer; var vData: OleVariant): Boolean; stdcall; end; IRemoteServerConnector = interface(IInterface) ['{65931F56-07BA-42F8-BD5C-7409053F5B2C}'] procedure setHost(pvHost: PAnsiChar); procedure setPort(pvPort:Integer); procedure open; end;
演示操作窗体:<服务端是13K的成语记录>
相应按钮代码:
constructor TfrmMain.Create(AOwner: TComponent); begin inherited; // 通过注册的插件ID获取单实例的远程连接操作接口 FRemoteSvr := TMyBeanFactoryTools.getBean('diocpRemoteSvr') as IRemoteServer; end; procedure TfrmMain.btnConnectClick(Sender: TObject); begin // 打开远程连接,如果打开过可以不用打开,其他插件中可以直接使用 (FRemoteSvr as IRemoteServerConnector).setHost(PAnsiChar(AnsiString(edtHost.Text))); (FRemoteSvr as IRemoteServerConnector).setPort(StrToInt(edtPort.Text)); (FRemoteSvr as IRemoteServerConnector).open; ShowMessage('open succ!'); end; procedure TfrmMain.btnOpenClick(Sender: TObject); var vData:OleVariant; l : Cardinal; begin vData := mmoSQL.Lines.Text; l := GetTickCount; // 在远程打开SQL if FRemoteSvr.Execute(1, vData) then begin self.cdsMain.Data := vData; Self.Caption := Format('query: count:%d, time:%d', [self.cdsMain.RecordCount, GetTickCount - l]); end; end;
PS: 其他功能大家自己去实现,该文章写了好几个中午,希望对大家有用。
由于qdac开源项目 >=D2007,所以D7下面不能编译服务端,和diocp_bean.项目(你可以在D2007中编译好diocp_bean和服务端工程,其他插件可以在d7中完成)
-----------------------------------------------------------------------------------------------------
MyBean 轻量级配置开源框架 开源地址
https://git.oschina.net/ymofen/delphi-framework-MyBean
DIOCP3开源地址
https://github.com/ymofen/diocp3
qdac项目信息
官方网站:http://www.qdac.cc
官方QQ群:250530692
QDAC项目网址:http://sourceforge.net/p/qdac3
在线源码浏览:http://sourceforge.net/p/qdac3/code/HEAD/tree/
SVN检出地址:
http://svn.code.sf.net/p/qdac3/code/
svn://svn.code.sf.net/p/qdac3/code/