Multithreading Delphi:为什么Socket.ReadString(10)有时不';我得不到正确的数据
服务器:Multithreading Delphi:为什么Socket.ReadString(10)有时不';我得不到正确的数据,multithreading,delphi,Multithreading,Delphi,服务器: procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var len : integer; rest_packet : string; packet_length : string; begin try // length 10, it will be the rest packet length: 0000000545 // So the bug i
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
len : integer;
rest_packet : string;
packet_length : string;
begin
try
// length 10, it will be the rest packet length: 0000000545
// So the bug is in ReadBytes(10) , some times it do not receive the length 0000000015 but receive a mix of data+length, something like: "00015Hello" or "loWorldApp" etc.
packet_length := AContext.Connection.Socket.ReadString(10);
except
on E:Exception do begin Exit; end;
end;
// Convert string to int: 0000000545 , to 545
try
len := StrToInt(packet_length);
except
on E:Exception do begin Exit; end;
end;
// get reset of data, size: 545
try
rest_packet := AContext.Connection.Socket.ReadString(len);
except
on E:Exception do begin Exit; end;
end;
客户端:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
len : integer;
rest_packet : string;
packet_length : string;
begin
try
// length 10, it will be the rest packet length: 0000000545
// So the bug is in ReadBytes(10) , some times it do not receive the length 0000000015 but receive a mix of data+length, something like: "00015Hello" or "loWorldApp" etc.
packet_length := AContext.Connection.Socket.ReadString(10);
except
on E:Exception do begin Exit; end;
end;
// Convert string to int: 0000000545 , to 545
try
len := StrToInt(packet_length);
except
on E:Exception do begin Exit; end;
end;
// get reset of data, size: 545
try
rest_packet := AContext.Connection.Socket.ReadString(len);
except
on E:Exception do begin Exit; end;
end;
客户端以多线程方式向服务器发送数据,大约13个客户端通过一个套接字连接向服务器发送数据。客户端发送的数据如下所示:
EnterCriticalSection(CS);
// Memo1.Lines.Add(packet); // i can see here that all works fine.
Ftunnel.Socket.Write(packet);
LeaveCriticalSection(CS);
initialization
InitializeCriticalSection(CS);
finalization
DeleteCriticalSection(CS);
CS-是全局的,创建方式如下:
EnterCriticalSection(CS);
// Memo1.Lines.Add(packet); // i can see here that all works fine.
Ftunnel.Socket.Write(packet);
LeaveCriticalSection(CS);
initialization
InitializeCriticalSection(CS);
finalization
DeleteCriticalSection(CS);
数据包的结构是:
长度数据
例如:
00000000 15HelloWorldApple
所以这个bug是以ReadBytes(10)表示的,有时候它不接收长度0000000015,而是接收数据+长度的混合,比如:“00015Hello”或“loWorldApp”等等
为什么会这样,我做错了什么
ReadTimeout默认为-1
下面是如何查看客户端代码:
procedure ss_thread.Execute;
var ss : TIdTCPClient;
unix_time : integer;
data : TIdBytes;
packet, s : string;
begin
// connecting to website to get a data
ss := TIdTCPClient.Create(nil);
try
with TIdTcpClient(ss) do
begin
Host := '127.0.0.1';
Port := 80;
Connect;
end;
except
on E:Exception do
begin
ss.Disconnect;
exit;
end;
end;
// writing a data from server to connected web site server
try
ss.Socket.Write(Fff_data);
except
on E:Exception do begin end;
end;
unix_time := DateTimeToUnix(NOW);
// getting data from web site server, all looks thread safe?
while True do
begin
ss.Socket.CheckForDataOnSource(5);
if not ss.Socket.InputBufferIsEmpty then
begin
SetLength(data, 0);
ss.Socket.InputBuffer.ExtractToBytes(data);
s := TIdEncoderMIME.EncodeBytes(data);
packet := make_my_length(s) + s;
ss.Socket.InputBuffer.Clear;
unix_time := DateTimeToUnix(NOW);
try
// sending data from web site, to server
EnterCriticalSection(CS);
tunnel.Socket.Write(packet);
LeaveCriticalSection(CS);
except
on E:Exception do begin end;
end;
end;
// disconnecting if no more data for 120sec
if (DateTimeToUnix(NOW) - unix_time) > 120 then
begin
ss.Disconnect;
break;
end;
// killing thread if web site server disconnected us
if not ss.Connected then
begin
break;
end;
// disconnecting from web site server and killing thread if tunnel died
if not tunnel.Connected then
begin
ss.Disconnect;
break;
end;
end;
// terminate thread
Terminate;
end;
使我的长度代码,工作良好:
function make_my_length(s:string):string;
var len, i : integer;
res : string;
begin
if s='' then
begin
Result := '';
Exit;
end;
res := '';
len := Length(s);
if len < 10 then
for i:=1 to (10 - len) do
begin
res := res + '0';
end;
Result := res + s;
end;
函数make_my_length(s:string):string;
变量len,i:整数;
res:字符串;
开始
如果s='',那么
开始
结果:='';
出口
结束;
res:='';
长度:=长度(s);
如果len<10,则
对于i:=1到(10-len)do
开始
res:=res+'0';
结束;
结果:=res+s;
结束;
Delphi 2010,Indy 10,Win7您描述的症状与您的客户端线程在访问
Ftunnel
时未正确共享单个CS一致,因此多个写入操作可能会继续相互重叠,这是CS试图避免的。您是否在与CheckForData\u from\u ss()
方法相同的单元中创建CS?您是否验证了每个线程确实正在访问相同的CS而不是不同的CS?实际上从哪里调用CheckForData\u from\u ss
()?直接从工作线程访问TMemo
(或任何其他UI控件,就这一点而言),就像代码所暗示的那样,是危险的,如果内存被破坏,可能会导致各种副作用。访问UI时,您需要与主线程同步,例如与Indy的TIdSync
或TIdNotify
类同步
另一种可能是接收服务器端没有正确读取隧道数据。例如,如果服务器在某一点丢失了一些字节,那么所有后续读取都将位于错误的帧偏移。如果要对隧道数据进行帧处理,则应在帧中添加识别标记,以便在尝试解码之前知道服务器是否接收到有效帧,并在标记不同步时进行恢复
编辑:在客户端线程中尝试以下操作:
var
CS: TCriticalSection;
procedure ss_thread.Execute;
var
ss : TIdTCPClient;
data : TIdBytes;
packet, s : string;
begin
// connecting to website to get a data
ss := TIdTCPClient.Create(nil);
try
ss.Host := '127.0.0.1';
ss.Port := 80;
ss.ReadTimeout := 120000;
ss.Connect;
// writing a data from server to connected web site server
ss.IOHandler.Write(Fff_data);
// getting data from web site server
while not Terminated do
begin
SetLength(data, 0);
ss.IOHandler.ReadBytes(data, -1, False);
s := TIdEncoderMIME.EncodeBytes(data);
packet := make_my_length(s) + s;
// sending data from web site, to server
CS.Enter;
try
tunnel.IOHandler.Write(packet);
finally
CS.Leave;
end;
end;
finally
ss.Free;
end;
end;
initialization
CS := TCriticalSection.Create;
finalization
CS.Free;
您描述的症状与您的客户端线程在访问
Ftunnel
时未正确共享单个CS一致,因此多个写入操作可能会继续相互重叠,这正是CS试图避免的。您是否在与CheckForData\u from\u ss()
方法相同的单元中创建CS?您是否验证了每个线程确实正在访问相同的CS而不是不同的CS?实际上从哪里调用CheckForData\u from\u ss
()?直接从工作线程访问TMemo
(或任何其他UI控件,就这一点而言),就像代码所暗示的那样,是危险的,如果内存被破坏,可能会导致各种副作用。访问UI时,您需要与主线程同步,例如与Indy的TIdSync
或TIdNotify
类同步
另一种可能是接收服务器端没有正确读取隧道数据。例如,如果服务器在某一点丢失了一些字节,那么所有后续读取都将位于错误的帧偏移。如果要对隧道数据进行帧处理,则应在帧中添加识别标记,以便在尝试解码之前知道服务器是否接收到有效帧,并在标记不同步时进行恢复
编辑:在客户端线程中尝试以下操作:
var
CS: TCriticalSection;
procedure ss_thread.Execute;
var
ss : TIdTCPClient;
data : TIdBytes;
packet, s : string;
begin
// connecting to website to get a data
ss := TIdTCPClient.Create(nil);
try
ss.Host := '127.0.0.1';
ss.Port := 80;
ss.ReadTimeout := 120000;
ss.Connect;
// writing a data from server to connected web site server
ss.IOHandler.Write(Fff_data);
// getting data from web site server
while not Terminated do
begin
SetLength(data, 0);
ss.IOHandler.ReadBytes(data, -1, False);
s := TIdEncoderMIME.EncodeBytes(data);
packet := make_my_length(s) + s;
// sending data from web site, to server
CS.Enter;
try
tunnel.IOHandler.Write(packet);
finally
CS.Leave;
end;
end;
finally
ss.Free;
end;
end;
initialization
CS := TCriticalSection.Create;
finalization
CS.Free;
我添加了代码,看。如果它在自己的线程中运行,为什么它必须是线程安全的?@waza123:他有多个线程同时向一个套接字连接写入数据。这就是为什么他有一个关于写作的关键部分。@MarcusAdams:
make_my_length()
不需要是这个类的成员。它不访问任何其他类成员。它接受一个输入字符串,并且只对该值进行操作。这是线程安全的。是的,马库斯·亚当斯建议不起作用。我添加了代码,看。如果它在自己的线程中运行,为什么它必须是线程安全的?@waza123:他有多个线程同时向一个套接字连接写入。这就是为什么他有一个关于写作的关键部分。@MarcusAdams:make_my_length()
不需要是这个类的成员。它不访问任何其他类成员。它接受一个输入字符串,并且只对该值进行操作。这是线程安全的。是的,Marcus Adams建议不起作用。*“您是否在与CheckForData_from_ss()方法相同的单元中创建CS?”是。*“您是否验证了每个线程确实正在访问相同的CS而不是不同的CS?”是。*“CheckForData_from_ss()实际从何处调用?”来自过程ss_thread.Execute;*“如果服务器在某一点上丢失了一些字节,那么所有后续读取都将在错误的帧偏移处。”这是怎么发生的?因为CheckForData\u from\u ss()
是在线程中调用的,所以您必须确保其代码是完全线程安全的,以避免任何不必要的副作用。您显示的不是线程安全的。除此之外,我建议您使用数据包嗅探器,如Wireshark,或将Indy自己的TIdLog…
组件连接到Ftunnel
,以便您可以看到正在传输的实际数据。这将告诉你,你的客户端应用程序是否正在向隧道发送坏数据。如果不是,那么服务器端没有正确读取隧道数据