-
Content Count
3055 -
Joined
-
Last visited
-
Days Won
139
Everything posted by Remy Lebeau
-
There is quite a lot wrong with the server code you have shown. TIdTCPServer is a multi-threaded component. The listening ports are managed in worker threads. Each connected client is handled in its own worker thread. Your management of your f_CClients list is not thread-safe, or even accurate in places. It is also redundant, as TIdTCPServer already has a thread-safe list of connected clients in its Contexts property. You should just get rid of your f_CClients list altogether and use the Contexts list by itself. You can store your tCClient objects in the TIdContext.Data property, or you can derive it from TIdServerContext and then set it to the TIdTCPServer.ContextClass property before activating the server. You are accessing your TMemo and TListView directly in the context of each client worker thread. Any access to the UI from a worker thread must be synchronized with the main UI thread, which you are not doing at all. Use TThread.Synchronize() or TThread.Queue() (or Indy's TIdSync or TIdNotify) for that purpose. Do be careful with TThread.Synchronize()/TIdSync, though. Since they are synchronous, if you deactivate the server in the main UI thread, and then try to sync with the UI thread, you will deadlock the server. So, don't sync synchronously while deactivating the server, or do the deactivation in a separate thread so the main UI thread remains free to process sync requests. You are directly sending your QUIT commands to clients from the context of the main UI thread. In this example, you are not sending anything else to the clients, but if you were, you would potentially be overlapping any sends those clients' threads may happen to be performing at the same time, which would corrupt your communications. You must serialize access to a client's socket when sending to it across thread boundaries. It is generally best to keep your socket I/O with a given client in that client's worker thread as much as possible. In short, your server code needs a good amount of rewriting to operate safely in a multi-threaded environment.
-
Migrate an old app in Delphi 2009 to modren C++
Remy Lebeau replied to Alkanium's topic in General Help
You should be able to, as it should have the command-line Delphi compiler available, so that 3rd party components can still be compiled and installed into the IDE for C++Builder to use. -
Indy TIdTCPClient do not detect Disconnections.
Remy Lebeau replied to sBsaidus's topic in Network, Cloud and Web
Indy uses blocking sockets and synchronous I/O. The client's OnDisconnected event is fired when the *client* disconnects on its end. If the server disconnects first, there is no real-time notification of that. The client will notify your code only when the client tries to access the connection and gets an error from the OS, at which time it will raise an exception to your code, such as EIdConnClosedGracefully, etc. So, if you want timely notification of a remote disconnect, you need to actively send/read data. If your code is not using the connection for lengths of time, use a timer to poll for incoming data periodically. Or use a reading loop in a worker thread. -
Per the CE FAQ: https://www.embarcadero.com/products/delphi/starter/faq
-
I don't know, I thought they were done maintaining a copy, but I guess not. The readme on that mirror says:
-
Influenced by C# (like several features in Delphi have been): https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/raw-string-literal
-
Streams - Writing and Reading data or text from
Remy Lebeau replied to JohnLM's topic in Algorithms, Data Structures and Class Design
It is working fine for me. Yes. My hosting provider did perform maintenance on my site's server yesterday, so maybe they messed something up, or have already resolved the issue. -
OK, well... The fulgan mirror has been decommissioned for some time now, the GitHub repo is the official spot now.
-
Streams - Writing and Reading data or text from
Remy Lebeau replied to JohnLM's topic in Algorithms, Data Structures and Class Design
But at least we get some of that back via Embarcadero MVP! -
Those are the same DLLs that used to be on the indy.fulgan.com site.
-
Yes, per the EULA: The EULA explains several restrictions on the Community Edition, but this is not one of them.
-
The Delay option was added in 10.4 Sydney, so it is a relatively recent addition (2020).
-
BCB6 predates Visual Styles and VCL Styles and basically most modern UI practices. For example, when UI changes are animated by the OS or Styles, it would thus take multiple paint cycles to fully animate the complete state change, and ProcessMessages() or Update() may not (likely will not) catch all of the paint cycles in a single call. You can mitigate that somewhat by using thread pools, for instance. There is nothing in UI programming that says like "state change is finished and the UI for it is fully drawn". Not even back in BCB6's day. Although back then, drawing was much simpler, and usually completed in 1 paint cycle. That may not be the case anymore in modern UIs. That is not really a factor of the property itself, but in the UI rendering behind the scenes, so more oriented to differ between OS versions rather than compiler/VCL versions. Update() only processes paint messages that are already pending in the message queue. But you also need Invaliidate() to generate those messages in the first place, if they are not already generated for you. So that is where Repaint() comes into play, to signal that a new paint cycle is needed if it hasn't already been signaled.
-
Yes, it does. But it can only handle messages that are already in the message queue (or synthetically generated by the queue) at the moment it is called. It does not process future messages. And the behavior you describe sounds like the actions are being delayed such that ProcessMessages() doesn't see all of them right away, which is why calling ProcessMessages() multiple times produces better results. You really should not be using ProcessMessages() in this manner in the first place. Break up your code logic using TThread::ForceQueue() or a TTimer or equivalent to allow control to flow back into the main message loop so it can continue to process new messages while your desired time interval is waiting to elapse. For example, using TThread::ForceQueue(): void __fastcall TForm1::Button1Click(TObject *Sender) { Button1->Enabled = false; Button2->Enabled = false; Button3->Enabled = false; TThread::ForceQueue(nullptr, &Step2, 3000); } void __fastcall TForm1::Step2() { Button1->Enabled = true; Button2->Enabled = true; Button3->Enabled = true; TThread::ForceQueue(&Step3, 3000); } void __fastcall TForm1::Step3() { //... } // alternatively void __fastcall TForm1::Button1Click(TObject *Sender) { Button1->Enabled = false; Button2->Enabled = false; Button3->Enabled = false; TThread::ForceQueue(nullptr, [this](){ Button1->Enabled = true; Button2->Enabled = true; Button3->Enabled = true; TThread::ForceQueue(nullpr, [](){ //... }, 3000); }, 3000); } Or, using TTimer: void __fastcall TForm1::Button1Click(TObject *Sender) { Button1->Enabled = false; Button2->Enabled = false; Button3->Enabled = false; //Timer1->Interval = 3000; Timer1->Tag = 1; Timer1->Enabled = true; } void __fastcall TForm1::Timer1Timer(TObject *Sender) { if (Timer1->Tag == 1) { Button1->Enabled = true; Button2->Enabled = true; Button3->Enabled = true; //Timer1->Interval = 3000; Timer1->Tag = 2; } else { //... Timer1->Enabled = false; Timer1->Tag = 0; } } // alternatively... void __fastcall TForm1::Button1Click(TObject *Sender) { Button1->Enabled = false; Button2->Enabled = false; Button3->Enabled = false; //Timer1->Interval = 3000; Timer1->OnTimer = Step2; Timer1->Enabled = true; } void __fastcall TForm1::Step2(TObject *Sender) { Button1->Enabled = true; Button2->Enabled = true; Button3->Enabled = true; //Timer1->Interval = 3000; Timer1->OnTimer = Step3; } void __fastcall TForm1::Step3(TObject *Sender) { //... Timer1->Enabled = false; Timer1->OnTimer = nullptr; }
-
Can you show the actual code you are having trouble with? What is "Meta" defined as? What is it you are trying to do with "Meta", exactly? We can't really help you without more detail. Or maybe just re-think your design? Perhaps interfaces are more suitable? Hard to say without seeing what you are actually trying to do. I'm sure there are ways to accomplish that, but again, it really depends on what your data looks like and how you are using it.
-
What @DelphiUdIT said. Indy 10 does have .dpk package files for Delphi 7, namely: IndySystem70.dpk IndyCore70.dpk IndyProtocols70.dpk dclIndyCore70.dpk dclIndyProtocols70.dpk
-
Did you download only the main code, or did you also download the PR #299 code on top of it? No ETA at this time. My understanding is that the code works as-is, but still needs to be updated/finalized to include design-time support, added to all of the supported packages, etc.
-
What, 1 click to open the list, and 1 click to select? I really don't think that is much of an issue. But if it is, then just use TRadioGroup/3xTRadioButton instead.
-
If UI space is an issue, you could use a TComboBox with Style=csDropDownList instead, and just have 3 items to choose from.
-
Call for Delphi 12 Support in OpenSource projects.
Remy Lebeau replied to Tommi Prami's topic in Delphi Third-Party
I also use {$LEGACYIFEND ON} in Indy for XE4+ versions. Helps the code remain backwards compatible with earlier versions by forcing the compiler to not accept {$ENDIF} to close {$IF}. The compiler can run into issues when you have nested {$IF} and {$IFDEF} blocks but don't use {$IFEND} consistently, which can trigger error E2029, as mentioned in the {$LEGACYIFEND} documentation (and I've run into it before). -
Call for Delphi 12 Support in OpenSource projects.
Remy Lebeau replied to Tommi Prami's topic in Delphi Third-Party
I don't remember the specific version, but I know there was a time when the compiler had problems with the evaluation order of {$IF} and {$IFDEF}, I think that was around the early Kylix days, so would have probably been around Delphi 6-8. But that is just a guess... -
IdTCPServer IdTCPClient knowledge.
Remy Lebeau replied to limelect's topic in Network, Cloud and Web
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. -
Class function vs constructor for object creation
Remy Lebeau replied to Michael Taylor's topic in Algorithms, Data Structures and Class Design
Standard strings are compiler-managed types. They are created dynamically and are also reference-counted, so the compiler needs to be able to manage and release them correctly. Don't just let them leak. Otherwise, use fixed-length character arrays if you really don't want to allocate their memory dynamically. Instances of a Class are created dynamically and must be Destroy'ed/Free'd to avoid memory leaks. Otherwise, use a Record instead of a Class. -
IdTCPServer IdTCPClient knowledge.
Remy Lebeau replied to limelect's topic in Network, Cloud and Web
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. 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. 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. 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). 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. Yes, it is. -
Current alternatives for SMTP with TLS 1.3
Remy Lebeau replied to LeusKapus's topic in Network, Cloud and Web
Indy's support for TLS 1.3 has already been implemented here, it just hasn't been merged into the main codebase yet. There is a difference. You are free to try the code for yourself and see if it meets your needs. Eventually, it will get merged in, I just couldn't tell you when. That doesn't mean you can't use it today. Other people are.