使用TIdTCPServerIndy 的组件,一个包以两个分数接收,但客户端发送一个64 KB.
如何在Server OnExecute事件中收到完整的包?
现在我放置一个原型(服务器和客户端)代码来重新创建这种情况.
服务器代码
procedure TFrmServer.IdTCPServer1Execute(AContext: TIdContext);
Var
ReceivedBytesTCP : Integer;
IBuf : TIdBytes;
begin
if Not AContext.Connection.IOHandler.InputBufferIsEmpty then Begin
Try
ReceivedBytesTCP := AContext.Connection.IOHandler.InputBuffer.Size;
SetLength(IBuf,ReceivedBytesTCP);
AContext.Connection.IOHandler.ReadBytes(IBuf,ReceivedBytesTCP,False);
AContext.Connection.IOHandler.Write(IBuf,Length(IBuf),0);
Except
On E : Exception Do Begin
Memo1.Lines.Add('Except Server TCP: ' + E.Message);
End;
End;
End Else Begin
Sleep(1);
End;
end;
Run Code Online (Sandbox Code Playgroud)
客户代码
procedure TFrm_TCP_Client.BtnSendClick(Sender: TObject);
Var
IBuf,RBuf : TIdBytes;
I : Integer;
LenPacket : Integer;
begin
LenPacket := StrToInt(EdtLength.Text);
if IdTCPClient1.Connected then Begin
SetLength(IBuf,LenPacket);
for I := 1 to LenPacket do
IBuf[I] := 1;
IdTCPClient1.IOHandler.Write(IBuf,Length(IBuf),0);
I := 0;
Repeat
IdTCPClient1.IOHandler.CheckForDataOnSource(50);
Inc(I);
Until Not IdTCPClient1.IOHandler.InputBufferIsEmpty or (I >= 10);
If Not IdTCPClient1.IOHandler.InputBufferIsEmpty Then Begin
SetLength(RBuf,IdTCPClient1.IOHandler.InputBuffer.Size);
IdTCPClient1.IOHandler.ReadBytes(RBuf,IdTCPClient1.IOHandler.InputBuffer.Size,False);
if Length(RBuf) = Length(IBuf) then
Memo1.Lines.Add('Response Received OK: '+IntToStr(Length(RBuf)))
Else
Memo1.Lines.Add('Response Received With Different Length: '+IntToStr(Length(RBuf)));
if Not IdTCPClient1.IOHandler.InputBufferIsEmpty then
Memo1.Lines.Add('Llego otro Mensaje');
End Else Begin
Memo1.Lines.Add('NO Response Received');
End;
End;
end;
Run Code Online (Sandbox Code Playgroud)
如何知道消息是第一个还是第二个片段?如何强制接收第二个片段?
TCP中的发送和读取之间没有1对1的关系.它可以自由地对数据进行分段,但是它希望优化网络传输.TCP仅保证传送数据,并且以与发送数据相同的顺序保证,但在传输过程中没有任何关于HOW数据的碎片.TCP将在接收端重建片段.这就是TCP的工作原理,它不是Indy独有的.无论使用哪种TCP框架,每个TCP应用程序都必须处理此问题.
如果您需要64KB的数据,那么只需读取64KB的数据,让OS和Indy为您内部处理片段.TCP的这种碎片化正是Indy 在将数据拼接回来时IOHandler使用a InputBuffer来收集碎片的原因.
更新:停止关注片段.这是TCP层的实现细节,您没有在其中运行.您不需要处理代码中的片段.让Indy为您处理.请专注于您的应用程序级别协议.
而且,仅供参考,您基本上已经实施了ECHO客户端/服务器解决方案.印有实际ECHO客户机/服务器组件,TIdECHO并且TIdECHOServer,你应该看看他们.
在任何情况下,您的服务器端异常处理都是非常有问题的.它不与主UI线程同步(OnExecute在工作线程中调用).但是,更重要的是,TIdTCPServer当客户端连接丢失/断开时,它会阻止处理Indy自身发出的任何通知,因此客户端线程将继续运行,并且在您停用服务器之前不会停止.不要吞下Indy自己的例外(源自EIdException).如果你需要在你的代码中捕获它们,你应该在完成后重新提升它们,让TIdTCPServer它们处理它们.但是,在您的示例中,try..except完全删除并使用服务器的OnException事件会更容易.
此外,您尝试使用它时,您的客户端读取循环是错误的.您没有IBuf正确初始化.但是,更重要的是,你用的是很短的超时(TCP连接可能有延迟),和你尽快打破你的阅读循环的任何数据到达或500ms的最大过去了,即使有更多的数据仍然来了.你应该阅读,直到没有什么可读的.
尝试更像这样的东西:
服务器:
procedure TFrmServer.IdTCPServer1Execute(AContext: TIdContext);
var
IBuf : TIdBytes;
begin
AContext.Connection.IOHandler.ReadBytes(IBuf, -1);
AContext.Connection.IOHandler.Write(IBuf);
end;
procedure TFrmServer.IdTCPServer1Exception(AContext: TIdContext, AException: Exception);
var
Msg: string;
begin
if AException <> nil then
Msg := AException.Message
else
Msg := 'Unknown';
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add('Except Server TCP: ' + Msg);
end
);
end;
Run Code Online (Sandbox Code Playgroud)
客户:
procedure TFrm_TCP_Client.BtnSendClick(Sender: TObject);
Var
IBuf,RBuf : TIdBytes;
LenPacket : Integer;
begin
if not IdTCPClient1.Connected then Exit;
LenPacket := StrToInt(EdtLength.Text);
if LenPacket < 1 then Exit;
SetLength(IBuf, LenPacket);
FillBytes(IBuf, LenPacket, $1);
try
IdTCPClient1.IOHandler.InputBuffer.Clear;
IdTCPClient1.IOHandler.Write(IBuf);
except
Memo1.Lines.Add('Request Send Error');
Exit;
end;
try
while IdTCPClient1.IOHandler.CheckForDataOnSource(500) do;
if not IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
IdTCPClient1.IOHandler.ReadBytes(RBuf, IdTCPClient1.IOHandler.InputBuffer.Size, True);
if Length(RBuf) = Length(IBuf) then
Memo1.Lines.Add('Response Received OK: ' + IntToStr(Length(RBuf)))
else
Memo1.Lines.Add('Response Received With Different Length. Expected: ' + IntToStr(Length(IBuf)) + ', Got: ' + IntToStr(Length(RBuf)));
end
else
Memo1.Lines.Add('NO Response Received');
except
Memo1.Lines.Add('Response Receive Error');
end;
end;
Run Code Online (Sandbox Code Playgroud)
更好的解决方案是根本不依赖于这样的逻辑,更明确地说明数据协议的结构<length><data>,例如:
服务器:
procedure TFrmServer.IdTCPServer1Execute(AContext: TIdContext);
var
IBuf : TIdBytes;
LenPacket : Int32;
begin
LenPacket := AContext.Connection.IOHandler.ReadInt32;
AContext.Connection.IOHandler.ReadBytes(IBuf, LenPacket, True);
AContext.Connection.IOHandler.Write(LenPacket);
AContext.Connection.IOHandler.Write(IBuf);
end;
procedure TFrmServer.IdTCPServer1Exception(AContext: TIdContext, AException: Exception);
var
Msg: string;
begin
if AException <> nil then
Msg := AException.Message
else
Msg := 'Unknown';
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add('Except Server TCP: ' + Msg);
end
);
end;
Run Code Online (Sandbox Code Playgroud)
客户:
procedure TFrm_TCP_Client.BtnSendClick(Sender: TObject);
Var
IBuf,RBuf : TIdBytes;
LenPacket : Int32;
begin
if not IdTCPClient1.Connected then Exit;
LenPacket := StrToInt(EdtLength.Text);
if LenPacket < 1 then Exit;
SetLength(IBuf, LenPacket);
FillBytes(IBuf, LenPacket, $1);
try
IdTCPClient1.IOHandler.InputBuffer.Clear;
IdTCPClient1.IOHandler.Write(LenPacket);
IdTCPClient1.IOHandler.Write(IBuf);
except
Memo1.Lines.Add('Request Send Error');
Exit;
end;
try
IdTCPClient1.IOHandler.ReadTimeout := 5000;
LenPacket := IdTCPClient1.IOHandler.ReadInt32;
IdTCPClient1.IOHandler.ReadBytes(RBuf, LenPacket, True);
except
Memo1.Lines.Add('Response Receive Error');
Exit;
end;
Memo1.Lines.Add('Response Received OK');
end;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
133 次 |
| 最近记录: |