TServerSocket: Confusion with Socket Objects - delphi

So I'm having this application that verifies weather a user can log-in or not.
It consists of multiple Clients (up to 200) and a server that processes the login querys (containing Username, PW and IP). The Server checks weather the user exists and sends an answer back.
TLoginQuery is a Record.
procedure TLogin_Form.btnLoginClick(Sender: TObject);
var LoginQuery1: TLoginQuery;
begin
if not LoginSocket.Active then
begin
LoginSocket.Open;
end;
//Paketchen schnüren.
LoginQuery1.Name := ledtName.Text;
LoginQuery1.Passwort := ledtPasswort.Text;
LoginQuery1.IP := LoginSocket.Socket.LocalAddress;
//LoginQuery ín den Socket legen.
LoginSocket.Socket.SendBuf(LoginQuery1, SizeOf(LoginQuery1));
end;
The Server currently reads:
procedure TServer_Form.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var LoginQuery: TLoginQuery;
uservalid: boolean;
begin
uservalid := false;
Socket.ReceiveBuf(LoginQuery, SizeOf(LoginQuery));
if CheckIfUserValid(LoginQuery) then
begin
uservalid := true;
ServerSocket.Socket.SendBuf(uservalid, SizeOf(uservalid));
end;
end;
The question now is:
Does the server (as it should generally be) create a different socket connection per client?
My assumption:
ClientA sends his login data and recieves the uservalid boolean (code above) from the server. As the uservalid boolean is written into the socket connection the following happens: Just before ClientA can get the uservalid boolean (as it should be) ClientB, who is already logged in, reads from the socket and gets (as it should NOT be) the uservalid boolean.
This could be intervented using one socket per client. Right?

TServerSocket.OnClientRead's Socket: TCustomWinSocket parameter represents just that one connection between one of your clients and the server. Thus, when client Foo sends the login record, and TServer_Form.ServerSocketClientRead is called, just say
if CheckIfUserValid(LoginQuery) then
begin
uservalid := true;
Socket.SendBuf(uservalid, SizeOf(uservalid));
end;
and you'll send the data to the right client.

Typically, your server socket is not going to be broadcasting its outgoing messages to all connected clients. Instead, it will be choosing one specific connected client to send the response. Think of all those connections from different clients as unique. Sure, they may have some common settings, but they are unique connections. (In database-speak, the primary key for a connection is a combination of all of the server IP, server port, client IP, client port)
I've not used TServerSocket, but the IPWorks library makes this explicit by using a connection ID that is specified both on the receiving side and the sending side. This way you know that the data you are reading/writing will be using a specific connection and the data is from/to the expected client.

Related

Indy IdFTP fails on Active connection

I'm trying to use Indy's IdFTP to send and receive some files via FTP.
function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
var
TimeoutFTP: integer;
begin
Result := false;
with TIdFTP.Create(Fm) do
try
try
TimeoutFTP := 2000;
Host := Servidor;
Port := 21;
UserName := Usuario;
PassWord := Contra;
Passive := Pasivo;
Connect(true, TimeoutFTP);
if not Connected then
begin
Error := true;
end
else
begin
TransferType := ftASCII;
if Binario then
TransferType := ftBinary;
OnWorkEnd := FinDeTransmision;
if Descargar then
Get(Remoto , Local, True)
else
Put(InterpretarRutaEspecial(Local), Remoto, True);
if Descargar and Borrar then
Delete(Remoto);
Disconnect;
Result := true;
Fm.Hide;
end;
Except on E: Exception do
Mensaje := E.Message;
end;
finally
Free;
end;
if not Result then
ErrorTransmision;
end;
Whenever I try to do a PUT/GET on Active mode I get the following error: EIdProtocolReplyError: 'Failed to establish connection". It works fine on Passive mode.
The thing is that I want to use Indy (used elsewhere in the project) but the previous version of the code, using OverbyteIcsFtpCli works fine both in Active and Passive mode.
This is the code using OverbyteIcsFtpCli:
function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
begin
with TFtpClient.Create(Fm) do
try
HostName := Servidor;
Port := '21';
UserName := Usuario;
PassWord := Contra;
HostDirName := '';
HostFileName := Origen;
LocalFileName := InterpretarRutaEspecial(Destino);
Binary := Binario;
Passive := Pasivo;
OnRequestDone := FinDeTransmision;
if Descargar then
Result := Receive
else
Result := Transmit;
OnRequestDone := nil;
if Descargar and Borrar then
Delete;
Result := Result and not Error;
Fm.Hide;
if not Result then
ErrorTransmision;
finally
Free;
end;
end;
So I took a look under the hood using wireshark and I found that Indy's FTP is not answering some messages from the server.
This is the file-transmission handshake with OverBytes' FTP:
I've highlighted in yellow the two packets sent between server and client that start the data transmission.
Now let's see what happens with Indy's FTP:
The server is sending the packet to start the file transmission but IdFTP is not answering.
I've seen this question but this two tests where ran in the same computer, same network connection, same firewall, etc. Also this one, but I want the FTP to work both in active and passive modes.
What's happening?
In an Active mode transfer, an FTP server creates an outgoing TCP connection to the receiver.
Your Wireshark captures clearly show that the FTP server in question is creating that transfer connection BEFORE sending a response to the RETR command to let your client know that the connection is proceeding. TFtpClient is accepting that connection before receiving the RETR response. But TIdFTP waits for the RETR response before it will then accept the transfer connection (this also applies to TIdFTP's handling of STOR/STOU/APPE commands, too).
LPortSv.BeginListen; // <-- opens a listening port for transfer
...
SendPort(LPortSv.Binding); // <-- sends the PORT command
...
SendCmd(ACommand, [125, 150, 154]); // <-- sends the RETR command and waits for a response!
...
LPortSv.Listen(ListenTimeout); // <-- accepts the transfer connection
...
Re-reading RFC 959, it says the following:
The passive data transfer process (this may be a user-DTP or a second server-DTP) shall "listen" on the data port prior to sending a transfer request command. The FTP request command determines the direction of the data transfer. The server, upon receiving the transfer request, will initiate the data connection to the port. When the connection is established, the data transfer begins between DTP's, and the server-PI sends a confirming reply to the user-PI.
ICS is asynchronous, so this situation is not a big deal for it to handle. But Indy uses blocking sockets, so TIdFTP will need to be updated to account for this situation, likely by monitoring both command and transfer ports simultaneously so it can act accordingly regardless of the order in which the transfer connection and the command response arrive in.
I have opened a ticket in Indy's issue tracker for this:
#300: TIdFTP fails on Active mode transfer connection with vsFTPd
UPDATE: the fix has been merged into the main code now.

SendMail with TIdSMTP Connection Time Out

I am not able to send emails using TIdSMTP I am getting the following message: Socket Error # 10060 / Connection timed out.
I am using version Delphi XE6
Here is my code:
procedure TForm1.Button1Click(Sender: TObject);
var
IdSMTP : TIdSMTP;
IdMessage : TIdMessage;
begin
IdSMTP := TIdSMTP.Create (Nil);
IdMessage := TIdMessage.Create (Nil);
IdSMTP.Host := 'mail.mysmtp.com';
IdSMTP.Port := 25;
IdSMTP.Connect ();
IdMessage.From.Address := 'test#mysmtp.com';
IdMessage.From.Name := 'Contato';
IdMessage.Recipients.EMailAddresses := 'test#hotmail.com';
IdMessage.Subject := 'Contato test';
IdMessage.Body.Text := 'test';
IdSMTP.Send (IdMessage);
IdSMTP.Disconnect ();
FreeAndNil (IdMessage);
FreeAndNil (IdSMTP);
end;
From Google: A socket error in the 10060 range is a Winsock error. It is generally caused by either outgoing connection problems or connection problems on the host end.
I don't know if you sanitized this code to post it or not, but I'd say the culprit is either the hostname or the username on the from address.
Winsock will attempt to create a connection to the hostname. If it fails to get the expected ACK, it'll generate a timeout error. I've also seen this happen when the domain name isn't resolved by DNS.
Also, what was mentioned earlier regarding authentication ... the lack of response from the SMTP host could be due to improper authentication. It all depends on how the host's SMTP service was configured, so it could just ignore unauthorized requests.
You need to see if you must pass in a username/pwd with the SMTP request, or read the mailbox first (read before write, so to speak). I cannot imagine anybody configuring an SMTP server without requiring some kind of authentication, because otherwise you've got what amounts to an "open relay" where any process can send out unlimited traffic through it.
Also, the from address might be required to be valid. That is, 'test#mysmtp.com' would require a user/mailbox for 'test' to exist, as opposed to '*#mysmtp.com' that would work with ANY user/mailbox name.
All of these could result in a timeout because the SMTP host could be configured to simply ignore improper and unauthenticated requests.

Start Communication from server first in delphi by Indy 10

In Socket applications programmed by TCPServer/Client components, usually we active server side, then connect client to server, and when we need to get or send data from one side to other, first we send a command from client to server and a communication will starts.
But the problem is that always we need to start conversation from client side!
I want to ask is any idea for start conversation randomly from server side without client side request?
I need this functionality for notify client(s) from server side. for example, when a registered user (client-side) connected to server, other connected users (on other client-sides), a notification must send from server to all users (like Yahoo Messenger).
I'm using TIdCmdTCPServer and TIdTCPClient components
You are using TIdCmdTCPServer. By definition, it sends responses to client-issued commands. For what you are asking, you should use TIdTCPServer instead, then you can do whatever you want in the TIdTCPServer.OnExecute event.
What you ask for is doable, but its implementation depends on your particular needs for your protocol.
If you just want to send unsolicited server-to-client messages, and never responses to client-to-server commands, then the implementation is fairly straight forward. Use TIdContext.Connection.IOHandler when needed. You can loop through existing clients in the TIdTCPServer.Contexts list, such as inside the TIdTCPServer.OnConnect and TIdTCPServer.OnDisconnect events. On the client side, you need a timer or thread to check for server messages periodically. Look at TIdCmdTCPClient and TIdTelnet for examples of that.
But if you need to mix both client-to-server commands and unsolicited server-to-client messages on the same connection, you have to design your protocol to work asynchronously, which makes the implementation more complex. Unsolicited server messages can appear at anytime, even before the response to a client command. Commands need to include a value that is echoed in the response so clients can match up responses, and the packets need to be able to differentiate between a response and an unsolicited message. You also have to give each client its own outbound queue on the server side. You can use the TIdContext.Data property for that. You can then add server messages to the queue when needed, and have the OnExecute event send the queue periodically when it is not doing anything else. You still need a timer/thread on the client side, and it needs to handle both responses to client commands and unsolicited server messages, so you can't use TIdConnection.SendCmd() or related methods, as it won't know what it will end up reading.
I have posted examples of both approaches in the Embarcadero and Indy forums many times before.
Clients initiate communication. That is the definition of a client–the actor that initiates the communication. Once the connection is established though, both sides can send data. So, the clients connect to the server. The server maintains a list of all connected clients. When the server wants to send out communications it just sends the data to all connected clients.
Since clients initiate communication, it follows that, in the event of broken communication, it is the client's job to re-establish connection.
If you want to see working code examples where server sends data, check out Indy IdTelnet: the telnet client uses a thread to listen to server messages. There is only one socket, created by the client, but the server uses the same socket for its messages to the client, at any time.
The client starts the connection, but does not have to start a conversation by saying 'HELLO' or something like that.
Technically, the client only needs to open the socket connection, without sending any additional data. The client can remain quiet as long as he wants, even until the end of the connection.
The server has a socket connection to the client as soon as the client has connected. And over this socket, the server can send data to the client.
Of course, the client has to read from the connection socket to see the server data. This can be done in a loop in a background thread, or even in the main thread (not in a VCL application of course as it would block).
Finally, this is the code that I used to solve my problem:
// Thread at client-side
procedure FNotifRecieverThread.Execute;
var
str: string;
MID: Integer;
TCP1: TIdTCPClient;
begin
if frmRecieverMain.IdTCPClient1.Connected then
begin
TCP1 := TIdTCPClient.Create(nil);
TCP1.Host := frmRecieverMain.IdTCPClient1.Host;
TCP1.Port := frmRecieverMain.IdTCPClient1.Port;
TCP1.ConnectTimeout := 20000;
while True do
begin
try
TCP1.Connect;
while True do
begin
try
str := '';
TCP1.SendCmd('checkmynotif');
TCP1.Socket.WriteLn(IntToStr(frmRecieverMain.UserID));
str := TCP1.Socket.ReadLn;
if Pos('showmessage_', str) = 1 then
begin
MID := StrToInt(Copy(str, Pos('_', str) + 1, 5));
frmRecieverMain.NotifyMessage(MID);
end
else
if str = 'updateusers' then
begin
LoadUsers;
frmRecieverMain.sgMsgInbox.Invalidate;
frmRecieverMain.sgMsgSent.Invalidate;
frmRecieverMain.cbReceipent.Invalidate;
end
else
if str = 'updatemessages' then
begin
LoadMessages;
frmRecieverMain.DisplayMessages;
end;
except
// be quite and try next time :D
end;
Sleep(2000);
end;
finally
TCP1.Disconnect;
TCP1.Free;
end;
Sleep(5000);
end;
end;
end;
// And command handlers at server-side
procedure TfrmServer.cmhCheckMyNotifCommand(ASender: TIdCommand);
var
UserID, i: Integer;
str: string;
begin
str := 'notifnotfound';
UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);
for i := 0 to NotificationStack.Count - 1 do
if NotificationStack.Notifs[i].Active and
(NotificationStack.Notifs[i].UserID = UserID)
then
begin
NotificationStack.Notifs[i].Active := False;
str := NotificationStack.Notifs[i].NotiffText;
Break;
end;
ASender.Context.Connection.Socket.WriteLn(str);
end;
// And when i want to some client notificated from server, I use some methodes like this:
procedure TfrmServer.cmhSetUserOnlineCommand(ASender: TIdCommand);
var
UserID, i: Integer;
begin
UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);
if UserID <> -1 then
begin
for i := 0 to OnLineUsersCount - 1 do // search for duplication...
if OnLineUsers[i].Active and (OnLineUsers[i].UserID = UserID) then
Exit; // duplication rejected!
Inc(OnLineUsersCount);
SetLength(OnLineUsers, OnLineUsersCount);
OnLineUsers[OnLineUsersCount - 1].UserID := UserID;
OnLineUsers[OnLineUsersCount - 1].Context := ASender.Context;
OnLineUsers[OnLineUsersCount - 1].Active := True;
for i := 0 to OnLineUsersCount - 1 do // notify all other users for refresh users list
if OnLineUsers[i].Active and (OnLineUsers[i].UserID <> UserID) then
begin
Inc(NotificationStack.Count);
SetLength(NotificationStack.Notifs, NotificationStack.Count);
NotificationStack.Notifs[NotificationStack.Count - 1].UserID := OnLineUsers[i].UserID;
NotificationStack.Notifs[NotificationStack.Count - 1].NotiffText := 'updateusers';
NotificationStack.Notifs[NotificationStack.Count - 1].Active := True;
end;
end;
end;

How to store an identifier in a ListView a client connection - Indy 10

I am creating a TCP server which receives multiple clients and must be able to send messages to each.
How do I get a handle to the client connection and then send arbitrary data?
Thanks: D
Code:
procedure TFRM_Main.ServerConnect(AContext: TIdContext);
var lAdd: TListItem;
var Index: integer;
begin
lAdd := ListView.Items.Add;
//AContext connection ID, what to do here?
lAdd.Caption := IntToStr(Index);
end;
TIdTCPServer is a multithreaded component. Accessing the UI directly from within its OnConnect event (or OnDisconnect, OnExecute, or OnException) is not thread-safe! You need to use Indy's TIdSync or TIdNotify class to access the UI safely.
To answer the original question, the simpliest, but not necessarily the safest, way is to store the TIdContext.Connection object pointer in the TListItem.Data property. The main thread code will then have direct access to the connection when it needs it.
I do not advise that, though. A safer solution is to uniquely identify each client yourself, such as with a username that the client sends, and store that ID in the TIdContext.Data and TListItem.Data properties. Then, when your main thread code wants to send a message to a client, it can loop through the TIdTCPServer.Contexts list looking for the desired ID, and if found then it will have access to the corresponding TIdContext.Connection object.
use:
AContext.Connection.IOHandler.Write( (* bytes *) );
AContext.Connection.IOHandler.WriteFile( (* send a file to the client *) );
For more options, just invoke the code completion(CTRL+SPACE) after IOHandler and see available options, some time ago, I've wrote a simple client/server test app, click here to see and/or download source.

Indy SSL to plain socket pump

I have to provide an SSL front for a plain TCP server, so I'm making a "pump" application that will provide an SSL connection to the outside while the original server can stay plain.
I'm trying to use the Indy components to support my SSL needs, but I can't seem to get any data from the SSL port. I assigned the TIdTCPServer.OnExecute to the following event handler:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
c:TIdTCPClient;
cs,ss:TIdIOHandler;
b:TBytes;
begin
c:=TIdTCPClient.Create(Self);
try
c.Host:='127.0.0.1';
c.Port:=60675;
c.ConnectTimeout:=500;
c.Connect;
ss:=c.IOHandler;
cs:=AContext.Connection.IOHandler;
while (cs.Connected) and (ss.Connected) do
begin
if cs.CheckForDataOnSource(1) then
begin
try
cs.ReadBytes(b,1,False);
except on e:Exception do
Memo1.Lines.Add(e.Message); //BAD out of Thread context
end;
if Length(b)>0 then
ss.Write(b);
end;
if ss.CheckForDataOnSource(1) then
begin
ss.ReadBytes(b,1,False);
if Length(b)>0 then
cs.Write(b);
end;
end;
finally
c.Free;
end;
end;
The TCP server has an SSL handler attached. I did the same on a plain HTTP server and it worked fine, so I'm assuming my SSL setup is not the issue.
cs=Client Side (the server socket) and ss=Server side (the client for the TCP server I'm trying to add SSL to).
Now, I know it needs cleanup and doing 1ms waits isn't pretty, but before I can attack that issue, I'd like to receive some data.
Neither of my ReadBytes get called. When I used cs.Readable(), I get true just once, but I still couldn't read.
What can I do to make a pump? Why am I not getting data?
Try using the TIdMappedPortTCP component instead of TIdTCPServer directly. TIdMappedPortTCP handles all the work of passing data back and forth between a client and another server. By default, the outbound connection to the second server is not encrypted, even if the inbound connection to TIdMappedPortTCP is encrypted.

Resources