gpt4 book ai didi

synchronization - TIdServer,关于再次同步

转载 作者:行者123 更新时间:2023-12-04 19:36:18 25 4
gpt4 key购买 nike

根据雷米·勒博(Remy Lebeau)的一个特别的回答(几乎再次令人满意),我试图结合对我的应用有用的代码。
还有几个方面让我不清楚。当您查看下面的代码时:


当我使用Button3Click过程将BroadCast从GUI发送到连接的客户端时,这是正确的方法吗(我的意思是:它安全吗?)?
可以输入类似于DoSomethingSafe方法的代码,在其中创建与DB的连接,对其进行操作,然后关闭与DB的连接吗?安全吗?
为什么当有20个以上的客户端时我的应用程序冻结,我想通过使用其active:= false(button2.click方法)来停止服务器工作?
我可以在TCliContext.ProccessMsg内使用TCliContext.BroadcastMessage而不进行任何同步吗?
如何在OnConnect方法中读取(Connection.IOHandler.ReadLn())(我想读取登录数据行并在DB中进行检查,然后在不正确的情况下立即断开连接?
我在某处阅读,使用IdSync有时会带来危险(如果在使用它时内部出现任何问题),因此我有最后一个问题:什么是实现全局变量或VCL对象的更好解决方案?


我的示例代码如下:

type
TCliContext = class(TIdServerContext)
private
Who: String;
Queue: TIdThreadSafeStringList;

Activity_time: TDateTime;
Heartbeat_time: TDateTime;

InnerMessage: String;

procedure BroadcastMessage(const ABuffer: String);
procedure SendMessageTo(const ADestUser: String; const ABuffer: String);

public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;

procedure ProccessMsg;
procedure DoSomethingSafe;
procedure info_about_start_connection;
end;

procedure TCliContext.BroadcastMessage(const ABuffer: String);
var
cList: TList;
Count: Integer;
CliContext: TCliContext;
begin
cList := Server.Contexts.LockList;
try
for Count := 0 to cList.Count - 1 do
begin
CliContext := TCliContext(cList[Count]);
if CliContext <> Self then
CliContext.Queue.Add(ABuffer);
end;
finally
Server.Contexts.UnlockList;
end;
end;

procedure TCliContext.SendMessageTo(const ADestUser: String;
const ABuffer: String);
var
cList: TList;
Count: Integer;
CliContext: TCliContext;
begin
cList := Server.Contexts.LockList;
try
for Count := 0 to cList.Count - 1 do
begin
CliContext := TCliContext(cList[Count]);
if CliContext.Who = ADestUser then
begin
CliContext.Queue.Add(ABuffer);
Break;
end;
end;
finally
Server.Contexts.UnlockList;
end;
end;

constructor TCliContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
// inherited Create(AConnection, AYarn, AList);
inherited;
Queue := TIdThreadSafeStringList.Create;
end;

destructor TCliContext.Destroy;
begin
Queue.Free;
inherited;
end;

procedure TCliContext.ProccessMsg;
begin
InnerMessage := Connection.IOHandler.ReadLn();
TIdSync.SynchronizeMethod(DoSomethingSafe);
// is it ok?
end;

procedure TCliContext.info_about_start_connection;
begin
MainForm.Memo1.Lines.Add('connected');
end;

procedure TCliContext.DoSomethingSafe;
begin
MainForm.Memo1.Lines.Add(InnerMessage);
end;


和代码与GUI核对

procedure TMainForm.BroadcastMessage(Message: string);
var
cList: TList;
Count: Integer;
begin
cList := IdTCPServer.Contexts.LockList;
try
for Count := 0 to cList.Count - 1 do
TCliContext(cList[Count]).Queue.Add(Message);
finally
IdTCPServer.Contexts.UnlockList;
end;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
IdTCPServer.ContextClass := TCliContext;
end;

procedure TMainForm.IdTCPServerConnect(AContext: TIdContext);
begin
TCliContext(AContext).Queue.Clear;
TCliContext(AContext).Heartbeat_time := now;
TCliContext(AContext).Activity_time := now;
TIdSync.SynchronizeMethod(TCliContext(AContext).info_about_start_connection);
// is it safe?
end;

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext);
var
tmplist, Queue: TStringlist;
dtNow: TDateTime;
begin
dtNow := now;
tmplist := nil;
try
Queue := TCliContext(AContext).Queue.Lock;
try
if Queue.Count > 0 then
begin
tmplist := TStringlist.Create;
tmplist.Assign(Queue);
Queue.Clear;
end;
finally
TCliContext(AContext).Queue.Unlock;
end;
if tmplist <> nil then
begin
AContext.Connection.IOHandler.Write(tmplist);
TCliContext(AContext).Heartbeat_time := dtNow;
end;
finally
tmplist.Free;
end;

if SecondsBetween(dtNow, TCliContext(AContext).Heartbeat_time) > 30 then
begin
AContext.Connection.IOHandler.WriteLn('E:');
TCliContext(AContext).Heartbeat_time := dtNow;
end;

if SecondsBetween(dtNow, TCliContext(AContext).Activity_time) > 6 then
begin
AContext.Connection.Disconnect;
Exit;
end;
TCliContext(AContext).ProccessMsg;;
end;

procedure TMainForm.Button1Click(Sender: TObject);
begin
IdTCPServer.Active := true;
end;

procedure TMainForm.Button2Click(Sender: TObject);
begin
IdTCPServer.Active := false;
// here application freezes when there are more then tens active clients
end;

procedure TMainForm.Button3Click(Sender: TObject);
begin
BroadcastMessage('Hello');
// is it safe and correct?
end;


更新(您的最佳答案后的最后一个问题)为了更简单(缩短代码长度),我可以使用TIdNotify类,如下所示:

TMyNotify.Create(1, 'ABC').Notify; 




type
TMyNotify = class(TidNotify)
public
faction: string;
fdata:string;
procedure DoNotify; override;
procedure action1();
procedure action2();
constructor Create(action:integer;fdata:string); reintroduce;
end;

constructor TMyNotify.Create(action:integer;fdata:string); reintroduce;
begin
inherited Create;
faction:=action;
fdata:=data;
end;

procedure TMyNotify.action2()
begin
//use fdata and do something with vcl etc.
end;

procedure TMyNotify.action2()
begin
//use fdata and do something with vcl etc.
end;

procedure TMyNotify.DoNotify;
begin
case action of
1: action1()
2: action2()
end;
end;


再次感谢您之前的帮助

最佳答案

当我使用Button3Click过程将BroadCast从GUI发送到连接的客户端时,这是正确的方法吗(我的意思是:它安全吗?)?


是的,您正在正确安全地发送数据。但是,TCliContext.ProcessMsg()正在执行对ReadLn()的阻止调用。如果客户端有一段时间没有发送任何数据,则该逻辑将阻止您的OnExecute代码及时执行其时敏逻辑(如果有的话)。由于涉及时间敏感逻辑,因此需要在连接处理上使用超时,以便您可以进行时间检查。在实际数据可供读取之前(或让ProcessMsg()在内部处理超时),请不要调用ProcessMsg(),并且应为TIdIOHandler.ReadTimeout属性分配一个值,以防万一客户端停止在服务器中发送数据。消息的中间。例如:

procedure TMainForm.IdTCPServerConnect(AContext: TIdContext);
begin
...
AContext.Connection.IOHandler.ReadTimeout := 10000;
end;

procedure TMainForm.IdTCPServerExecute(AContext: TIdContext);
var
...
begin
...

if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
if not AContext.Connection.IOHandler.CheckForDataOnSource(100) then
begin
AContext.Connection.IOHandler.CheckForDisconnect;
Exit;
end;
end;

TCliContext(AContext).ProccessMsg;
TCliContext(AContext).Activity_time := Now();
end;



可以输入类似于DoSomethingSafe方法的代码,在其中创建与DB的连接,对其进行操作,然后关闭与DB的连接吗?安全吗?


是。实际上,尤其是对于数据库查询,应尽可能为每个客户端线程提供自己的与数据库的连接。然后,您不必同步数据库查询(根据所使用的数据库,您甚至可以在查询本身中使用数据库提供的同步锁)。如果可能的话,您还应该合并数据库连接(由于架构限制,某些数据库类型不可合并。例如,由于使用了特定于线程的ActiveX / COM对象,因此是ADO)。如果不需要,请勿跨多个线程同步数据库连接。当您需要执行数据库查询时,从池中获取数据库连接(或在需要时创建新连接),进行数据库查询,然后将数据库连接放回池中(如果可能),以便另一个客户端线程可以在需要时使用它。如果数据库连接在池中存在一段时间,请断开连接,然后在需要再次使用它时重新连接它。这有助于最大程度地减少数据库连接的数量,同时最大程度地利用它们。


为什么当有20个以上的客户端时我的应用程序冻结,我想通过使用其active:= false(button2.click方法)来停止服务器工作?


发生这种情况的最常见原因是,您可能同时启动了对主线程的同步操作(或已经处于同步操作的中间),同时又从主线程内停用了服务器。这是有保证的死锁方案。请记住,每个客户端都在服务器内的各自线程中运行。当主线程停用服务器时,它在等待服务器完成停用过程中被阻止,因此它无法处理同步请求。服务器停用会等待所有客户端线程完全终止。等待主线程处理同步请求时,同步客户端线程被阻止,因此它无法终止。发生死锁。客户端的数量无关紧要,即使仅连接了一个客户端,也可能会发生这种情况。

为了解决这个问题,您有两种选择:


创建一个工作线程来停用服务器,而不是使主线程停用它。这样可以释放主线程以正常处理同步请求,从而允许客户端线程正常终止,并且服务器可以正常完全停用。例如:

type
TShutdownThread = class(TThread)
protected
procedure Execute; override;
end;

procedure TShutdownThread.Execute;
begin
MainForm.IdTCPServer.Active := False;
end;

procedure TMainForm.Button2Click(Sender: TObject);
begin
if MainForm.IdTCPServer.Active then
begin
with TShutdownThread.Create(False) do
try
WaitFor; // internally processes sync requests...
finally
Free;
end;
end;
end;

尽可能消除线程阻塞同步。直接在客户端线程中而不是在主线程中完成尽可能多的工作。特别是对于您的客户端代码实际上不必等待主线程响应的操作。如果实际上不需要跨线程边界同步某些内容,则不要同步它。当您必须与主线程同步时,请尽可能使用 TIdNotify而不是 TIdSyncTIdNotify是异步的,因此它不会像 TIdSync那样阻塞调用线程,从而避免了停用死锁。您只需要对 TIdNotify多加注意,因为它是异步的。它被放置在后台队列中并在以后执行,因此您必须确保使用它访问的任何对象和数据在最终运行时仍然有效。因此,最好使 TIdNotify实现尽可能独立,以免依赖外部事物。例如:

type
TMemoNotify = class(TIdNotify)
protected
FStr: String;
procedure DoNotify; override;
public
class procedure AddToMemo(const Str: string);
end;

procedure TMemoNotify.DoNotify;
begin
MainForm.Memo1.Lines.Add(FStr);
end;

class procedure TMemoNotify.AddToMemo(const Str: string);
begin
with Create do
begin
FStr := Str;
Notify;
// DO NOT free it! It is self-freeing after it is run later on...
end;
end;

procedure TCliContext.ProcessMsg;
var
Msg: string;
begin
Msg := Connection.IOHandler.ReadLn;
TMemoNotify.AddToMemo(Msg);
...
end;

procedure TMainForm.IdTCPServerConnect(AContext: TIdContext);
begin
...
TCliContext(AContext).Who := ...;
TMemoNotify.AddToMemo(TCliContext(AContext).Who + ' connected');
...
end;

procedure TMainForm.IdTCPServerDisconnect(AContext: TIdContext);
begin
...
TMemoNotify.AddToMemo(TCliContext(AContext).Who + ' disconnected');
...
end;




我可以在TCliContext.ProccessMsg内使用TCliContext.BroadcastMessage而不进行任何同步吗?


是的,因为 TIdTCPServer.ContextTIdThreadSafeStringList锁提供了足够的同步(它们都在内部使用 TCriticalSection)。同样适用于 TCliContext.SendMessageTo()


如何在OnConnect方法中读取(Connection.IOHandler.ReadLn())(我想读取登录数据行并在DB中进行检查,然后在不正确的情况下立即断开连接?


是。 OnConnect(和 OnDisconnect)在与 OnExecute相同的客户端线程上下文中运行。 TIdTCPServerOnConnect退出后(然后再启动 OnExecute循环之前)检查套接字是否仍处于连接状态 OnConnect决定断开与客户端的连接。


我在某处阅读,使用IdSync有时会带来危险(如果在使用它时内部出现任何问题)


在大多数情况下,只要正确使用 TIdSyncTIdNotify,它们都是安全的。

如果主线程被阻塞,则同步的 TIdSync确实具有死锁的可能性,仅此而已。

如果使用 TIdNotify,请确保使用的是Indy 10的最新版本。某些Indy 10的早期版本在 TIdNotify中存在内存泄漏,但是最近已修复。


有什么更好的解决方案来访问全局变量或VCL对象?


未严格绑定到任何给定线程的全局变量应在可能的情况下提供自己的同步。无论是在自己的内部代码中(例如您的 BroadcastMessage()SendMessageTo()实现),还是通过单独的锁(例如 TCriticalSection对象)。

VCL对象只能在主线程中访问,因此,如果不使用 TIdSync / TIdNotify,则必须使用其他形式的线程同步来选择委托代码在主线程中运行线。这是真正实现UI逻辑和业务逻辑分离的地方。如果可能,应该将业务数据与UI分开,然后在数据操作周围提供安全的线程间锁,然后可以使UI在需要时安全地更新数据,并可以让工作线程在需要时安全地更新数据,并且向UI发布异步请求以显示最新数据。

关于synchronization - TIdServer,关于再次同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14173140/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com