-
Content Count
3091 -
Joined
-
Last visited
-
Days Won
139
Everything posted by Remy Lebeau
-
[Logical advise needed] TCP persistent connection custom protocol server
Remy Lebeau replied to FearDC's topic in Indy
Try something like this (based on your earlier example): type TOnUserConnect = procedure(AConn: TIdContext) of object; TOnUserDisconnect = procedure(AConn: TIdContext) of object; TOnUserDataIn = procedure(AConn: TIdContext; const AData: String) of object; TOnUserDataOut = procedure(AConn: TIdContext; const AData: String) of object; type // server listener TMyServer = class(TIdCustomTCPServer) protected procedure InitComponent; override; private FOnUserConnect: TOnUserConnect; FOnUserDisconnect: TOnUserDisconnect; FOnUserDataIn: TOnUserDataIn; FOnUserDataOut: TOnUserDataOut; procedure DoConnect(AContext: TIdContext); override; // note: thread safety procedure DoDisconnect(AContext: TIdContext); override; // note: thread safety function DoExecute(AContext: TIdContext): Boolean; override; // note: thread safety procedure DoParseProtocol(AContext: TIdContext; const AData: String); public LWaitFor: String; // command separator property OnUserConnect: TOnUserConnect read FOnUserConnect write FOnUserConnect; property OnUserDisconnect: TOnUserDisconnect read FOnUserDisconnect write FOnUserDisconnect; property OnUserDataIn: TOnUserDataIn read FOnUserDataIn write FOnUserDataIn; property OnUserDataOut: TOnUserDataOut read FOnUserDataOut write FOnUserDataOut; end; procedure TMyServer.InitComponent; begin inherited InitComponent; ContextClass := TMyClient; end; procedure TMyServer.DoConnect(AContext: TIdContext); begin if Assigned(FOnUserConnect) then TThread.Queue(nil, procedure begin if Assigned(FOnUserConnect) then FOnUserConnect(AContext); end ); end; procedure TMyServer.DoDisconnect(AContext: TIdContext); begin if Assigned(FOnUserDisconnect) then TThread.Queue(nil, procedure begin if Assigned(FOnUserDisconnect) then FOnUserDisconnect(AContext); end ); end; function TMyServer.DoExecute(AContext: TIdContext): Boolean; var AData: String; begin (AContext as TMyClient).SendQueue; AData := AContext.Connection.IOHandler.WaitFor(LWaitFor, True, False, nil, 5000); if AData <> '' then begin TThread.Queue(nil, procedure begin if Assigned(FOnUserDataIn) then FOnUserDataIn(AConn, AData); DoParseProtocol(AConn, AData); end ); end; Result := AContext.Connection.Connected; end; procedure TMyServer.DoParseProtocol(AConn: TIdContext; const AData: String); var AUser: TMyClient; begin AUser := AConn as TMyClient; if AData.Equals('Validate') then begin AUser.FNick := '<parsed data>'; AUser.QueueData('Hello'); end else if AData.Equals('<bad command>') then begin AConn.Binding.CloseSocket; end; // this is the actual protocol parser, from here user connection will be // verified, protocol parsed, responses written, other helper classes // will check for bans, shared memory access will be performed, file // contents will be read from hard drive and sent back to users, // thread-unsafe objects will be used, users will get disconnected, // basically all the main load will be performed here end; ... uses ..., IdThreadSafe; type // user connection TMyClient = class(TIdServerContext) protected // FQueue: TIdThreadSafeStringList; ... procedure SendData(const AData: String); procedure SendQueue; public constructor Create(AConn: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override; destructor Destroy; override; procedure QueueData(const AData: String); end; constructor TMyClient.Create(AConn: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); begin inherited Create(AConn, AYarn, AList); ... FQueue := TIdThreadSafeStringList.Create; end; destructor TMyClient.Destroy; begin ... FQueue.Free; inherited Destroy; end; procedure TMyClient.QueueData(const AData: String); begin FQueue.Add(AData); end; procedure TMyClient.SendData(const AData: String); begin Connection.IOHandler.Write(AData); if Assigned((Server as TMyServer).FOnUserDataOut) then TThread.Queue(nil, procedure begin if Assigned((Server as TMyServer).FOnUserDataOut) then (Server as TMyServer).FOnUserDataOut(Self, AData); end ); end; procedure TMyClient.SendQueue; var QueueList: TStringList; SendList: TStringList; I: Integer; begin SendList := nil; try QueueList := FQueue.Lock; try if QueueList.Count = 0 then Exit; SendList := TStringList.Create; SendList.Assign(QueueList); QueueList.Clear; finally FQueue.Unlock; end; for I := 0 to SendList.Count-1 do SendData(SendList[I]); finally SendList.Free; end; end; ... Why? You don't need the thread handle for this task. But, that being said, it is technically possible if you do need it for some reason - you can type-cast the TIdContext.Yarn property to TIdYarnOfThread, and then use the TIdYarnOfThread.Thread.Handle property. handle. -
No. What you are looking at is just a namespace within the XML document. XML namespaces are formatted as URIs, but they are not required to refer to actual live sites. They just need to be unique within the XML document.
-
[Logical advise needed] TCP persistent connection custom protocol server
Remy Lebeau replied to FearDC's topic in Indy
You get that for free with TIdTCPServer, as each client runs in its own thread. Sending the answer from the main thread itself is not a good idea. If the send blocks, not only will you block all other requests and answers, but also block the UI, too. This goes back to my earlier suggestion that you should use a per-client queue for outgoing messages. The main thread can put the answer in the client's queue, and then the server's OnExecute event can send the client's queue when it is safe to do so, ie between reads of incoming messages. By default, no. Blocking sockets wait forever for operations to finish. But, if desired, you can use the TIdContext.Binding.SetSockOpt() method to set a timeout for the SO_SNDTIMEO option on the underlying socket. If a send times out, a failure will be reported back to you. At which point, you don't know the state of the socket, or how many bytes it may have sent before timing out, so the only sane thing you can do is close that socket and let that client reconnect to your server. That may or may not be a good design choice. For example, if Client1 and Client2 want to send a message to Client3 at the same time, then you would need to synchronize those messages. But, if Client1 wants to send a message to Client2 while Client3 wants to send a message to Client4 at the same time, then there is no need to synchronize those messages. So, unless the requests are accessing UI resources, there is not a good reason for the main UI thread to be processing the client requests. TIdTCPServer already disconnects active client sockets during its shutdown. But, if you must do it manually, then using Connection.Binding.CloseSocket() would be safer than Connection.Disconnect() especially in a multi-threaded environment. That said, make sure your server's event handlers are exiting properly during server shutdown. This is another reason NOT to use the main UI thread to process requests. If the main thread is blocked shutting down the server then it can't handle subsequent synchronization requests, thus causing a deadlock (client threads are blocked waiting on the main thread, and the main thread is blocked waiting on the client threads). So, do not perform synchronizations with the main thread during server shutdown, or else perform the shutdown in a separate thread leaving the main thread open to handle synchronizations until the server is fully shutdown. -
Or, you could just have the main EXE do that same work in its startup code to send the message to an existing instance of itself. This has the added benefit that if there is no existing instance running, the new instance can continue to load itself normally, and then take the requested action when ready.
-
tidtcpserver read (peek) without removing from buffer.
Remy Lebeau replied to alnickels's topic in Network, Cloud and Web
Indy has some wrappers for setsockopt() and recv(), so your code can be cleaned up a little bit. Try something more like this (based on https://stackoverflow.com/a/78873272/65863): uses ..., IdStack, IdStackBSDBase, IdSSL; type TIdStackBSDBaseAccess = class(TIdStackBSDBase) end; function SslTlsHandshakeDetected(ASocket: TIdStackSocketHandle): Boolean; var Buffer: array[0..5] of Byte; NumBytes, Len: Integer; MsgType: Byte; begin Result := False; NumBytes := GStack.CheckForSocketError( TIdStackBSDBaseAccess(GBSDStack).WSRecv(ASocket, Buffer, SizeOf(Buffer), MSG_PEEK) ); if NumBytes < 3 then Exit; // SSL v3 or TLS v1.x ? if (Buffer[0] = $16) and // type (22 = handshake) (Buffer[1] = $3) then // protocol major version (3.0 = SSL v3, 3.x = TLS v1.x) begin Result := True; Exit; end; // SSL v2 ? if (Buffer[0] and $80) <> 0 then begin // no padding, 2-byte header Len := (Integer(buffer[0] and $7F) shl 8) + buffer[1]; MsgType := buffer[2]; end else begin // padding, 3-byte header Len := (Integer(buffer[0] and $3F) shl 8) + buffer[1]; MsgType := Buffer[3]; end; Result := (Len > 9) and (MsgType = $1); // msg type (1 = client hello) end; procedure SetRecvTimeout(AContext: TIdContext; const ASecs: Integer); {$IFDEF POSIX} type TTimeVal = record tv_sec, tv_usec: LongInt; end; var LTime: TTimeVal; {$ENDIF} begin {$IFDEF POSIX} LTime.tv_sec := ASecs; LTime.tv_usec := 0; GBSDStack.SetSocketOption(AContext.Binding.Handle, Id_SOL_SOCKET, Id_SO_RCVTIMEO, LTime, SizeOf(LTime)); {$ELSE} AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_RCVTIMEO, ASecs * 1000); {$ENDIF} end; ... begin SetRecvTimeout(AContext, 1); TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := not SslTlsHandshakeDetected(AContext.Binding.Handle); SetRecvTimeout(AContext, 0); end; -
win32 exe compiled in Delphi11 does not run on W2000
Remy Lebeau replied to rudy999's topic in Windows API
One of the new features in Delphi 13 is support for UI Automation in the VCL. The UIA framework was introduced in Windows 7. Embarcadero dropped suppprt for XP awhile ago, and Win2K is even older than XP. -
Well, then the hint is valid since the value -1 will never be used.
-
Did they update Abort() to mark it with the new 'noreturn' attribute?
-
Have you tried changing the Size property of the VCL's global TScreen.MenuFont?
-
This is explained in the DocWiki documentation: https://docwiki.embarcadero.com/RADStudio/Florence/en/What's_New#Unified_internal_version_numbers_to_37
-
New Delphi features in Delphi 13
Remy Lebeau replied to David Heffernan's topic in RTL and Delphi Object Pascal
Yes. Major version releases can coexist with each other. It's minor version releases that can't. -
Install, activate and use Delphi on an offline computer (windows)
Remy Lebeau replied to PatAura's topic in General Help
The Community edition does not support offline installation or offline registration.. You need a Paid edition for that. -
udp discovery Delphi Super Fast LAN tethering in IPv4 network using Indy UDP AutoDiscovery IP
Remy Lebeau replied to bravesofts's topic in I made this
Forcing use of client/server components on specific ports does not provide security. Nor are your "secret" ports actually very secret, they are easily discoverable. Also, the server has no way to know if the client is even listening on the "secret" port before replying, so there's no security in favoring one port over another. If you want true security, use a proper security protocol, like DTLS. Also, using an asynchronous server component to receive 1 response is not making the client faster and simpler, it's actually making the client slower and complex (multi-thread handling, breaking up code flow, etc), and it goes against one of Indy's core designs - linear writes and reads in command/response models. But whatever. It's your business design, do what makes sense to you. What do I know? I'm just the guy who maintains the components you are basing your work on. Good luck. -
udp discovery Delphi Super Fast LAN tethering in IPv4 network using Indy UDP AutoDiscovery IP
Remy Lebeau replied to bravesofts's topic in I made this
I have some issues with your implementation: IPv6 networks are not supported. The client request is needlessly wordy. The client broadcasts to 255.255.255.255 instead of to the subnet's actual broadcast IP. Some routers block traffic to 255.255.255.255 by default. In case the client is run on a machine with multiple networks installed (VPN, etc), there is no option to let the user decide which network to broadcast to. The client is using separate UDP sockets to send the request and read replies. You are forcing the client to receive on a specific listening port. The server is not sending its reply back to the real port that actually sent the request. Most routers will block this traffic if port forwarding is used. On the server side, always reply to the sending port. On the client side, either 1) let TIdUDPClient read replies on its sending port, or 2) let TIdUDPServer send the request on its listening binding. Use one or the other, not both. Doesn't use Indy's TIdStack.GetLocalAddressList() method to discover local networks (at least on Windows, as it may have issues on Android). Since your goal is to allow a TCP client to discover a server, I would opt to get rid of the TIdUDPServer on the client side, and just use TIdUDPClient by itself. Send a request, read a response, done. You don't need to make that process asynchronous with TIdUDPServer in most use cases. Using TIdUDPServer on the client side makes more sense if you want to discover multiple servers and then let the user choose between them, or to leave it open so servers can just broadcast their presence over time. -
Global in RTL for unit communication?
Remy Lebeau replied to david berneda's topic in RTL and Delphi Object Pascal
Why? You said you are creating IDE design-time packages. So they run in the IDE process, and the IDE is not itself a cross-platform application, it is a Windows-only VCL application. What am I missing? Can you provide more exact details of what you are actually trying to accomplish? -
Global in RTL for unit communication?
Remy Lebeau replied to david berneda's topic in RTL and Delphi Object Pascal
System.Classes.RegisterClass()? It is not clear what you are actually looking for. Please clarify. Can you provide an example of what you want to accomplish? Maybe using custom window messages, or System.Messaging.TMessageManager ? Are they separate packages in the same process, or are they in separate processes? It makes a big difference depending on whether you have to cross the process boundary or not. There is nothing like that framework in the common RTL. And it wouldn't make sense anyway if your two units can't share a common unit that defines what they need to share. -
If I'm reading your codes correctly, you are both assuming that ReturnAddress() itself doesn't have a stack frame, and it is returning the address stored in its caller's stack frame. Which means it won't work correctly if its caller has no stack frame, or if it is called from inside of an inline function.
-
ICS 8.71 UDP ServerScoket inside a Thread
Remy Lebeau replied to LennyKrost's topic in ICS - Internet Component Suite
Never heard of CleanTalk. Are you putting your code snippets in code blocks (the `</>` button on the editor toolbar)? I'm guessing you did not read the ICS documentation. It says: https://wiki.overbyte.eu/wiki/index.php/TWSocketServer On a side note: `while PeekMessage() = False do WaitMessage();` is the exact same as calling `GetMessage()` without a loop. Makes sense, if you are creating a TCP server socket expecting it to receive UDP traffic. You need a UDP server socket. -
ICS 8.71 UDP ServerScoket inside a Thread
Remy Lebeau replied to LennyKrost's topic in ICS - Internet Component Suite
Can you provide the actual code you are having trouble with? Did you set the component's MultiThreaded property to true? Does your thread have a message loop? -
debugging an executable started by the one being debugged
Remy Lebeau replied to dummzeuch's topic in Delphi IDE and APIs
"The only way I currently see would be to attach to that program after it has been executed. But that's rather cumbersome." -
Start Menu items missing for RAD Studio 12.3
Remy Lebeau replied to Remy Lebeau's topic in Delphi IDE and APIs
The only useful tidbits I get from that page are that: the Start Menu doesn't display two shortcuts pointing to the same target the Start Menu tries to block showing Uninstallers. Good to know. Seems to be on track with what I'm seeing. -
I just installed RAD Studio 12.3 on Windows 10. On my Start Menu, underneath "Embarcadero RAD Studio 12", I see the following shortcuts: C++Builder 12 C++Builder 12 (DPI Unaware) Delphi 12 Delphi 12 (DPI Unaware) Migration Tool RAD Studio 12 (64-bit Initial Release) RAD Studio 12 (64-bit Initial Release) (DPI Unaware) RAD Studio 12 RAD Studio 12 (DPI Unaware) RAD Studio Command Prompt RAD Studio Command Prompt (x64) Uninstall Note the two highlighted items are missing. However, in the "%ProgramData%\Microsoft\Windows\Start Menu\Programs\Embarcadero RAD Studio 12" folder, I see the following shortcuts: C++Builder 12 C++Builder 12 (DPI Unaware) Delphi 12 Delphi 12 (DPI Unaware) Migration Tool RAD Studio 12 (64-bit Initial Release) RAD Studio 12 (64-bit Initial Release) (DPI Unaware) RAD Studio 12 RAD Studio 12 (DPI Unaware) RAD Studio Command Prompt RAD Studio Command Prompt (x64) Uninstall So, why are these two items not appearing on my Start Menu? Anyone else ever see this happen?
-
Start Menu items missing for RAD Studio 12.3
Remy Lebeau replied to Remy Lebeau's topic in Delphi IDE and APIs
It turns out that I had a separate "RAD Studio 12" shortcut in the "%APPDATA%\Microsoft\Windows\Start Menu\Programs" folder from an earlier installation. So the Start Menu was displaying "RAD Studio 12" underneath "R" instead of underneath "Embarcadero RAD Studio 12". When I deleted the rogue shortcut, "RAD Studio 12" now appears under "Embarcadero RAD Studio 12" as expected. But, the "Uninstall" shortcut is still missing. And I have several other programs with "Uninstall" shortcuts that are also missing. Apparently, the Start Menu doesn't like displaying multiple shortcuts with duplicate names, even though they are in different folders. Nice one, Microsoft. -
Or TThread.ForceQueue(), eg: procedure TMyForm.Edit1Exit(Sender: TObject); begin TThread.ForceQueue(nil, procedure begin ShowDialog('It works!'); end ); end;
-
v13 has not been released yet, and no information about its pricing is available at this time.