使用 TIdTCPServer/TIdTCPClient 个组件在 Delphi 中丢失数据
Data loss in Delphi using TIdTCPServer/TIdTCPClient components
我正在使用 Delphi 中的 TIdTCPClient/TIdTcpServer indy 组件编写客户端-服务器应用程序。
数据传输通常工作正常,但我经常从服务器读取错误数据;我得到以前请求的答案,而不是当前请求的答案。
在调试期间,两个应用程序都在本地运行,因此在传输过程中数据不会丢失。
超时为 1000-3000 毫秒,这足以避免在收到第一个请求的答复之前发送第二个请求。
我使用简单的数据格式:前4个字节是数据包长度,其余是该长度的二进制数据。
服务器端代码是(为仅发送字符串而简化;我也以相同的方式使用二进制缓冲区,但这段代码更易于理解和检查):
Var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
lng:=length(s);// s is an AnsiString to be sent
SetLength(ib,lng+4);
Move(lng,ib[0],4);
Move(s[1],ib[4],length(s));
// Send:
AContext.Connection.IOHandler.WriteDirect(ib);
end;
发送请求的客户端代码相同(在最后一行调用TIdTcpClient.IOHandler.WriteDirect())。
读取服务器答案的客户端代码是:
Var
ib: TIdBytes;
size,done,lng: LongInt;
begin
Result:=false;
// answer length:
try
SetLength(ib,0);
tcp.IOHandler.ReadBytes(ib,4,false);
Move(ib[0],size,4);
if length(ib)<0 then Exit;// wrong data
except
on E: Exception do;// code skipped
end;
// read answer body:
done:=0;
b.Clear;// b is my buffer, TStream descendant
while done<size do
begin
lng:=Min(size-done,MaxBlockSize);
// read:
SetLength(ib,0);// to be sure
tcp.IOHandler.ReadBytes(ib,lng,false);
if length(ib)=0 then Exit;// error reading
// append my buffer:
b.Wr(ib[0],length(ib));
// progress:
Inc(done,length(ib));
end;
end;
数据交换顺序为:
客户端向服务器发送请求,
服务器读取请求并将应答发送回客户端,
客户阅读答案。
第 3 步出现错误数据。
也许我做错了什么?
我已经尝试在向服务器发送请求以清除传入缓冲区之前使用 ReadBytes(),但这也无济于事,因为我已经尝试了很多其他事情...
现在我完全没有想法:(
您的 I/O 逻辑比需要的复杂得多,尤其是在客户端。您正在手动执行 Indy 可以自动为您执行的操作。
在客户端,由于您将数据保存到 TStream 中,您可以让 Indy 直接将数据读取到 TStream 中:
begin
...
b.Clear;// b is my buffer, TStream descendant
// ReadStream() can read a '<length><bytes>' formatted
// message. When its ASize parameter is -1 and its
// AReadUntilDisconnect parameter is False, it reads
// the first 4 or 8 bytes (depending on the LargeStream
// property) and interprets them as the byte count,
// in network byte order...
tcp.IOHandler.RecvBufferSize := MaxBlockSize;
tcp.IOHandler.LargeStream := False; // read 4-byte length
// read answer:
try
tcp.IOHandler.ReadStream(b, -1, false);
except
on E: Exception do begin
// the socket is now in an indeterminate state.
// You do not know where the reading left off.
// The only sensible thing to do is disconnect
// and reconnect...
tcp.Disconnect;
...
end;
end;
...
end;
在服务器端,您可以通过两种不同的方式发送与上述代码兼容的消息:
var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
// s is an AnsiString to be sent
lng := Length(s);
SetLength(ib, lng);
Move(PAnsiChar(s)^, PByte(ib)^, lng);
// Send:
AContext.Connection.IOHandler.Write(lng); // send 4-byte length, in network byte order
AContext.Connection.IOHandler.Write(ib); // send bytes
end;
或者:
var
strm: TIdMemoryBufferStream;
begin
// Prepare data to send:
// s is an AnsiString to be sent
strm := TIdMemoryBufferStream.Create(PAnsiChar(s), Length(s));
try
// Send:
// Write(TStream) can send a '<length><bytes>' formatted
// message. When its ASize parameter is 0, it sends the
// entire stream, and when its AWriteByteCount parameter
// is True, it first sends the byte count as 4 or 8 bytes
// (depending on the LargeStream property), in network
// byte order...
AContext.Connection.IOHandler.LargeStream := False; // send 4-byte lengtb
AContext.Connection.IOHandler.Write(strm, 0, True);
finally
strm.Free;
end;
end;
如您所见,此代码发送的消息类型与您最初发送的消息类型相同,只是管理消息的代码发生了变化。此外,它强制以 网络字节顺序 发送消息字节数,而您却以 主机字节顺序 发送它。为保持一致性和多平台兼容性,应尽可能始终以网络字节顺序发送多字节整数。
我正在使用 Delphi 中的 TIdTCPClient/TIdTcpServer indy 组件编写客户端-服务器应用程序。
数据传输通常工作正常,但我经常从服务器读取错误数据;我得到以前请求的答案,而不是当前请求的答案。
在调试期间,两个应用程序都在本地运行,因此在传输过程中数据不会丢失。
超时为 1000-3000 毫秒,这足以避免在收到第一个请求的答复之前发送第二个请求。
我使用简单的数据格式:前4个字节是数据包长度,其余是该长度的二进制数据。
服务器端代码是(为仅发送字符串而简化;我也以相同的方式使用二进制缓冲区,但这段代码更易于理解和检查):
Var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
lng:=length(s);// s is an AnsiString to be sent
SetLength(ib,lng+4);
Move(lng,ib[0],4);
Move(s[1],ib[4],length(s));
// Send:
AContext.Connection.IOHandler.WriteDirect(ib);
end;
发送请求的客户端代码相同(在最后一行调用TIdTcpClient.IOHandler.WriteDirect())。 读取服务器答案的客户端代码是:
Var
ib: TIdBytes;
size,done,lng: LongInt;
begin
Result:=false;
// answer length:
try
SetLength(ib,0);
tcp.IOHandler.ReadBytes(ib,4,false);
Move(ib[0],size,4);
if length(ib)<0 then Exit;// wrong data
except
on E: Exception do;// code skipped
end;
// read answer body:
done:=0;
b.Clear;// b is my buffer, TStream descendant
while done<size do
begin
lng:=Min(size-done,MaxBlockSize);
// read:
SetLength(ib,0);// to be sure
tcp.IOHandler.ReadBytes(ib,lng,false);
if length(ib)=0 then Exit;// error reading
// append my buffer:
b.Wr(ib[0],length(ib));
// progress:
Inc(done,length(ib));
end;
end;
数据交换顺序为:
客户端向服务器发送请求,
服务器读取请求并将应答发送回客户端,
客户阅读答案。
第 3 步出现错误数据。
也许我做错了什么?
我已经尝试在向服务器发送请求以清除传入缓冲区之前使用 ReadBytes(),但这也无济于事,因为我已经尝试了很多其他事情...
现在我完全没有想法:(
您的 I/O 逻辑比需要的复杂得多,尤其是在客户端。您正在手动执行 Indy 可以自动为您执行的操作。
在客户端,由于您将数据保存到 TStream 中,您可以让 Indy 直接将数据读取到 TStream 中:
begin
...
b.Clear;// b is my buffer, TStream descendant
// ReadStream() can read a '<length><bytes>' formatted
// message. When its ASize parameter is -1 and its
// AReadUntilDisconnect parameter is False, it reads
// the first 4 or 8 bytes (depending on the LargeStream
// property) and interprets them as the byte count,
// in network byte order...
tcp.IOHandler.RecvBufferSize := MaxBlockSize;
tcp.IOHandler.LargeStream := False; // read 4-byte length
// read answer:
try
tcp.IOHandler.ReadStream(b, -1, false);
except
on E: Exception do begin
// the socket is now in an indeterminate state.
// You do not know where the reading left off.
// The only sensible thing to do is disconnect
// and reconnect...
tcp.Disconnect;
...
end;
end;
...
end;
在服务器端,您可以通过两种不同的方式发送与上述代码兼容的消息:
var
lng: LongInt;
ib: TIdBytes;
begin
// Prepare data to send:
// s is an AnsiString to be sent
lng := Length(s);
SetLength(ib, lng);
Move(PAnsiChar(s)^, PByte(ib)^, lng);
// Send:
AContext.Connection.IOHandler.Write(lng); // send 4-byte length, in network byte order
AContext.Connection.IOHandler.Write(ib); // send bytes
end;
或者:
var
strm: TIdMemoryBufferStream;
begin
// Prepare data to send:
// s is an AnsiString to be sent
strm := TIdMemoryBufferStream.Create(PAnsiChar(s), Length(s));
try
// Send:
// Write(TStream) can send a '<length><bytes>' formatted
// message. When its ASize parameter is 0, it sends the
// entire stream, and when its AWriteByteCount parameter
// is True, it first sends the byte count as 4 or 8 bytes
// (depending on the LargeStream property), in network
// byte order...
AContext.Connection.IOHandler.LargeStream := False; // send 4-byte lengtb
AContext.Connection.IOHandler.Write(strm, 0, True);
finally
strm.Free;
end;
end;
如您所见,此代码发送的消息类型与您最初发送的消息类型相同,只是管理消息的代码发生了变化。此外,它强制以 网络字节顺序 发送消息字节数,而您却以 主机字节顺序 发送它。为保持一致性和多平台兼容性,应尽可能始终以网络字节顺序发送多字节整数。