limelect 48 Posted August 26, 2023 (edited) I need just advice. I have no problems. I have IdTCPServer IdTCPClient with more than one client. I am using the server as a response to clients. However, I would like to send specific client information from the server, which is impossible. However, I saw a TIMER answer which I liked at the end of https://stackoverflow.com/questions/5530038/how-to-handle-received-data-in-the-tcpclient-delphi-indy Here comes the knowledge. If the server sends a message to whom does he send it? To all clients? Or else? The server message is not a response to the client message. I could not find a write instruction with the peer number. As I have the PeerPort on my server software (I keep a list) I suspect it is not possible to send from server text to a specific Client. A solution is to embed in the message a sign that a specific client will recognize. Any other solution in your opinion? Edited August 26, 2023 by limelect Share this post Link to post
limelect 48 Posted August 27, 2023 I have a nice solution You can send any string to any client This is just the general idea and someone mite use this On the client put the timer procedure TForm1.Timer1Timer(Sender: TObject); var s: string; begin if not IdTCPClient1.Connected then Exit; if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit; s := IdTCPClient1.IOHandler.InputBufferAsString; Memo1.Lines.Add('Received: ' + s); end; On the server Use the list procedure TForm2.Button2Click(Sender: TObject); var List: TList; T:TStringList; i:Integer; begin T:=TStringList.Create; t.Add('1234'); List := IdTCPServer1.Contexts.LockList; TIdContext(List.Items[0]).Connection.WriteRFCStrings(t); IdTCPServer1.Contexts.UnlockList; t.Free; end; Keep the client list List.Items[0]<< 1 2 3 4 and so on It needs more work but it works This is a general idea; I added listview with the client list and used it And it works nicely. TIdContext(List.Items[StrToInt( ListView1.Selected.Caption)]).Connection.WriteRFCStrings(t); Share this post Link to post
mjustin 23 Posted August 27, 2023 (edited) 21 hours ago, limelect said: I suspect it is not possible to send from server text to a specific Client. A solution is to embed in the message a sign that a specific client will recognize. Any other solution in your opinion? With Indy, the recommended way to send a message to a specific client is to assign an unique client id to new connections in the OnConnect method of the server. The server application then can use the client id to limit the message to one (or some) of the connected clients. Technically, the client id would be a property in a subclass of the TIdContext class, and this class must be "registered" with the Indy TCP server. There are plenty of articles on the web covering this technique. Edited August 27, 2023 by mjustin Share this post Link to post
limelect 48 Posted August 27, 2023 7 hours ago, mjustin said: With Indy, the recommended way to send a message to a specific client is to assign an unique client id I think i do not understand. The peerport is assigned AFTER connection. An ID? where is it. Do you know it before connection.Does it equel peerport so it is the same. In my techinc LIST has all necesery information to identify the client. PeerPort changes every time you reconnect to server and is not fixed. Further more in the SERVER I associate user to peerport of the client so I know who is using the client. In the IdTCPServer1Execute I know who is connected as I send info from client to server So what is left to do is pick the right info from my LIST port=user and sent what ever to port. Share this post Link to post
limelect 48 Posted August 27, 2023 To clarify the application The client is a worker. He has a number in the server DB. Connection is done when the worker send his ID=DB number and get needed information from server. The exchange is very simple. Obviusly the server gets worker number and associate the peerport to it. I get all kind of messeges for worker where he get data from DB in the server. Share this post Link to post
Fr0sT.Brutal 900 Posted August 28, 2023 When server accepts client connection, it creates its internal client object. You can send anything to that server-side client object. But client must be ready to read that data which it hadn't requested. You can assign any data to a server-side client object when it gets created to identify it. Share this post Link to post
DelphiUdIT 178 Posted August 28, 2023 What @mjustin meant is that normally a custom CONTEXT is used where for each connection received additional data can be inserted, such as an ID, a particular string of marking, or any other information we need for the activity. Example: TMyContext = class(TIdServerContext) // <-- must derive from TIdServerContext public /// <summary> This is a new RefID field </summary> RefID: UInt64; ChargeStr: string; end; //Somewhere, BEFORE activate the server assign MyContext to the component begin IdTCPServer1.ContextClass := TMyContext; IdTCPServer1.Activate := True; end; //In the Connect event you can initialize the Context procedure IdTCPServer1Connect(AContext: TIdContext); var Ctx: TMyContext; begin Ctx := TMyContext(AContext); Ctx.RefID := 0; Ctx.ChargeStr := ''; end; //Now you can use the new context whereever you want 1 Share this post Link to post
Remy Lebeau 1414 Posted August 28, 2023 On 8/26/2023 at 9:01 AM, limelect said: I am using the server as a response to clients. However, I would like to send specific client information from the server, which is impossible. It is not impossible. You simply send the desired data using the IOHandler of the TIdContext object that represents the client on the server. But, you do have to be VERY careful when sending unsolicited (ie, non-requested) data from the server to a client. If you are not careful and send the unsolicited data incorrectly, you can easily corrupt your TCP communication. You need to make sure the client is not trying to request data at the same time that you are sending unsolicited data, or the client might misinterpret the data. Most protocols tat support unsolicited data design their messages in such a way that clients can identify what messages are unsolicited and what messages are responses to requests. Is this particular client EVER requesting data from the server? Or does the server ALWAYS initiate the sending? It makes a BIG difference in how you design your protocol and manage it in your code. So it is kind of difficult to answer your question definitively without knowing more about your communication needs. On 8/26/2023 at 9:01 AM, limelect said: Here comes the knowledge. If the server sends a message to whom does he send it? To all clients? Or else? TCP is a 1:1 connection between client and server. There is no broadcasting. The data will be sent only to the specific client that you send it to. On 8/26/2023 at 9:01 AM, limelect said: The server message is not a response to the client message. Are there ANY requests being sent from this client to the server? If yes, then you need to synchronize your sends so that unsolicited data does not overlap response data. This is typically done by creating a per-client queue of outgoing data, and then having the server's OnExecute event send the calling client's queue when it is safe to do so, ie when it is not servicing any requests. If no, then you can simply send data directly to the client whenever you want. On 8/26/2023 at 9:01 AM, limelect said: I could not find a write instruction with the peer number. There is no "peer number" in TCP. But, the server does have a unique socket handle (ie, TIdContext object) for each connected client. And you can always assign your own ID to each client, as well. You would simply send to whichever socket/context that you are interested in. This is typically handled inside the server's OnExecute event, using the TIdContext that is passed in as a parameter. But, depending on your needs, you can also search the server's Contexts list for the desired client and then send to it (if/when safe to do so). On 8/26/2023 at 9:01 AM, limelect said: As I have the PeerPort on my server software (I keep a list) The PeerPort by itself is not adequate enough to uniquely identify clients. If clients connects to the server from different networks, they could use the same PeerPort. You would need the combination of PeerIP+PeerPort to uniquely identify each client. Better to just use the TIdContext objects instead, since they are already unique. On 8/26/2023 at 9:01 AM, limelect said: I suspect it is not possible to send from server text to a specific Client. Yes, it is. 1 Share this post Link to post
limelect 48 Posted August 29, 2023 Thank you all. I was thinking of using the above when someone changes data in the server and notifying the client. @Remy Lebeau Thanks very enlightening. For now, the clients are those that initiate the need for data from the server. Share this post Link to post
limelect 48 Posted August 29, 2023 (edited) 13 hours ago, Remy Lebeau said: Better to just use the TIdContext objects instead, since they are already unique. Just for knowledge. How will you save TIdContext and then interrogate the list? In my case, I use a stringlist with peernumber=worker# it is very helpful to search for peernumber. or vice versa. clients.IndexOfName(IntToStr(PeerPort)); in one case. or vice-versa. P.S. I do not suspect using outside the network of the project. Edited August 29, 2023 by limelect Share this post Link to post
Remy Lebeau 1414 Posted August 29, 2023 10 hours ago, limelect said: Just for knowledge. How will you save TIdContext and then interrogate the list? In my case, I use a stringlist with peernumber=worker# it is very helpful to search for peernumber. or vice versa. clients.IndexOfName(IntToStr(PeerPort)); in one case. or vice-versa. P.S. I do not suspect using outside the network of the project. Again, the PeerPort alone is typically not unique enough to identify clients. You are better off assigning your own custom IDs to the TIdContext objects when clients connect/login, eg: type TMyContext = class(TIdServerContext) UserID: string; end; var Clients: TIdThreadSafeStringList; procedure TMyForm.FormCreate(Sender: TObject); begin Clients := TIdThreadSafeStringList.Create; IdTCPServer1.ContextClass := TMyContext; IdTCPServer1.Active := True; end; procedure TMyForm.FormDestroy(Sender: TObject); begin IdTCPServer1.Active := False; Clients.Free; end; procedure TMyForm.IdTCPServer1Connect(AContext: TIdContext); var UserID: string; List: TStringList; begin UserID := ...; // read user ID from client List := Clients.Lock; try if List.IndexOf(UserID) <> -1 then begin AContext.Connection.Disconnect; Exit; end; List.AddObject(UserID, AContext); TMyContext(AContext).UserID := UserID; finally Clients.Unlock; end; end; procedure TMyForm.IdTCPServer1Disconnect(AContext: TIdContext); begin if TMyContext(AContext).UserID <> '' then Clients.Remove(TMyContext(AContext).UserID); end; And then you can search your Client's list for your user IDs whenever you need, and then you'll have access to the TIdContext object. Just be careful accessing it if it happens at the same time that the client disconnects. That's why if you intend to send data from outside of the server's own threads, it is best to add a queue to the TIdContext and have the OnExecute event send the queue when safe to do so. 1 Share this post Link to post
limelect 48 Posted August 29, 2023 @Remy Lebeau As usual you are the greatest. Thanks But also everybody thanks Share this post Link to post