Im trying to code a basic irc client in Delphi 2010 using Indy components.
Im able to connect to my irc server (unrealircd) using sample A below.
After connecting I have many procedures that should perform actions when it receives a private message, ping, ctcp, channel modes etc. but they never react to any of these incoming events.
Sample A:
This connects to the IRC server when button4 is pressed.
It sucessfully joins the channel with the name specified.
procedure TForm1.Button4Click(Sender: TObject);
begin
IdIRC1.Host := '72.20.53.142';
IdIRC1.Port := 6667;
IdIRC1.Nickname := ssh.frmmain.Edit1.text;//insert your nickname here
try
idIRC1.Connect;
except
if not idIRC1.Connected then
begin
Memo2.Lines.add('Error Connecting to ' + idIRC1.Host);
Exit;
end;
end;
Memo2.Lines.add ('Connected to Auth Server');
idIRC1.Join(channel,key);
Memo2.Lines.add ('Auth Accepted');
end;
These events dont work at all and no errors are generated during a compile.
procedure TForm1.IdIRC1Connected(Sender: TObject);
begin
memo2.Lines.Clear;
memo2.Lines.add ('2Connected');
end;
procedure TForm1.IdIRC1ServerVersion(ASender: TIdContext; Version, Host, Comments: String);
begin
memo2.Lines.Add(Version +'Host '+Host+'Comments '+Comments);
end;
Ive had a few people look at this, and it just seems theres some unicode issues that destroyed my TClientSocket irc setup, and even when I moved to indy and used samples off the official site I was still unable to get anything to fire such as the onconnect event.
A friend had a copy of an application he wrote in Delphi 2010 using the same version of indy and I managed to import his project and it started working.
Not sure why
Related
I use Delphi 10.1 Berlin and Indy 10.6.2, 32bit on a Windows Server 2012.
I implemented a server based on TIdHTTPServer. Works like a charm for many years.
Now my customer wants the traffic secured. SSL is an area I have little knowledge of.
There are several helpful pointers on the web that have helped me make CA certificate and key files with OpenSSL. And from several examples I've put together the code below, using TIdServerIOHandlerSSLOpenSSL.
The cert/key files are in the exe directory and so are the OpenSSL dlls ssleay32 and libeay32.
The server responds to http://localhost:8080 but there is no response when addressing it via https://localhost. It behaves as if the TIdServerIOHandlerSSLOpenSSL is not there at all. (The IOHandler does read the cert/key files and it complains when I remove the OpenSSL DLLs). It is as if I've forgotten to throw a switch somewhere.
The analysis of Windows Network Diagnostics (in IEdge) is 'The device or resource (localhost) is not set up to accept connections on port "https".'
I tried to log a message via the OnConnect event, but that stage is never reached with HTTPS.
I have run out of ideas, and can not find relevant suggestions on the web.
Here is my code (the components are all declared in code):
procedure TServerForm.FormCreate(Sender: TObject);
var
ServerSSLIOHandler: TIdServerIOHandlerSSLOpenSSL;
rootdir : string;
begin
inherited;
rootdir:=ExtractFilePath(Application.ExeName);
ServerSSLIOHandler:=TIdServerIOHandlerSSLOpenSSL.Create(self);
ServerSSLIOhandler.SSLOptions.RootCertFile:=rootdir+'ca.cert.pem';
ServerSSLIOhandler.SSLOptions.CertFile:=rootdir+'localhost.cert.pem';
ServerSSLIOhandler.SSLOptions.KeyFile:=rootdir+'localhost.key.pem';
ServerSSLIOhandler.SSLOptions.Method:=sslvSSLv23;
ServerSSLIOhandler.SSLOptions.Mode:=sslmServer;
ServerSSLIOhandler.OnGetPassword:=NIL;
ServerSSLIOhandler.OnVerifyPeer:=OnVerifyPeer;
HTTPServer:=TIdHTTPServer.Create(self);
HTTPServer.IOhandler:=ServerSSLIOHandler;
HTTPserver.Bindings.Add.Port:=443;
HTTPserver.Bindings.Add.Port:=8080;
HTTPServer.Active:=True;
HTTPServer.AutoStartSession:=True;
HTTPServer.SessionTimeOut:=1200000;
HTTPserver.OnQuerySSLPort:=OnQuerySSLPort;
HTTPServer.OnCommandGet:=HTTPServerCommandGet;
...
end;
procedure TServerForm.OnQuerySSLPort(APort: Word; var VUseSSL: Boolean);
// This will not be called when the request is a HTTPS request
// It facilitates the use of the server for testing via HTTP://localhost:8080 (i.e. without SSL)
begin
VUseSSL := (APort<>8080);
end;
function TServerForm.OnVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
result:=AOk;
end;
Thank you. Remy's remark about OnConnect and his suggestion to use netstat did the trick. I.e. it lead me to discover that the problem was elsewhere. In the past I had to move away from port 80 because it became in use by a Windows service. From then on I specified a port number (8080) in an ini file and acted as follows.
Code:
prt:=parameters.ReadInteger('settings','port',80);
if prt<>HTTPserver.DefaultPort
then begin HTTPserver.Active:=false;
HTTPserver.Bindings.Clear;
HTTPserver.DefaultPort:=prt;
HTTPserver.Active:=true;
end;
Since this piece of code was still there, obviously only the specified port (8080) was active. netstat revealed that immediately.
Will you believe that I am very happy with your quick response!
I created a stand-alone Datasnap TCP/IP server using the Wizard. I selected sample methods (echostring and reversestring). I saved the server and ran it. Then I created a client application, and using the file-new-other, added a ClientModule to that client project, along with the ClientClasses unit. On the main form. I added a button. On the button's onclick event handler, I added the following code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
The purpose here is to simulate a situation where the client is logging into and logging out of the server regularly rather than keeping a connection. This is especially important on apps deployed to mobile.
You can see I commented out the Connection.Open, because the first call to the ServerMethods1client opens the connection. The generated code is shown here:
function TClientModule1.GetServerMethods1Client: TServerMethods1Client;
begin
if FServerMethods1Client = nil then
begin
SQLConnection1.Open;
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, FInstanceOwner);
end;
Result := FServerMethods1Client;
end;
Now the problem arises. On first click to the button, the connection is opened, and the method is called. On the second click to the button, the connection is closed.
On the 3rd click, an exception is raised "Operation Failed. Connection was Closed" is raised from with the TDBXCommand code.
As a workaround, I tried this:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
ClientModule1.ServerMethods1Client := nil;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
This does sort-of solve the problem, since the ClientModule1's FServerMethods1Client instance is reset so the create code runs again like it did on the first run.
The only other problem now, is (I am using Eurekalog) it creates a memory leak.
What am I doing wrong? What's the right way to connected/disconnect from a Datasnap server repeatedly without restarting the app?
The reason for the first error is that the code that binds the client side proxy (which allows server methods to be called) is tied to the local SQL connection. Note the call to create the proxy class:
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, ...)
The underlying DBExpress connection is passed by reference, and the proxy class uses that connection to call the server. You closed and re-opened the connection, but the underlying DBExpress connection that ServerMethodsClient1 was using has been destroyed. Thus, you receive the "Connection was closed" exception. The connection that ServerMethodsClient1 was using has been closed. You have to recreate ServerMethodsClient1 as you did in your second example.
I can't answer your second question, as I believe it is ARC specific. For a VCL DataSnap app, I would call ServerMethodsClient1.Free rather than setting it to nil. Based on my very, very limited understanding of Delphi's ARC implementation (which is all from the newsgroups), I believe you should call ServerMethodsClient1.DisposeOf, since the class descends from TComponent
But I'm not sure about that. I'm sure someone will jump on here that understands ARC and the proper solution to destroy the object rather than having a memory leak.
In my Android FMX implementation, I only call servermethods to get stuff done. (ie I don't use Datasnap data components). There's too much uncontrolled data transmission overhead to the Datasnap architecture to contemplate anything else realistically on a mobile device... To get around it (and not have memory leaks), I now create local instances of the TServermethods1Client as and when I need them and free them in context:
function TClientModule1.PostTheLog: Boolean;
var
Server: TServerMethods1Client;
begin
Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
try
UserID := Server.GetUserID;
...
finally
Server.Free;
end;
end;
Now the ClientModule1.SQLConnection1 can be connected and disconnected at will (preferably connected just before any call to a servermethod, and disconnected thereafter) and no further issues arise.
Which then begs the question: In which ideal world would the publicly accessible ServerMethods1Client actually be useful?
I'm trying to make a Delphi application to work with AlwaysOn solution. I found on Google that I have to use MultiSubnetFailover=True in the connection string.
Application is compiled in Delphi XE3 and uses TADOConnection.
If I use Provider=SQLOLEDB in the connection string, application starts but it looks like MultiSubnetFailover=True has no effect.
If I use Provider=SQLNCLI11 (I found on Google that OLEDB doesn't support AlwaysOn solution and I have to use SQL Native client) I get invalid attribute when trying to open the connection.
The connection string is:
Provider=SQLOLEDB.1;Password="password here";Persist Security Info=True;User ID=sa;Initial Catalog="DB here";Data Source="SQL Instance here";MultiSubnetFailover=True
Do I have to upgrade to a newer version on Delphi to use this failover solution or is something that I'm missing in the connection string?
I am currently using XE2 with SQL Server AlwaysOn. If you read the documentation you will see that AlwaysOn resilience events will cause your database connection to fail and you need to initiate a new one.
If a SqlClient application is connected to an AlwaysOn database that
fails over, the original connection is broken and the application must
open a new connection to continue work after the failover.
I've dealt with this via the simple expedient of overriding the TAdoQuery component with my own version which retries the connection after getting a connection failure. This may not be the proper way to do this but it certainly works. What it does is override the methods invoked for opening (if the query returns a result set) or executes the SQL (otherwise) and if there is a failure due to connection loss error tries again (but only once). I have heavily tested this against AlwaysOn switch overs and it works reliably for our configuration. It will also react to any other connection loss events and hence deals with some other causes of queries failing. If you are using a component other than TAdoQuery you would need to create similar overrides for that component.
It is possible this can be dealt with in other ways but I stopped looking for alternatives once I found something that worked. You may want to tidy up the uses statement as it clearly includes some stuff that isn't needed. (Just looking at this code makes me want to go away and refactor the code duplication as well)
unit sptADOQuery;
interface
uses
Windows, Messages, SysUtils, Classes, Db, ADODB;
type
TsptADOQuery = class(TADOQuery)
protected
procedure SetActive(Value: Boolean); override;
public
function ExecSQL: Integer; // static override
published
end;
procedure Register;
implementation
uses ComObj;
procedure Register;
begin
RegisterComponents('dbGo', [TsptADOQuery]);
end;
procedure TsptADOQuery.SetActive(Value: Boolean);
begin
try
inherited SetActive(Value);
except
on e: EOleException do
begin
if (EOleException(e).ErrorCode = HRESULT($80004005)) then
begin
if Assigned(Connection) then
begin
Connection.Close;
Connection.Open;
end;
inherited SetActive(Value); // try again
end
else raise;
end
else raise;
end;
end;
function TsptADOQuery.ExecSQL: Integer;
begin
try
Result := inherited ExecSQL;
except
on e: EOleException do
begin
if (EOleException(e).ErrorCode = HRESULT($80004005)) then
begin
if Assigned(Connection) then
begin
Connection.Close;
Connection.Open;
end;
Result := inherited ExecSQL; // try again
end
else raise;
end
else raise;
end;
end;
end.
I set up an OLE Automation Server and an OLE Client in Delphi XE2. My two Methods are the following:
function TMyCom2.Get_Text() : IStrings;
begin
GetOleStrings(unit1.Form1.Memo1.Lines, Result);
end;
and:
procedure TMyCom2.Set_Text(const value: IStrings);
begin
SetOleStrings(unit1.Form1.Memo1.Lines, value);
end;
Now I tried to call both by the client.
The second Method(Set_Text) was working perfectly fine.
But the first one(Get_Text) wich should gather the content of the memo of the server and write it into the memo of the client, caused this exception:
Exception error of the server!
To get the Ole information I wrote this on the client side:
procedure TForm1.Button1Click(Sender: TObject);
var
aStrings : IStrings;
begin
aStrings.Add(Server.Get_Text);
SetOleStrings(Memo1.Lines, aStrings);
end;
I have no idea what could be wrong and I was incredibly grateful if somebody could take a look at this code and tell me what the hell is going wrong;D
PS: I already tested it with Integers and it worked so the Problem has to do something with Strings
I uploaded the two projects on Dropbox so feel free to download them:
Client: here
Server: here
I've been struggling with the crap documentation of Google and can't get the program to join the channel even though it connects to the server fine. (It says Connected to server)
//On Form Make
procedure TForm2.FormCreate(Sender: TObject);
begin
IdIRC1.Connect();
end;
//on connected
procedure TForm2.IdIRC1Connected(Sender: TObject);
begin
ShowMessage('Connected to server');
IdIRC1.Join('#TheChannel', 'password');
end;
Once i close the form an error comes up saying:
Project raised exception class EIdException with message 'Not Connected'
Also once connected what functions do i use to talk on the channel/check input?
And what other IRC connection options (components) are there for Delphi applications?
ANY HELP WOULD BE APPRECIATED, GOOGLE HAS NOTHING ON THIS REALLY.
All i want is to be able to connect/check channel chat messages & talk on the chat; Simple String IO through IRC...
Guess you are not filling all the server requirements. Just IdIrc.Connect isn't enought, this works for me:
FIRC.Host:= 'irc.freenode.org';
FIRC.Port := 6667;
FIRC.Username:= 'SapoIndy';
FIRC.Nickname:= 'SapoIndy';
FIRC.RealName:= 'Fabio Gomes';
FIRC.Connect;
FIRC.Join('#TheChannel');
To figure out what is going on, you need to get the output of some events, I had implemented these:
FIRC.OnStatus:= #Status;
FIRC.OnNotice:= #Notice;
FIRC.OnMOTD:= #MOTD;
Get some events up and you should figure out what the server is telling you, don't go with trial and error.
And about sending and receiving messages, I've implemented some of this some time ago, here is the project, it was made using Lazarus.
https://bitbucket.org/fabioxgn/irc/src/b510d73e553d/main.pas
Do not call Join() in the OnConnected event. That event simply means the underlying socket connection is established. Connect() is still running, and has not actually logged into the IRC server yet when the OnConnected event is triggered. Wait until Connect() exits before issuing any commands:
procedure TForm2.FormCreate(Sender: TObject);
begin
IdIRC1.Connect;
ShowMessage('Connected to server');
IdIRC1.Join('#TheChannel', 'password');
end;