Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Is it possible to combine TIdTCPServer components with TClientSocket ?
#1
Hello. I am using Embarcadero C++ and I have Indy 10.6.2.0 installed.

I have 4 components on my form:

TIdTCPClient *Client;
TIdTCPServer *Server;

TClientSocket *ClientSocket;
TServerSocket *ServerSocket;

Client is configured to connect to Server;
ClientSocket is configured to connect to ServerSocket;

If I assign ClientConnect() to Button1::OnClick event, it connects successfully.

If I assign ClientSocket->Active=true to Button2::OnClick event, it also connects successfully.

However, if I try to connect by setting ClientSocket->Active=true in Server::OnConnect event:
{
ClientSocket->Active=true;
}

It doesn't work. The same is true if I try to connect in OnContextCreate or OnExecute events.

Why is this happening?
Reply
#2
(copied from my answer to your same question on StackOverflow):

Quote:By default, TClientSocket operates in non-blocking mode (TClientSocket::ClientType = ctNonBlocking). In that mode, the client uses an HWND window internally to handle socket events. It also means that the client connects to its server asynchronously, delivering a completion event to its HWND once the connection is successful or fails.

An HWND window has thread affinity, meaning it can only operate within the thread that creates it, and that thread must have a message loop to process and dispatch any messages that are sent to that window.

The TButton events are being fired in the main UI thread, and thus use the main UI message loop for processing. If the TClientSocket creates its HWND in the main UI thread, then everything is handled for you.

However, the TIdTCPServer events are fired in separate worker threads, one for each client that connects to it, and those threads do not have messages loops by default. So, you can't use TClientSocket in non-blocking mode inside of a TIdTCPServer thread without some extra work. Specifically, you must either:
  • give each TIdTCPServer client thread a message-dispatching loop, ie in the server's OnExecute event.
  • otherwise, put the TClientSocket into blocking mode (TClientSocket::ClientType = ctBlocking) instead, in which case no HWND is used at all, and the client connects to its server synchronously. However, you will then have to use a TWinSocketStream object to handle all reads/writes on that TClientSocket after its connection is successful.

Reply
#3
Yes, the connection worked. The TClientSocket component has a good OnRead property, which means that I can connect to the server and have a constant connection. Data comes from the server and I can read it immediately without reconnecting. Is there a similar possibility in TIdTCPClient? The component has an OnConnect property, but it only triggers once. It is unclear how to receive data without reconnecting.
Reply
#4
(03-21-2023, 02:59 AM)Max2009 Wrote: Yes, the connection worked. The TClientSocket component has a good OnRead property, which means that I can connect to the server and have a constant connection. Data comes from the server and I can read it immediately without reconnecting. Is there a similar possibility in TIdTCPClient? The component has an OnConnect property, but it only triggers once. It is unclear how to receive data without reconnecting.

The TClientSocket.OnRead event operates asynchronously only. You must use the TClientSocket in non-blocking mode, which means it uses an internal window, and so the owning thread must have a message pump to dispatch window messages. The TClientSocket.OnRead event is fired whenever the client's internal window receives an FD_READ notification from the socket.

TIdTCPClient, on the other hand, operates synchronously only, there are no events (the OnConnect event is merely a callback that is called by TIdTCPClient.Connect() before it returns to its caller). If you need to receive data asynchronously from a server using TIdTCPClient, you must either:
  • have a worker thread read from the TIdTCPClient.IOHandler in a blocking manner, and then delegate received data where needed.
  • or else, have the main thread use a UI timer or sleep loop to periodically poll the TIdTCPClient.IOHandler for new data using a short timeout, and then use any received data as needed.

Reply
#5
Another problem has arisen. When a client is connected to TIdTCPServer *Server, OnConnect is triggered, but when the client is disconnected, OnDisconnect does not trigger, and it only triggers when the server is disconnected by setting Server->Active=false. Why is this happening?
Reply
#6
The TIdTCPServer.OnDisconnect event is fired when the server thread that owns the socket is being shut down.  There are 2 reasons I can think of for why that event might not happen when you are expecting it:
  • if your server code is preventing TIdTCPServer from recognizing the disconnect so it can tell the appropriate thread to stop running.  For instance, if your TIdTCPServer.OnExecute event handler swallows Indy exceptions from bubbling up the call stack.  Remember, Indy uses exceptions to report errors, which includes when a peer disconnects while a blocking socket operation is in progress.  If TIdCPServer catches an exception raised from its events, it will shut down the raising thread. So, if you are catching exceptions in your own code, make sure you re-raise any exception that is derived from EIdException, at least.
  • if the disconnect is abnormal in such a way that the OS has not detected it yet, then it will not have been reported to Indy yet.  Eventually, the OS will timeout internally and report errors on a lost connection, but that timeout is quite large, so it may take awhile.  You can use timeouts in your own code to help avoid that.  Either with Indy's own ReadTimeout properties and function parameters, or by enabling TCP keep-alives on the connection.

Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)