我正在通过Indy套接字处理流数据包字符串,在客户端,我有一个线程从TIdTCPClient
读取传入数据,并不断将此数据附加到单个字符串缓冲区的末尾。我有另一个线程,它从头开始连续读取此缓冲区,根据需要复制(和删除)数据(一次一个完整的数据包)。
我知道在任何情况下,访问同一个变量的两个线程都是危险的。但这也适用于字符串吗?或只是对象?从两个不同的线程读取/写入相同的字符串,我能感觉安全吗?如果没有,那么我该怎么做才能保护这个字符串呢?这是一个名为FBuffer
的纯字符串。
我将数据追加到最后:
procedure TListenThread.CheckForData;
begin
if FClientSocket.Connected then begin
FClientSocket.IOHandler.CheckForDataOnSource(5000);
if not FClientSocket.IOHandler.InputBufferIsEmpty then
FBuffer:= FBuffer + FClientSocket.IOHandler.InputBufferAsString;
end;
end;
另一个主题是这样读它:
procedeure TPacketThread.CheckForPacket;
var
P: Integer; //Deliminator position
T: String; //Temp copying string
Z: Integer; //Expected packet size
begin
P:= Pos('#', FBuffer);
if P > 0 then begin //Is the deliminator found?
T:= Copy(FBuffer, 1, P-1); //Copy up to deliminator...
Z:= StrToIntDef(T, 0); //Convert packet size to integer...
if Z > 0 then begin
//Is there a full packet waiting in buffer?
if Length(FBuffer) >= Z then begin
//First, delete size definition and deliminator...
Delete(FBuffer, 1, P);
//Now grab the rest of it up to the packet size...
T:= Copy(FBuffer, 1, Z);
//Delete what we just copied...
Delete(FBuffer, 1, Z);
//Finally, pass this packet string for further processing...
ProcessPacket(T);
end;
end;
end;
end;
代码是我的代码的简化版本,只是为了演示我需要对FBuffer
执行的所有工作。
答案 0 :(得分:5)
是的,从多个线程访问时必须保护字符串,您可以使用关键部分来执行此操作。查看EnterCriticalSection,LeaveCriticalSection,InitializeCriticalSection和DeleteCriticalSection函数。
答案 1 :(得分:5)
是的,您必须保护字符串缓冲区不受并发访问的影响。 Indy有一个TIdThreadSafeString
类可以用于此目的,例如:
FBuffer: TIdThreadSafeString;
// make sure to Create() and Free() as needed..
procedure TListenThread.CheckForData;
begin
if FClientSocket.Connected then begin
FClientSocket.IOHandler.CheckForDataOnSource(5000);
if not FClientSocket.IOHandler.InputBufferIsEmpty then
FBuffer.Append(FClientSocket.IOHandler.InputBufferAsString);
end;
end;
procedure TPacketThread.CheckForPacket;
var
P: Integer; //Deliminator position
T: String; //Temp copying string
Z: Integer; //Expected packet size
begin
FBuffer.Lock;
try
P:= Pos('#', FBuffer.Value);
if P > 0 then begin //Is the deliminator found?
T := Copy(FBuffer.Value, 1, P-1); //Copy up to deliminator...
Z := StrToIntDef(T, 0); //Convert packet size to integer...
if Z > 0 then begin
//Is there a full packet waiting in buffer?
if Length(FBuffer.Value) >= Z then begin
//First, delete size definition and deliminator...
FBuffer.Value := Copy(FBuffer.Value, P+1, MaxInt);
//Now grab the rest of it up to the packet size...
T := Copy(FBuffer.Value, 1, Z);
//Delete what we just copied...
FBuffer.Value := Copy(FBuffer.Value, Z+1, MaxInt);
//Finally, pass this packet string for further processing...
ProcessPacket(T);
end;
end;
end;
finally
FBuffer.Unlock;
end;
end;
话虽如此,鉴于你所展示的数据包格式化,我会采取不同的策略:
FBuffer: TIdThreadSafeStringList;
// make sure to Create() and Free() as needed..
procedure TListenThread.CheckForData;
var
T: String; //Temp copying string
Z: Integer; //Expected packet size
begin
if FClientSocket.Connected then begin
if FClientSocket.IOHandler.InputBufferIsEmpty then begin
FClientSocket.IOHandler.CheckForDataOnSource(5000);
if FClientSocket.IOHandler.InputBufferIsEmpty then Exit;
end;
// data is available, keep reading as long as packets are present...
repeat
T := FClientSocket.IOHandler.ReadLn('#');
Z := StrToIntDef(T, 0);
if Z > 0 then begin
T := FClientSocket.IOHandler.ReadString(Z);
FBuffer.Add(T);
end;
until FClientSocket.IOHandler.InputBufferIsEmpty;
end;
end;
procedure TPacketThread.CheckForPacket;
var
L: TStringList;
T: String;
begin
L := FBuffer.Lock;
try
if L.Count = 0 then Exit;
T := L[0];
L.Delete(0);
finally
FBuffer.Unlock;
end;
ProcessPacket(T);
end;