Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TCP server: hand over socket connection

Tags:

delphi

I'm using an idTCPServer for processing data. For a new device I need to hand over the socket to a dll (stop the tcp server to read from that socket).

Is that possible with Indy or ICS?

[Edit] For test purposes I made a thread instead of using the dll, because the lack of hardware.

  procedure TfrmIndyMain.IdTCPServer1Connect(AContext: TIdContext);
  begin
    FMyThread := TMyThread.Create(true);
    FMyThread.FSocket := AContext.Binding.Handle;
    FMyThread.Resume;
  end;  

  procedure TfrmTest.IdTCPServer1Execute(AContext: TIdContext);
  begin
    // Its necessary that indy stop reading from the socket, without reset a lot of messages is lost
    AContext.Binding.Reset(false); //not sure if this is correct
  end;

  procedure TMyThread.Execute;
  var
    len: integer;
    data: string;
  begin
    ioctlsocket(FSocket, FIONREAD, len);
    if len>0 then
    begin
      Setlength(data, len);
      if recv(FSocket, pointer(data)^, len, 0) <> SOCKET_ERROR then
        Log('Data: ' + data)
      else
        Log('Error');
    end;
  end;
like image 741
Arjen van der Spek Avatar asked Mar 12 '26 14:03

Arjen van der Spek


1 Answers

In Indy, you can use the TIdPeerThread.Connection.Binding.Socket property (Indy 9 and earlier) or the TIdContext.Connection.Socket.Binding.Handle property (Indy 10 - shortcut as TIdContext.Binding.Handle) to access the underlying SOCKET handle.

Update: Indy maintains an InputBuffer of data that is read from the socket during Indy-based reading operations. That includes calls to TIdTCPConnection.Connected(), which TIdTCPServer calls in between successive triggers of the OnExecute event. So it is possible that you do not see all of the data that is on the socket because you are not looking at the InputBuffer for cached data. To avoid that, you would have to make sure the event only triggers once by running your own reading loop inside the event and avoid calling any of Indy's reading methods, eg:

procedure TfrmTest.IdTCPServer1Execute(AContext: TIdContext);
var
  ret: Integer;
  data: AnsiString;
  tv: timeval;
  fd: fd_set;
begin
  repeat
    FD_ZERO(@fd);
    FD_SET(AContext.Binding.Handle, @fd);
    tv.tv_sec := 1;
    tv.tv_usec := 0;
    ret := select(0, @fd, nil, nil, @tv);
    if ret = SOCKET_ERROR then
    begin
      Log('Error on select()');
      Break;
    end;
    if ret = 0 then Continue;
    ret := ioctlsocket(AContext.Binding.Handle, FIONREAD, len);
    if ret = SOCKET_ERROR then
    begin
      Log('Error on ioctlsocket()');
      Break;
    end;
    if len < 1 then Break;
    SetLength(data, len);
    len := recv(AContext.Binding.Handle, Pointer(data)^, len, 0);
    if len = SOCKET_ERROR then
    begin
      Log('Error on recv()');
      Break;
    end;
    SetLength(data, len);
    Log('Data: ' + data);
  until (some stop condition);
  AContext.Connection.Disconnect;
end;

If possible, a better solution would be to change your logic to not need to access the socket directly anymore. Let Indy do all of the reading normally, and then pass any received data to the rest of your code for processing, eg:

procedure TfrmTest.IdTCPServer1Execute(AContext: TIdContext);
var
  data: TMemoryStream;
begin
  with AContext.Connection.IOHandler do
  begin
    if InputBufferIsEmpty then
    begin
      CheckForDataOnSource(1000);
      CheckForDisconnect(True);
      if InputBufferIsEmpty then Exit;
    end;
  end;
  data := TMemoryStream.Create;
  try
    with AContext.Connection.IOHandler do
      ReadStream(data, InputBuffer.Size, False);
    // use data.Memory up to data.Size bytes as needed...
  finally
    data.Free;
  end;
end;
like image 72
Remy Lebeau Avatar answered Mar 14 '26 05:03

Remy Lebeau