2012-12-31 102 views
3

经过大量的搜索后,我认为Indy TCP服务器将是我正在使用的即时通讯服务器上最好的使用方式。我现在面临的唯一问题是向其他连接的客户端广播和转发消息,向同一个客户端发回响应似乎没问题,并且不会挂断其他客户端的活动,但是为了将消息转发给其他客户端,我知道的机制是通过使用aContext.locklist,并在连接列表之间进行迭代来查找要接收数据的客户端连接。Indy 10 TCP服务器

这里的问题我认为它冻结了列表并且不会处理其他客户端请求,直到解锁列表被调用。那么它会不会损害服务器的性能?锁定列表并在连接之间迭代以转发每条消息(因为这是在Messenger中经常发生的事情)。有没有更好的方法来做到这一点?

我使用印10和Delphi 7

代码广播:

Var tmpList: TList; 
    i: Integer; 
Begin 
tmpList := IdServer.Contexts.LockList; 

For i := 0 to tmpList.Count Do Begin 
    TIdContext(tmpList[i]).Connection.Socket.WriteLn('Broadcast message'); 
End; 
IdServer.Contexts.UnlockList; 

代码转发消息:

Var tmpList: TList; 
    i: Integer; 
Begin 
    tmpList := IdServer.Contexts.LockList; 

    For i := 0 to tmpList.Count Do Begin 
    If TIdContext(tmpList[i]).Connection.Socket.Tag = idReceiver Then 
     TIdContext(tmpList[i]).Connection.Socket.WriteLn('Message'); 
    End; 
    IdServer.Contexts.UnlockList; 
+0

您希望我们能够在心理上调试您的代码? – Barmar

+0

对不起,我其实认为它是一个常见问题,而不是代码问题。 –

回答

8

是的,您可以通过Contexts名单中有循环为了向多个客户端广播消息。但是,您不(也不应该)从循环内执行实际的写作。其中一个,正如你已经注意到的那样,服务器的性能可能会受到一段时间锁定列表的影响。二,它不是线程安全的。如果您的循环在另一个线程同时向同一连接写入数据时将数据写入连接,那么这两个写入将相互重叠并破坏与该客户端的通信。

我通常使用TIdContext.Data属性或TIdServerContext后代实现每个客户端出站队列来保存实际队列。当您需要从客户端的OnExecute事件之外向客户端发送数据时,请将数据放入该客户端的队列中。当客户端的事件安全时,可以将队列的内容发送给客户端。

例如:

type 
    TMyContext = class(TIdServerContext) 
    public 
    Tag: Integer; 
    Queue: TIdThreadSafeStringList; 
    ... 
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override; 
    destructor Destroy; override; 
    end; 

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

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

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    IdServer.ContextClass := TMyContext; 
end; 

procedure TForm1.IdServerConnect(AContext: TIdContext); 
begin 
    TMyContext(AContext).Queue.Clear; 
    TMyContext(AContext).Tag := ... 
end; 

procedure TForm1.IdServerDisconnect(AContext: TIdContext); 
begin 
    TMyContext(AContext).Queue.Clear; 
end; 

procedure TForm1.IdServerExecute(AContext: TIdContext); 
var 
    Queue: TStringList; 
    tmpList: TStringList; 
begin 
    ... 
    tmpList := nil; 
    try 
    Queue := TMyContext(AContext).Queue.Lock; 
    try 
     if Queue.Count > 0 then 
     begin 
     tmpList := TStringList.Create; 
     tmpList.Assign(Queue); 
     Queue.Clear; 
     end; 
    finally 
     TMyContext(AContext).Queue.Unlock; 
    end; 
    if tmpList <> nil then 
     AContext.Connection.IOHandler.Write(tmpList); 
    finally 
    tmpList.Free; 
    end; 
    ... 
end; 

var 
    tmpList: TList; 
    i: Integer; 
begin 
    tmpList := IdServer.Contexts.LockList; 
    try 
    for i := 0 to tmpList.Count-1 do 
     TMyContext(tmpList[i]).Queue.Add('Broadcast message'); 
    finally 
    IdServer.Contexts.UnlockList; 
    end; 
end; 

var 
    tmpList: TList; 
    i: Integer; 
begin 
    tmpList := IdServer.Contexts.LockList; 
    try 
    for i := 0 to tmpList.Count-1 do 
    begin 
     if TMyContext(tmpList[i]).Tag = idReceiver then 
     TMyContext(tmpList[i]).Queue.Add('Message'); 
    end; 
    finally 
    IdServer.Contexts.UnlockList; 
    end; 
end; 
+0

感谢您对线程安全的提醒,我会以这种方式使用它,但是这又出现了一个问题,原始问题仍然没有答案。因此,为了在serverexecute过程之外编写代码,我应该实现一个队列,并为在serverexecute过程中的操作,例如对发送者客户端的直接响应,我可以在队列检查完成后在服务器执行过程中执行任务吗?还是我应该写信给队列呢?而且似乎可以遍历所有的广播连接,但... –

+0

...但是对于从一个客户端到另一个客户端的简单消息转发或文件传输迭代每次都不会是愚蠢的?我可以用他们的ip和TIdContext变量为每个客户端声明一个类,并为每个客户端分配它的上下文吗?它会安全吗? –

+2

我的回答确实解决了您的原始问题 - 考虑到性能和线程安全性,进行广播/转发的最佳方式是使用每客户端队列。无论是广播还是转发,您仍然需要遍历'Contexts'列表来查找要发送到的客户端(除非您实现自己的线程安全查找,例如将多个'TIdContext'指针组合在一起)。通过将大部分工作卸载到每个客户端的'OnExecute'事件,队列有助于最大限度地减少'Contexts'列表需要被锁定的时间... –