halfdream(哈欠) , 我以为你睡着了呢:) 我现在的问题是发送数据时被Borland的ITransport解释为前Sinagture不一样,在Receive的这一段代码出错了 .... if (Sig and CallSig <> CallSig) and (Sig and ResultSig <> ResultSig) then raise Exception.CreateRes(@SInvalidDataPacket); ....然后就提示Error reading from socket
其中一个关键问题就是需通讯协议冲突问题..这儿说的通讯协议,指的是在TCP之上的应用协议.TSocketConnection/scktsrvr.exe用的应用协议,读一下源码,可以把它大致可以划分为两层. A: Dispatch调用所需的通讯交易: asError = $01; // Specify an exception was raised asInvoke = $02; // Specify a call to Invoke asGetID = $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 AppServer classname list asSoapCommand = $14; // Soap command asMask = $FF; // Mask for actionB: 数据简单封装(加标志,长度信息)(未完待续..)
我的构思是:想实现从Borland Scktsrvr.exe在增加一个菜单项,名叫["发送消息到客户端正“],当选中相应的客户端连接,然后点击这个菜单,则发送消息到客户端,客户端显示相应的信息。 我的菜单点击代码是: var i: Integer; myData :TDataBlock; msg :string; begin msg :='来自服务端的信息!'; myData :=TDataBlock.Create(); myData.Signature := CallSig; myData.Write(msg,sizeof(msg )) ; with SelectedSocket.Socket do begin Lock; try for i := 0 to ConnectionList.Items.Count - 1 do with ConnectionList.Items[i] do if Selected then begin TServerClientThread(Data).ServerSocket.Connections[0].SendBuf(myData,Sizeof(myData)) ; end; finally Unlock; end; end; end; --- 但是在Receive的这一段代码出错了 .... if (Sig and CallSig <> CallSig) and (Sig and ResultSig <> ResultSig) then raise Exception.CreateRes(@SInvalidDataPacket); ....然后就提示Error reading from socket
当然,最大的错误是你用的.SendBuf去传一个对象指针. 原因你可以看看.TSocketDispatcherThread.Send方法,看它是怎么发送TDataBlock的.于是你的代码至少需要改成下面:..... with ConnectionList.Items[0] do begin if Assigned(Data) then begin (TSocketDispatcherThread(Data) as ISendDataBlock).send(myData); end end; //---------------------------------------------------这仍离你的目标相当远..仅仅能成功的把数据包主动发向客户端而已,这样还存在的不少问题.. 下一步你仍需要深入去理解scktsrvr.exe源码架构,分析解决协议冲突为主的一系列问题.
改成这样子就跟我从客户端发送数据产生的错误一样了,提示如下错误: Invalid action received: 0.应当是如halfdream(哈欠)所说,主要是我对协议方面处理没搞清楚,自己这方面的经验真的太少,能否指点得更细点。我客户端发送信息的代码是: var SocketTransport: TSocketTransport; Data :TDataBlock; msg :string; begin msg :='客户端发送的消息!'; Data :=TDataBlock.Create(); Data.Signature := CallSig; Data.Write(msg,sizeof(msg)) ; Self.SocketConnection1.Send(Data,true); end;-- 其中Send方法是继承自TStreamedConnection来的,执行后提示如下错误: Invalid action received: 0.
出现Invalid action , peterzhou2000只要看TDataBlockInterpreter.InterpretData方法源码就很容易理解了..procedure TDataBlockInterpreter.InterpretData(const Data: IDataBlock); var Action: Integer; begin Action := Data.Signature; if (Action and asMask) = asError then DoException(Data); try case (Action and asMask) of asInvoke: DoInvoke(Data); asGetID: DoGetIDsOfNames(Data); asCreateObject: DoCreateObject(Data); asFreeObject: DoFreeObject(Data); asGetServers: DoGetServerList(Data); asGetAppServers: DoGetAppServerList(Data); else //你发的数据包不属于上面每一种情况.. if not DoCustomAction(Action and asMask, Data) then raise EInterpreterError.CreateResFmt(@SInvalidAction, [Action and asMask]); end; except........DoCustomAction又干了些什么呢:function TDataBlockInterpreter.DoCustomAction(Action: Integer; const Data: IDataBlock): Boolean; begin Result := False;//很明显,TDataBlockInterpreter这个类并未处理非约定协议的数据. end; //--------------------------------------- 这是绕不开的地方之一,你想想可以如何去修改或扩展此处.
可以参考,增加自己的协议类型如: asMyMsg = $15; // customer message by 服务端发送消息的时候,Data.Signature := ResultSig or asMyMsg;修改如下方法: procedure TStreamedConnection.ThreadReceivedStream(var Message: TMessage); var Data: IDataBlock; Applied : boolean; begin Data := IDataBlock(Message.lParam); Data._Release; Applied := false; if assigned(FOnReceive) then FOnReceive(Data, Applied); if not Applied then Interpreter.InterpretData(Data); end;FOnReceive 是自定义的事件,可以从TStreamedConnection一直property 到 TSocketConnection, 这样你就可以在TSocketConnection的OnReceive事件中处理自己的数据包了 如: if (Data.Signature and asMask) = asMttMsg then begin //..... Applied := true; end;这样是可以从客户端接收到服务端的消息的,但是有一个奇怪的问题,我从服务端定时发送消息给客户端(测试客户端是否已经意外断开网络)的时候,还是会执行到 TDataBlockInterpreter.InterpretData 中进行解析,然后提示错误,实际上是自己的消息应该是在这之前就已经被自己处理掉了的。还希望大家能够帮忙借答
呵呵,刚才回答完问题,又看了一下代码,发现Data.Signature := ResultSig or asMyMsg; 改成 Data.Signature := asMyMsg; 然后在处理的时候 if Data.Signature = asMttMsg then 即没有出现错误提示了,多改写一个地方: procedure CheckSignature(Sig: Integer); begin if (Sig and $FF00 <> CallSig) and (Sig and $FF00 <> ResultSig) and (Sig <> asMyMsg) then raise Exception.CreateRes(@SInvalidDataPacket); end;错误分析:估计是,加了ResultSig标志后,在客户端接收数据的时候,原代码碰到ResultSig就会break出循环,结束一次wait,很有可能在客户端正在取数据的过程中(有可能要发送多个数据包完成一次请求),服务端发送一个包含ResultSig标志的自定义消息,中断了系统自己的请求数据操作,引发错误。ScktSrvr的源代码还并没有彻底读懂,不当支出,请高手指正。
procedure Tdatatest.doOnReceiveMttMsg(const Data: IDataBlock; var Applied: boolean); var aMsg : TMttMsg; begin if Data.Signature = asMttMsg then begin aMsg := TMttMsg.Create; aMsg.ReadMttMsgFrom(Data); if aMsg.SendType <> cSvrTest then showmsghintfrm(aMsg);
其实BORLAND的程序员在设计TSocketConnection,在架构上是留了扩展余地的: TDataBlockInterpreter的DoCustomAction是虚函数,可以扩展的. 下面是我随手写的,没调试. TMyDataBlockInterpreter=class(TDataBlockInterpreter) protected function DoCustomAction(Action: Integer; const Data: IDataBlock): Boolean; override; end;TMySocketConnection = class(TSocketConnection) private FMyInterpreter: TCustomDataBlockInterpreter; protected function GetInterpreter: TCustomDataBlockInterpreter; override; end;...function TMySocketConnection.GetInterpreter: TCustomDataBlockInterpreter; begin // if not Assigned(FMyInterpreter) then FMyInterpreter := TMyDataBlockInterpreter.Create(Self, SSockets); Result := FMyInterpreter;end;{ TMyDataBlockInterpreter }function TMyDataBlockInterpreter.DoCustomAction(Action: Integer; const Data: IDataBlock): Boolean; begin // //这儿,你可以把数据从Data中读出来 Result := true;//返回true值,在TDataBlockInterpreter.InterpretData处理时就不会抛异常 end;
scktsrvr中Receive处理中,没有作缓冲的处理,这样在网络有异常的情况,也就容易出现:invalid read from socket这错误了。一般加个缓存机制就基本可以了。本身的ITransport实现类没有作这步处理,且使用阻塞socket实现,收不到数据但连接正常(Internet中经常有此情况出现),就出raise invalid read错误了。也容易处理粘包的问题。
看错题 if (Sig and CallSig <> CallSig) and (Sig and ResultSig <> ResultSig) then raise Exception.CreateRes(@SInvalidDataPacket);SERVER & CLIENT都有这个错误,是为了验证数据包的正确性。所以你在组包的时候,将包加入这个标志就行了。Data.Sig = MyFlag or ResultSig;
我要的是通过Socket来进行双向通讯的。
用TSocketConnection和ScktSrvr.exe就是要利用它们的接口,如果想通过Socket来进行双向通讯,还不如自己自定义一些规则直接实现。用不着TSocketConnection和ScktSrvr.exe
你那个示例使用多线程时,会提示"应用程序调用一个已为另一线程整理的接口" 这个问题有没有解决到啊
那主要看应用协议与双方使用的I/O模式.关于应用协议方面要求,我想关键注意解决:
1,一端提收到数据,应该能够分清此数据是对方主动发动的请求还是自己这端请求的回应包.
2,能够判断回应包是哪次请求的回应.(这点偶尔也不需要)
然后就是I/O模式了..TClientSocket/TServerSocket缺省非阻塞方式是使用的基于消息的异步I/O,
TSocketConnection里使用TClientSocket则使用了WINDOWS的事件I/O.
你可以看一下这方面的资料,就会很明白TSocketconnection与TScktSrvr.exe的实现.
我以为你睡着了呢:)
我现在的问题是发送数据时被Borland的ITransport解释为前Sinagture不一样,在Receive的这一段代码出错了
....
if (Sig and CallSig <> CallSig) and
(Sig and ResultSig <> ResultSig) then
raise Exception.CreateRes(@SInvalidDataPacket);
....然后就提示Error reading from socket
ServerTCP.IOHandler.ReadStream(Source,8,False);//读取服务器发过来的数据包的长度
Source.Position:=4;
Source.Read(Len,4);
Source.Position:=8;
ServerTCP.IOHandler.ReadStream(Source,Len,False);//读取服务器发过来的数据包TSocketconnection与TScktSrvr.exe发送数据时的格式是这样的,前四个字节存放的是Sig(标识),接着的四个字节存放的是该次发送的数据的长度希望我提供的信息对你有所帮助,因为我到现在还不知道你想做些什么
看Delphi的演示,Demo
楼主是想根据TSocketConnection与TScktSrvr.exe源码来改造扩展成什么的.
A: Dispatch调用所需的通讯交易:
asError = $01; // Specify an exception was raised
asInvoke = $02; // Specify a call to Invoke
asGetID = $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 AppServer classname list
asSoapCommand = $14; // Soap command
asMask = $FF; // Mask for actionB: 数据简单封装(加标志,长度信息)(未完待续..)
var
i: Integer;
myData :TDataBlock;
msg :string;
begin
msg :='来自服务端的信息!';
myData :=TDataBlock.Create();
myData.Signature := CallSig;
myData.Write(msg,sizeof(msg )) ;
with SelectedSocket.Socket do
begin
Lock;
try
for i := 0 to ConnectionList.Items.Count - 1 do
with ConnectionList.Items[i] do
if Selected then
begin
TServerClientThread(Data).ServerSocket.Connections[0].SendBuf(myData,Sizeof(myData)) ;
end;
finally
Unlock;
end;
end;
end;
---
但是在Receive的这一段代码出错了
....
if (Sig and CallSig <> CallSig) and
(Sig and ResultSig <> ResultSig) then
raise Exception.CreateRes(@SInvalidDataPacket);
....然后就提示Error reading from socket
你说的对,因为现在的项目已经是使用TSocketConnection+Scktsrvr.exe做的,现在想扩展下支持双向的发送消息功能。
~~~~\这儿应该是i吧?
原因你可以看看.TSocketDispatcherThread.Send方法,看它是怎么发送TDataBlock的.于是你的代码至少需要改成下面:.....
with ConnectionList.Items[0] do
begin
if Assigned(Data) then
begin
(TSocketDispatcherThread(Data) as ISendDataBlock).send(myData);
end end;
//---------------------------------------------------这仍离你的目标相当远..仅仅能成功的把数据包主动发向客户端而已,这样还存在的不少问题..
下一步你仍需要深入去理解scktsrvr.exe源码架构,分析解决协议冲突为主的一系列问题.
Invalid action received: 0.应当是如halfdream(哈欠)所说,主要是我对协议方面处理没搞清楚,自己这方面的经验真的太少,能否指点得更细点。我客户端发送信息的代码是:
var
SocketTransport: TSocketTransport;
Data :TDataBlock;
msg :string;
begin
msg :='客户端发送的消息!';
Data :=TDataBlock.Create();
Data.Signature := CallSig; Data.Write(msg,sizeof(msg)) ;
Self.SocketConnection1.Send(Data,true);
end;--
其中Send方法是继承自TStreamedConnection来的,执行后提示如下错误:
Invalid action received: 0.
peterzhou2000只要看TDataBlockInterpreter.InterpretData方法源码就很容易理解了..procedure TDataBlockInterpreter.InterpretData(const Data: IDataBlock);
var
Action: Integer;
begin
Action := Data.Signature;
if (Action and asMask) = asError then DoException(Data);
try
case (Action and asMask) of
asInvoke: DoInvoke(Data);
asGetID: DoGetIDsOfNames(Data);
asCreateObject: DoCreateObject(Data);
asFreeObject: DoFreeObject(Data);
asGetServers: DoGetServerList(Data);
asGetAppServers: DoGetAppServerList(Data);
else //你发的数据包不属于上面每一种情况..
if not DoCustomAction(Action and asMask, Data) then
raise EInterpreterError.CreateResFmt(@SInvalidAction, [Action and asMask]);
end;
except........DoCustomAction又干了些什么呢:function TDataBlockInterpreter.DoCustomAction(Action: Integer;
const Data: IDataBlock): Boolean;
begin
Result := False;//很明显,TDataBlockInterpreter这个类并未处理非约定协议的数据.
end;
//---------------------------------------
这是绕不开的地方之一,你想想可以如何去修改或扩展此处.
自定义一个WINDOWS消息,把custom类数据块拆包,将内容POST出来.这样对原有的VCL代码影响最小.
至于你要将接收的数据怎么处理,就只管处理那个自定义消息就是了.
服务端发送消息的时候,Data.Signature := ResultSig or asMyMsg;修改如下方法:
procedure TStreamedConnection.ThreadReceivedStream(var Message: TMessage);
var
Data: IDataBlock;
Applied : boolean;
begin
Data := IDataBlock(Message.lParam);
Data._Release;
Applied := false;
if assigned(FOnReceive) then FOnReceive(Data, Applied);
if not Applied then Interpreter.InterpretData(Data);
end;FOnReceive 是自定义的事件,可以从TStreamedConnection一直property 到 TSocketConnection, 这样你就可以在TSocketConnection的OnReceive事件中处理自己的数据包了
如: if (Data.Signature and asMask) = asMttMsg then
begin
//.....
Applied := true;
end;这样是可以从客户端接收到服务端的消息的,但是有一个奇怪的问题,我从服务端定时发送消息给客户端(测试客户端是否已经意外断开网络)的时候,还是会执行到 TDataBlockInterpreter.InterpretData 中进行解析,然后提示错误,实际上是自己的消息应该是在这之前就已经被自己处理掉了的。还希望大家能够帮忙借答
改成 Data.Signature := asMyMsg; 然后在处理的时候 if Data.Signature = asMttMsg then 即没有出现错误提示了,多改写一个地方:
procedure CheckSignature(Sig: Integer);
begin
if (Sig and $FF00 <> CallSig) and
(Sig and $FF00 <> ResultSig) and (Sig <> asMyMsg) then
raise Exception.CreateRes(@SInvalidDataPacket);
end;错误分析:估计是,加了ResultSig标志后,在客户端接收数据的时候,原代码碰到ResultSig就会break出循环,结束一次wait,很有可能在客户端正在取数据的过程中(有可能要发送多个数据包完成一次请求),服务端发送一个包含ResultSig标志的自定义消息,中断了系统自己的请求数据操作,引发错误。ScktSrvr的源代码还并没有彻底读懂,不当支出,请高手指正。
CheckSignature你是写在哪里啊?能否将你的OnReceive事件贴出来看看
var Applied: boolean);
var
aMsg : TMttMsg;
begin
if Data.Signature = asMttMsg then
begin
aMsg := TMttMsg.Create;
aMsg.ReadMttMsgFrom(Data);
if aMsg.SendType <> cSvrTest then
showmsghintfrm(aMsg);
Applied := true;
aMsg.free;
end;
end;
----------------------------
CheckSignature 是 SConnect 单元里面的
if Data.Signature = asMyMsg then
我的SConnect 单元里面怎么找不到这个CheckSignature过程。我用的是Delphi6。
Tmttmsg 是一个自定义的类,没什么特别的内容,仅仅把一些read、write Data的操作简化一下, aMsg.ReadMttMsgFrom(Data); 也只是从data中读取数据,Tmttmsg里与之对应的还有一个aMsg.WriteMttMsgTo(Data)的方法。 你也可以把这句话写成 Data.read(buf^, 100) 或者对应的Data.write(buf^, 100) 等操作,其中buf是你的数据
设置 好后 可以 用 callback
server 主动调用 客户端的 方法
我用 DCOM 实现过这是对等吗?不过 我习惯这样用
我还是喜欢 直接 socket
这样写的时候,其实就不需要修改CheckSignature这函数了. msg :='客户端发送的消息!';
Data :=TDataBlock.Create();
Data.Signature := CallSig;//对端的CheckSignature仅是判断是否有这东西.
Data.Write(msg,sizeof(msg)) ;
SocketConnection1.Send(Data,true);
.........................peterzhou2000真是走了不少弯路,不过这源码还是有些复杂度.也曾被它弄得头晕晕的..
好在现在不了.....(继续上面我2.7日发的)...........
从源码可以很容易看出,这儿的基本数据封装是这样定义:
通讯包= 包头sig(4字节) + 包体长度(4字节) + 包体
包头sig四字节,由两部分组成,
一部分用来区分主动包还是回应包
CallSig = $DA00; // Call signature
ResultSig = $DB00; // Result signature
另一部分是一个字节的功能码
在我2.7日发的回贴里面具体列出这些功能码,仅从这一层数据封装层次上看,
功能码具体是什么含义,这儿不管.这样大致分层后,再重新去分析相关的代码,就会清晰得多了..
我估计把这两层叫为
A---功能层,定义了几种交易类型,来支撑了DISPATCH远程调用..
B---SOCKET封装层,加数据包头,发送接收数据.
A层: TStreamedConnection 相关的一些组件,特别是TDataBlockInterpreter
B层: TSocketConnection(继承自TStreamedConnection)相关的一些组件,如TSocketTransport很多东西仍需要楼主自己去分析去理解,你自己会发现,往往只差那一层窗户纸罢了.
TDataBlockInterpreter的DoCustomAction是虚函数,可以扩展的.
下面是我随手写的,没调试.
TMyDataBlockInterpreter=class(TDataBlockInterpreter)
protected
function DoCustomAction(Action: Integer; const Data: IDataBlock): Boolean; override;
end;TMySocketConnection = class(TSocketConnection)
private
FMyInterpreter: TCustomDataBlockInterpreter;
protected
function GetInterpreter: TCustomDataBlockInterpreter; override;
end;...function TMySocketConnection.GetInterpreter: TCustomDataBlockInterpreter;
begin
//
if not Assigned(FMyInterpreter) then
FMyInterpreter := TMyDataBlockInterpreter.Create(Self, SSockets);
Result := FMyInterpreter;end;{ TMyDataBlockInterpreter }function TMyDataBlockInterpreter.DoCustomAction(Action: Integer;
const Data: IDataBlock): Boolean;
begin
//
//这儿,你可以把数据从Data中读出来
Result := true;//返回true值,在TDataBlockInterpreter.InterpretData处理时就不会抛异常
end;
(Sig and ResultSig <> ResultSig) then
raise Exception.CreateRes(@SInvalidDataPacket);SERVER & CLIENT都有这个错误,是为了验证数据包的正确性。所以你在组包的时候,将包加入这个标志就行了。Data.Sig = MyFlag or ResultSig;
方法N多,不过不建议改原有系统支撑的东西,如果不很熟的话。