简体   繁体   English

具有400多个连接的Indy TCP Server

[英]Indy TCP Server at 400+ connections

I am trying to handle a few thousand users through Indy TCP servers but i always saw very high memory consumption even with a few hundred users... i just wrote a bot to test out the performance of the server handling the data. 我试图通过Indy TCP服务器处理几千个用户,但是即使有几百个用户,我也总是看到非常高的内存消耗...我只是写了一个机器人来测试处理数据的服务器的性能。 I connected 300 bots to the test server and started sending packets through. 我将300个漫游器连接到测试服务器,并开始通过发送数据包。 The memory usage climbed to a few hundred MB in matter of minutes... 几分钟内,内存使用量就攀升至数百MB。

After going through codes i noticed that the main issue was with using sender queue for each thread so each thread can transmit its messages in its Execute function. 经过检查代码后,我注意到主要的问题是为每个线程使用发送方队列,以便每个线程都可以在其Execute函数中传输其消息。 If 300 users are sending packets to each other and writing data to each thread's queue then it cause the memory to overload... Here is what i am doing and can anyone suggest any better way to handle this? 如果300个用户正在互相发送数据包并将数据写入每个线程的队列,那么它将导致内存过载...这就是我正在做的事情,有人可以建议解决这个问题的更好方法吗?

When a client is sending message to another client this function is called and is supplied with the context of that thread/client/conneciton 当客户端向另一个客户端发送消息时,将调用该函数,并随该线程/客户端/连接的上下文一起提供

Procedure TMainFrm.SendRoomBuffer(Packet: Pointer; Size: Integer; Context: TIdContext);
Var
  LocalBuffer: Pointer;
  Connected: Boolean;
Begin
    If Size < 1 then
        Exit;

    Try
        If Context <> Nil Then
           Connected := TRoomContext(Context).Connection.Connected
        Else
          Connected := False;
    Except
        Connected := False;
    End;

    If Connected = True Then Begin
        GetMem(LocalBuffer,Size);
        CopyMemory(LocalBuffer,Packet,Size);
        TRoomContext(Context).Queue.Add(LocalBuffer);
    End;
End;

Iterates through all the users present in the room and send them the packet 遍历房间中存在的所有用户并向他们发送数据包

Lst := Room.UsersList.LockList;
Try
    For I := 0 To Lst.Count -1 Do Begin
      Try
        Username := TRoomUserInfo(Lst.Items[I]).UserName.Value;
        If IncludingMe = False Then Begin
          If LowerCase(Username) <> LowerCase(MyNick) Then
            SendRoomBuffer(Packet, PacketSize, TRoomUserInfo(Lst.Items[I]).Context)
        End Else
          SendRoomBuffer(Packet, PacketSize, TRoomUserInfo(Lst.Items[I]).Context);
      Finally
        Username := '';
      End;
    End;
Finally
    Room.UsersList.UnlockList;
    Lst := Nil;
End;

This is where the actual sending is done, in the Execute of IdTCPServer 这是在IdTCPServer的Execute中完成实际发送的位置

If Not TRoomContext(AContext).Queue.IsEmpty Then Begin
    tmpQueue := TRoomContext(AContext).Queue.LockList;
    Try
      While tmpQueue.Count > 0 Do Begin
        outBuffer := tmpQueue.items[0];
        Try
          outLen := PCommunicatorPacket(outBuffer).BufferSize;
          SetLength(outBuf,outLen);
          Try
            CopyMemory(@outBuf[0],outBuffer,outLen);
            Try
              If Connected Then
                AContext.Connection.IOHandler.Write(outBuf)
            Finally
              tmpQueue.Delete(0);
            End;
          Finally
            SetLength(outBuf,0);
            outBuf := Nil;
          End;
        Finally
          If outBuffer <> Nil Then Begin
            FreeMem(outBuffer);
            outBuffer := Nil;
          End;
        End;
      End;
    Finally
      TRoomContext(AContext).Queue.UnlockList;
      tmpQueue := Nil;
    End;
End;

Complete OnExecute Function 完成OnExecute功能

Procedure TMainFrm.RoomSckExecute(AContext: TIdContext);
Var Buf: TIdBytes;
    Len: Integer;
    outBuffer: PIdBytes;
    tmpQueue: TList;
Begin
  AContext.Connection.IOHandler.CheckForDataOnSource(10);
  if not AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    Len := AContext.Connection.IOHandler.InputBuffer.Size;
    AContext.Connection.IOHandler.ReadBytes(Buf, Len, False);
    TRoomContext(AContext).ProcessPacket(@Buf[0], Len, AContext);
    SetLength(Buf, 0);
    Buf := nil;
  end;

    tmpQueue := TRoomContext(AContext).Queue.LockList;
  try
    while tmpQueue.Count > 0 do begin
      outBuffer := PIdBytes(tmpQueue.Items[0]);
      try
        tmpQueue.Delete(0);
        AContext.Connection.IOHandler.Write(outBuffer^);
      finally
        Dispose(outBuffer);
      end;
    end;
  finally
    TRoomContext(AContext).Queue.UnlockList;
  end;
End;

If a single client is sending to 300 clients present in the room then 300 copies of the packets are made and are freed only when the actual sending is done... 如果单个客户端正在向房间中的300个客户端发送邮件,那么将复制300个数据包副本,并且仅在完成实际发送后才释放它们。

If i do the writing directly to each connection and not by using queues then the memory consumption is not as rogue as this method but server hangs after a few minutes 如果我直接写每个连接而不是使用队列,则内存消耗不像这种方法那么糟糕,但是服务器会在几分钟后挂起

Sorry if i forgot to mention any more details. 对不起,如果我忘了提及更多细节。

PS: I am using Delphi 7 PS:我正在使用Delphi 7

EDIT: I just check, if i dont actually write to socket, and go with the whole process as is, then the issue doesn't happen... so it means the time it takes to write to the socket, there are over a few hundred more packets read... 编辑:我只是检查,如果我实际上不写套接字,并按原样进行整个过程,那么问题就不会发生...所以这意味着写套接字所需的时间,读取了几百个数据包...

EDIT 2 I copied your code for the OnExecute, if i don't prove a length to ReadBytes then it takes some time about 3-5 seconds to process each command, so i am providing it with the length to read... And i used madexcept it doesnt show any leaks, i am gonna try with FastMM too now... but if there was actually a leak and something was causing it then why would commenting out the actual Write command in OnExecute suppress the memory usage? 编辑2我为OnExecute复制了您的代码,如果我没有证明ReadBytes的长度,那么处理每个命令大约需要3-5秒的时间,所以我为它提供了读取的长度...而且我使用madexcept不会显示任何泄漏,我现在也要尝试使用FastMM ...但是如果实际上存在泄漏并且是由某种原因引起的,那么为什么在OnExecute注释掉实际的Write命令OnExecute抑制内存使用?

EDIT 3 To explain my question further, i am actually reading the bytes from the stream and then process them myself later to make distinct packets from them, here is the code of what happens further after the data is read from the socket. 编辑3为了进一步解释我的问题,我实际上是从流中读取字节,然后稍后自己处理它们以与它们形成不同的数据包,这是从套接字读取数据后进一步处理的代码。

...
FPacketBuffer: Pointer; // global memory upto 65kb for each client to store the incoming data
PacketBufferPtr: Integer; // the offset upto where the data is read from the global memory
...

procedure TRoomContext.ProcessPacket(Buffer: Pointer; BufSize: Integer; Context: TIdContext);
begin
  AddToPacketBuffer(Buffer,BufSize);
  CheckAndProcessPacket(Context);
end;

procedure TRoomContext.AddToPacketBuffer(Buffer: Pointer; Size: Integer);
var
  DestPtr: Pointer;
begin
  if PacketBufferPtr + Size<65536 then
  begin
    DestPtr := Pointer(Cardinal(FPacketBuffer)+Cardinal(PacketBufferPtr));
    Move(Buffer^,DestPtr^,Size);
    PacketBufferPtr := PacketBufferPtr + Size;
  end
  else
  begin
  end;
end;

procedure TRoomContext.CheckAndProcessPacket(Context: TIdContext);
var DestPtr: Pointer;
  NewPacketBufferLen: Integer;
  SharedBuff: Pointer;
begin
 if PCommunicatorPacket(FPacketBuffer).Signature = PACKET_SIGNATURE then
  begin
  while PCommunicatorPacket(FPacketBuffer).BufferSize <= PacketBufferPtr do
  begin
      GetMem(SharedBuff,PCommunicatorPacket(FPacketBuffer).BufferSize);
      Try
        CopyMemory(SharedBuff,FPacketBuffer,PCommunicatorPacket(FPacketBuffer).BufferSize);
        MainFrm.ExecuteRoomPacket(SharedBuff, Context);
      Finally
        If SharedBuff <> Nil Then FreeMem(SharedBuff);
      End;
    NewPacketBufferLen := PacketBufferPtr - PCommunicatorPacket(FPacketBuffer).BufferSize;
    DestPtr := Pointer(Cardinal(FPacketBuffer)+PCommunicatorPacket(FPacketBuffer).BufferSize);
    Move(DestPtr^, FPacketBuffer^, NewPacketBufferLen);
    PacketBufferPtr := NewPacketBufferLen;
  end;
  end
    else
    begin
      DropInvalidPacket;
      Inc(InvalidPackets);
      If InvalidPackets > 50 Then
        Context.Connection.Disconnect;
      Exit; 
    end;
end;

Apologies for thinking it was because of the writing, the writing actually just slowed deletion from queue which made me think so, if i even put a sleep of 10 milliseconds, the memory consumption go rogue. 道歉是因为写作,实际上写作只是减慢了从队列中删除的速度,这让我这样想,即使我什至睡了10毫秒,内存消耗也很糟糕。 About the leaks... one other reason i think this is not a leak is because if i stop the bots from messaging further, then the used memory gets back to where it was, but if i leave it running for a few minutes then it goes to a point where the application hangs or i receive an out of memory message. 关于泄漏...我认为这不是泄漏的另一个原因是,如果我停止进一步发送消息的机器人,则使用的内存将恢复到原来的状态,但是如果我让它运行几分钟,则它将到达应用程序挂起或我收到内存不足消息的地步。 I think the issue is with making copies, i tried using a global queue for the room to handle messages and so multiple copies aren't made of the data, but that cause the application to hang after sometime maybe too much thread contention or i am not playing it safe. 我认为问题出在复制上,我尝试使用一个全局队列在房间中处理消息,因此并没有从数据中复制多个副本,但是这导致应用程序在一段时间内可能因为过多线程争用而挂起,或者不安全地玩。

TCP does not support broadcasting, and directly writing to TIdTCPServer connections from outside the server's events is generally not thread-safe (although it can be done if you are careful). TCP不支持广播,并且从服务器事件外部直接写入TIdTCPServer连接通常不是线程安全的(尽管可以小心操作)。 In your situation, using queues is a good idea. 在您的情况下,使用队列是个好主意。

However, don't call Connected() in SendRoomBuffer() . 但是,不要在SendRoomBuffer()调用Connected() SendRoomBuffer() It performs a read operation, which can interfere with any reading the OnExecute event handler performs, and can corrupt the InputBuffer 's content by reading socket data out of order. 它执行读取操作,这可能会干扰OnExecute事件处理程序执行的任何读取,并且可能会通过乱序读取套接字数据来破坏InputBuffer的内容。 If Context is not nil then queue the data regardless of the socket state, and catch any errors. 如果Context不为nil,则无论套接字状态如何,都将数据排队,并捕获任何错误。

Also, in the OnDisconnect event, make sure you are freeing any queued packets that were not sent, otherwise you will leak them. 另外,在OnDisconnect事件中,请确保释放所有未发送的排队数据包,否则将泄漏它们。

Lastly, your OnExecute code is making another copy of the queued data and then sending that copy (I am assuming that outBuf is a TIdBytes ). 最后,您的OnExecute代码正在制作队列数据的另一个副本,然后发送该副本(我假设outBufTIdBytes )。 Try to avoid that. 尽量避免这种情况。 I would suggest you change your queue to store TMemoryStream or TIdBytes objects instead of raw memory blocks, then you can pass the queued items directly to IOHandler.Write() without having to make copies of them first. 我建议您更改队列以存储TMemoryStreamTIdBytes对象而不是原始内存块,然后可以将排队的项目直接传递到IOHandler.Write()而不IOHandler.Write()制作它们的副本。

Try something like this: 尝试这样的事情:

type
  PIdBytes = ^TIdBytes;

procedure TMainFrm.SendRoomBuffer(Packet: Pointer; Size: Integer; Context: TIdContext);
var
  LocalBuffer: PIdBytes;
begin
  if (Packet = nil) or (Size < 1) or (Content = nil) then
    Exit;

  New(LocalBuffer);
  try
    LocalBuffer^ := RawToBytes(Packet^, Size);
    TRoomContext(Context).Queue.Add(LocalBuffer);
  except
    Dispose(LocalBuffer);
  end;
end;

Lst := Room.UsersList.LockList;
try
  for i := 0 To Lst.Count -1 do begin
    Username := TRoomUserInfo(Lst.Items[i]).UserName.Value;
    if (not IncludingMe) and TextIsSame(Username, MyNick) then begin
      Continue;
    end;
    SendRoomBuffer(Packet, PacketSize, TRoomUserInfo(Lst.Items[i]).Context);
  end;
finally
  Room.UsersList.UnlockList;
end;

procedure TMainFrm.RoomSckDisconnect(AContext: TIdContext);
var
  tmpQueue: TList;
  i: Integer;
begin
  ...
  tmpQueue := TRoomContext(AContext).Queue.LockList;
  try
    for i := 0 to tmpQueue.Count-1 do begin
      Dispose(PIdBytes(tmpQueue.Items[i]));
    end;
    tmpQueue.Clear;
  finally
    TRoomContext(AContext).Queue.UnlockList;
  end;
  ...
end;

procedure TMainFrm.RoomSckExecute(AContext: TIdContext);
var
  Buf: TIdBytes;
  outBuffer: PIdBytes;
  tmpQueue: TList;
begin
  AContext.Connection.IOHandler.CheckForDataOnSource(10);
  if not AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    AContext.Connection.IOHandler.ReadBytes(Buf, -1, False);
    TRoomContext(AContext).ProcessPacket(@Buf[0], Len, AContext);
    SetLength(Buf, 0);
    Buf := nil;
  end;

  tmpQueue := TRoomContext(AContext).Queue.LockList;
  try
    while tmpQueue.Count > 0 do begin
      outBuffer := PIdBytes(tmpQueue.Items[0]);
      try
        tmpQueue.Delete(0);
        AContext.Connection.IOHandler.Write(outBuffer^);
      finally
        Dispose(outBuffer);
      end;
    end;
  finally
    TRoomContext(AContext).Queue.UnlockList;
  end;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM