Recent

Author Topic: LazWebsocket How to identify IP adress during connect  (Read 5775 times)

hgbk

  • New member
  • *
  • Posts: 9
LazWebsocket How to identify IP adress during connect
« on: April 20, 2021, 03:47:09 pm »
I have implemented a game as client server solution using LazWebsockets. It may happen that during the game the connection from a client breaks. In order to implement the possibility for this specific client to reconnect I need to identify the IP address during accept and compare it with ACommunication.SocketStream.RemoteAddress.Address of this client  which I have stored before failure. In accept I have TRequestData as parameter which delivers Host (Domain but not IP address). Any idea how to solve this?

Warfley

  • Hero Member
  • *****
  • Posts: 608
Re: LazWebsocket How to identify IP adress during connect
« Reply #1 on: April 21, 2021, 03:02:55 am »
There is currently no way implemented to identify the client during the handshake. This is defenetly something I will consider adding to the library.

But, I should point out that identifying clients based on their IPs is bad style, as due to the use of NAT networks, multiple devices usually share an external IP. While in Europe and the US it is usually something like one per household, for some (cheaper) internet providers or providers in other countries that haven't been so lucky with getting large shares of the IP address space, multiple homes or even larger parts like city blocks can share the same IP address. So there could be potentially hundreds of people with the same IP.

Therefore you should give the client a unique ID to reconnect. Like when a game starts, you could generate a game ID, which every client gets. If a client looses it's connection it can reconnect by sending this game ID to the server and the server can recognize this and put it back into the right game

hgbk

  • New member
  • *
  • Posts: 9
Re: LazWebsocket How to identify IP adress during connect
« Reply #2 on: April 21, 2021, 01:17:49 pm »
Understand - I will implement the "reconnect" according to your proposal. Thanks a lot for the quick and helpful answer!

r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #3 on: September 28, 2021, 08:38:38 pm »
There is currently no way implemented to identify the client during the handshake. This is defenetly something I will consider adding to the library.

But, I should point out that identifying clients based on their IPs is bad style, as due to the use of NAT networks, multiple devices usually share an external IP. While in Europe and the US it is usually something like one per household, for some (cheaper) internet providers or providers in other countries that haven't been so lucky with getting large shares of the IP address space, multiple homes or even larger parts like city blocks can share the same IP address. So there could be potentially hundreds of people with the same IP.

Therefore you should give the client a unique ID to reconnect. Like when a game starts, you could generate a game ID, which every client gets. If a client looses it's connection it can reconnect by sending this game ID to the server and the server can recognize this and put it back into the right game

Hi everyone!
I've been trying to figure out how to use LazWebSocket for some 3 days. I'm analyzing the example  server and client files. I managed to figure out how the client works (at least some basic functions), I created some basic GUI and applied the "client" code to it, I was able to run it iin StartReceiveMessageThread mode. My app is able to sent and receive the message (OnReceiveMessage). But when I run more than 1 instance, messages from the server are sent randomly. I even tried to start my client app from different IPs, local 192.168.x.x and through HamachiLogMeIn 25.x.x.x. Both instances can send messages to the server but the server answers randomly. I understand that it may happen when there are more than 1 client with the same IP but if 2 clients have a different IP address?
How can I make the server answer to a specific client?
Quote
you could generate a game ID, which every client gets
how could I use a client ID to make the server contact the specific client by his ID, not IP?

thanks for any hint.

Warfley

  • Hero Member
  • *****
  • Posts: 608
Re: LazWebsocket How to identify IP adress during connect
« Reply #4 on: September 29, 2021, 02:27:17 pm »
In LazWebsockets the connection is handled by the TWebsocketCommunicator instance given as Parameter to the handler.

If you want to handle multiple connections, you need to identify these. The (threaded) base handler already provides a list of all connections, you could use the index to identify these:
Code: Pascal  [Select][+][-]
  1.   procedure TSocketHandler.IdentifyClients;
  2.   var
  3.     ConnectionList: TConnectionList;
  4.     i: Integer;
  5.   begin
  6.     ConnectionList := Connections.Lock;
  7.     try
  8.       for i := 0 to ConnectionList.Count - 1 do
  9.         ConnectionList[i].WriteStringMessage('You are client number ' + i.ToString);
  10.     finally
  11.       Connections.Unlock;
  12.     end;
  13.   end;

The problem here is, when a client disconnects, it changes the indices of all clients connected after that one (as all move by one). So you could rather than that generate an ID (e.g. by simply having a counter counting up) and use a TDictionary or TFPGMap or similar to manage the connections. You might also need a reverse lookup to map the communicator to it's ID (some extendibility option for the communicator might be also useful here, to store such data right in the communicator).

Another solution is, each handler call gets executed in their own Thread (assuming you use a threaded handler). The best way is to have all the communication happen within that handler and you interact though messages in a message queue. When you receive something you can send a message to the GUI to handle the data. When the GUI wants to send something it sends a message to the handler.

But: no matter what approach you choose, you must always make sure to use sufficient thread locks. All the default  datastructures (arrays, Lists, Maps, Dictionaries, etc.) are not thread safe, so when dealing with the threaded websockets, you should always use locks

gucao

  • New member
  • *
  • Posts: 5
Re: LazWebsocket How to identify IP adress during connect
« Reply #5 on: October 06, 2021, 12:46:38 pm »
The accepted socket is the SocketStream.handle?
can get the ip and port from the accepted  socket
The code need test
Code: Pascal  [Select][+][-]
  1. uses sockets
  2.  
  3. var
  4.   VRtInt,VL:Integer;
  5.   VAddr:TSockAddr;
  6.   VSocketErr: cint;  
  7.   VIP:string;
  8.   VPort:integer;
  9. begin
  10.   VL:=SizeOf(VAddr);  
  11.   VRtInt:=fpgetpeername(ASocket,@VAddr,@VL);
  12.   if VRtInt=0 then begin
  13.      VIp:=NetAddrToStr(VAddr.sin_addr);
  14.      VPort:=NToHs(VAddr.sin_port);
  15.   end else begin
  16.      Result:='faild`````';
  17.      Exit;
  18.   end;  
  19. end;
  20.  
  21.  
  22.  


r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #6 on: October 14, 2021, 10:35:19 am »
Hi there!
I'm sorry for the delay in answering.

Warfley, thanks for the explanation, it seems pretty clear, however I'd have several questions:
1.
Quote
"The (threaded) base handler"
socket.AcceptingMethod:=samThreaded?

2. Does a new client always get the highest number in ConnectionList? If there are 5 connected clients, I suppose they are numbered 0..4 so the new one will get 5, right?
Clients have their user names so basing on ConnectionList, I could have some list linking ConnectionList's client number to the user name but...

3.
Quote
"when a client disconnects, it changes the indices of all clients connected"
Is there any event going on in this situation? how can I know that someone disconnected to update my list?

gucao, I haven't tested it it but quickly analyzing the code, I understand that it's extracting IP and port from the handler?
« Last Edit: October 14, 2021, 10:37:40 am by r.lukasiak »

Warfley

  • Hero Member
  • *****
  • Posts: 608
Re: LazWebsocket How to identify IP adress during connect
« Reply #7 on: October 14, 2021, 02:47:10 pm »
Warfley, thanks for the explanation, it seems pretty clear, however I'd have several questions:
1.
Quote
"The (threaded) base handler"
socket.AcceptingMethod:=samThreaded?
Nope, that makes the accepting handshake threaded (basically before establishing a connection a HTTP request is made, with samThreaded or samThreadPool this will be threaded).

What I meant is when implementing the handler you create a class inheriting from TWebsocketHandler. If you inherit from  TThreadedWebsocketHandler you make use of the threaded handler.
In the ChatServer example this would be this line:
Code: Pascal  [Select][+][-]
  1.   TSocketHandler = class(TThreadedWebsocketHandler) // inherit from TThreadedWebsocketHandler

2. Does a new client always get the highest number in ConnectionList? If there are 5 connected clients, I suppose they are numbered 0..4 so the new one will get 5, right?
Clients have their user names so basing on ConnectionList, I could have some list linking ConnectionList's client number to the user name but...
Yes it is a simple TFPGList used. When a client connects it is added to the list via .Add at the end of the list and if it disconnects it is removed via .Delete.

3.
Quote
"when a client disconnects, it changes the indices of all clients connected"
Is there any event going on in this situation? how can I know that someone disconnected to update my list?
The list is automatically updated. To be notified on a disconnect you can register an event handler. In the ChatServer example this is done in:
Code: Pascal  [Select][+][-]
  1.     ACommunication.OnClose := @ConnectionClosed;

r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #8 on: October 15, 2021, 12:30:26 am »
Quote
TSocketHandler = class(TThreadedWebsocketHandler)
ok, got it.

Quote
The list is automatically updated. To be notified on a disconnect you can register an event handler. In the ChatServer example this is done in:
of course... :| I don't know what I was thinking of when asking this question. I even use that event to do some stuff. What I really wanted to know is how can I figure out which client disconnected, is there any way get its number in ConnectionsList?

And there is another question about OnConnectionClosed. I testing the server using my gui and the HTML example. The server notices when the HTML client disconnects, whether I use the disconnect button, refresh the page or even kill the browser process, the server always triggers the OnConnectionClosed event but it doesn't happen with my GUI, whether I close it or just kill the process, the server doesn't notice it. How could I notify the server I disconnect?

Warfley

  • Hero Member
  • *****
  • Posts: 608
Re: LazWebsocket How to identify IP adress during connect
« Reply #9 on: October 15, 2021, 05:21:07 pm »
of course... :| I don't know what I was thinking of when asking this question. I even use that event to do some stuff. What I really wanted to know is how can I figure out which client disconnected, is there any way get its number in ConnectionsList?
Currently there is no nice way of getting it, you basically need to iterate that list and search for it

And there is another question about OnConnectionClosed. I testing the server using my gui and the HTML example. The server notices when the HTML client disconnects, whether I use the disconnect button, refresh the page or even kill the browser process, the server always triggers the OnConnectionClosed event but it doesn't happen with my GUI, whether I close it or just kill the process, the server doesn't notice it. How could I notify the server I disconnect?
Thats because you don't perform the closing handshake of websockets, but rather break the TCP connection (which will result in an exception).

To close the connection gracefully just call "TWebsocketCommunicator.Close" and wait until TWebsocketCommunicator.Open is false

r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #10 on: October 20, 2021, 06:18:01 pm »
Quote
To close the connection gracefully just call "TWebsocketCommunicator.Close" and wait until TWebsocketCommunicator.Open is false
worked perfectly!

Quote
Currently there is no nice way of getting it, you basically need to iterate that list and search for it
I see... anyway I managed to somehow solve it.  I dug through your code to find out how you implemented ConnectionList and what info it exactly stores.
I created something like:
Code: Pascal  [Select][+][-]
  1. ConnectedUser = record
  2.   UserName: string;
  3.   Handler: TWebsocketCommunicator;
  4. end;
  5. .
  6. .
  7. .
  8. var
  9. ConnectedUserList = array of ConnectedUser;
  10.  
OnConnect I add the client to the list and the client after connecting automatically sends some kind of "hello message" with its username so the server adds it to the list.
So far so good, thanks a lot!

I tried to use these libs with LAMW for Android and I came across a problem with:
Code: Pascal  [Select][+][-]
  1. FCommunicator.OnClose:=@StreamClosed;
  2. FCommunicator.OnReceiveMessage:=@ReceiveMessage;
The compiler (for aarch64) is giving me:
Code: Pascal  [Select][+][-]
  1. unit1.pas(138,26) Error: Variable identifier expected
changing it to:
Code: Pascal  [Select][+][-]
  1. FCommunicator.OnClose:=StreamClosed;
  2. FCommunicator.OnReceiveMessage:=ReceiveMessage;
helps to compile but it's obviously causing some problems.
I ran the app on my phone and it connected to the server, server showed info about the connected client but the app crashed immediately.

Have you tried it with LAMW? any hint?

« Last Edit: October 20, 2021, 10:39:16 pm by r.lukasiak »

r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #11 on: October 25, 2021, 07:15:50 am »
This solution seems to work, it stores the handlers and the usernames. However I'm stuck at sending messages from one client to another. All clients can communicate with the server and server can communicate with the clients, but the problem appears when the server receives a message and tries to send it to another clients.
What I did is:
1. SERVER: Server starts
2. SERVER: Client connects - server stores the handler in ConnectionList and I added my ConnectedUsersList(record of TWebsocketCommunicator and string), it add the handler to my array in TSocketHandler.DoHandleCommunication
3. CLIENT: right after chat.FCommunicator.StartReceiveMessageThread, if chat.FCommunicator.Open is true, it sends a message to the server providing the client's username (each client has its unique username).
4. SERVER: Server receives this message (TSocketHandler.MessageReceived) and updates my array adding the username to the handler.
5. Steps 2-4 for every client (I've tested with just 3)
At this point all clients are able to talk to the server and receive messages from the server.
But if a client sends a message that's supposed to be send to another client, I come across a problem. Some client (apparently the last one) can send messages and these are delivered to another client with no problem but others can't.
How I did it:
1. Client A sends a message that's supposed to get to Client B.
2. Server receives the message (TSocketHandler.MessageReceived). The message contains the Client B's username .
3. Server looks up my array to find the B's username:
Code: Pascal  [Select][+][-]
  1. for i:=0 to length(ConnectedUsersList)-1 do if ConnectedUsersList[i].UserID=ClientB then ConnectedUsersList[i].Handler.WriteStringMessage.....
4. Client B receives the message.
I use locks whenever I  use the connection list or send messages.

I performed some tests:
1#
-Client A connects
-Client B connects
-Client A sends a message to the Server (the message doesn't reach the server)
-Client B sends a message to the Server (the message goes through)
-Server sends a message to Client A (the message goes through)
-Server sends a message to Client B (the message goes through)
at this point Server can send messages to both clients but only Client B can send messages to the Server
-Client B sends a message to Client A (it goes through)
-Client A sends a message to Client B (the message doesn't even reach the Server)
at this point Server can still send message to both Clients but only Client B can send to the Server.
What's even more disturbing, Client B's GUI closes properly, Client A's GUI doesn't want to close, I need to kill the process.

2#
-3 clients connect
-Server can send messages to all 3 clients
-Only Client C can send message to the server

I've tried to do the same using my ConnectedUserList and the original ConnectionList. Another solution was adding UserID: string variable to TWebsocketCommunicator class (wsstream), since it's threaded, each thread keeps it's UserID. No matter which solution I use, the result is the same.

any idea what's wrong?

Warfley

  • Hero Member
  • *****
  • Posts: 608
Re: LazWebsocket How to identify IP adress during connect
« Reply #12 on: November 04, 2021, 08:15:26 pm »
This sounds like a deadlock, have you checked when a thread acquires a lock and when it is released (e.g. by adding simple writeln with debug output)?

r.lukasiak

  • New Member
  • *
  • Posts: 16
Re: LazWebsocket How to identify IP adress during connect
« Reply #13 on: November 05, 2021, 04:21:56 pm »
I was taking care of the locks, as far as I understand how to use them. Anyway the code was kinda messy, I was implementing several ideas and then changing them if they didn't work so I decided to start from 0. I took the example client and server and implemented just 2 simple things but I have already faced the same problem, not all clients can send messages.

First off, I added UserID: string to TWebsocketCommunicator class so the handler stores the user name:
Code: Pascal  [Select][+][-]
  1. TWebsocketCommunincator = class
  2.   private
  3.  .
  4. .
  5. .
  6.   public
  7.     UserID: string;
  8. .
  9. .
  10. .
  11.  end;

The client just connects and sends its user name right after connecting:
Code: Pascal  [Select][+][-]
  1. procedure Ttestchat.FormCreate(Sender: TObject);
  2. begin
  3.   if paramCount()>0 then MyUserID:=ParamStr(1) else MyUserID:='Client';
  4.      writeln(paramCount().ToString+' MyUserID='+MyUserID);
  5.  
  6.   client := TWebsocketClient.Create(MyServerIP, MyServerPort);
  7.   try
  8.     chat := TSimpleChat.Create(client.Connect(TSocketHandler.Create));  
  9.   finally
  10.     client.Free;
  11.   end;
  12.  
  13.     chat.FCommunicator.StartReceiveMessageThread;
  14.  
  15.     while not chat.FCommunicator.Open do
  16.               begin
  17.                 writeln('not open, sleeping...');
  18.                 Sleep(100);
  19.               end;
  20.  
  21.     if chat.FCommunicator.Open then
  22.       begin
  23.         Writeln('chat.FCommunicator.Open=true');
  24.         chat.FCommunicator.WriteStringMessage(mtHello+MyUserID);
  25.         Writeln('Hello message sent');
  26.       end
  27.       else Writeln('chat.FCommunicator.Open=false');
  28. end;

on the server side, when the client connects and sends the "hello" message:
Code: Pascal  [Select][+][-]
  1. procedure TSocketHandler.MessageRecieved(Sender: TObject);
  2.   var
  3.     Messages: TWebsocketMessageOwnerList;
  4.     m: TWebsocketMessage;
  5.     Comm: TWebsocketCommunincator;
  6.     CL: TConnectionList;
  7.     i: integer;
  8.   begin
  9.     Comm := TWebsocketCommunincator(Sender);
  10.     Messages := TWebsocketMessageOwnerList.Create(True);
  11.     try
  12.       Comm.GetUnprocessedMessages(Messages);
  13.       for m in Messages do
  14.         if m is TWebsocketStringMessage then
  15.         begin
  16.           WriteLn('Message from ', Comm.SocketStream.RemoteAddress.Address,
  17.             ': ', TWebsocketStringMessage(m).Data);
  18.  
  19.           //Hello message
  20.           if TWebsocketStringMessage(m).Data[1]=mtHello then
  21.           begin
  22.             writeln('Hello message received');
  23.             write('(MessageReceived) LOCK... ');
  24.             CL:=Connections.Lock;
  25.             writeln('...locked');
  26.             try
  27.             for i:=0 to CL.Count-1 do
  28.               begin
  29.                 if CL[i]=Comm then
  30.                   begin
  31.                     CL[i].UserID:=copy(TWebsocketStringMessage(m).Data,2,length(TWebsocketStringMessage(m).Data)-1);
  32.                     writeln('CL['+i.ToString+'].UserID:='+QuotedStr(CL[i].UserID));
  33.                     Break;
  34.                   end;
  35.               end;
  36.             finally
  37.               write('(MessageReceived) UNLOCK... ');
  38.               Connections.Unlock;
  39.               writeln('...unlocked');
  40.             end;
  41.           end;
  42.         end;
  43.     finally
  44.       Messages.Free;
  45.     end;
  46.   end;
  47.                          

I also implemented some command so I can peek on ConnectionList:
Code: Pascal  [Select][+][-]
  1. procedure TSocketHandler.DoHandleCommunication(
  2.     ACommunication: TWebsocketCommunincator);
  3.   var
  4.     str: string;
  5.     i: integer;
  6.     CL: TConnectionList;
  7.   begin
  8.     WriteLn('Connected to ', ACommunication.SocketStream.RemoteAddress.Address);
  9.     ACommunication.OnRecieveMessage := @MessageRecieved;
  10.     ACommunication.OnClose := @ConnectionClosed;
  11.     while ACommunication.Open do
  12.     begin
  13.       ReadLn(str);
  14.  
  15.       if str='cl' then
  16.       begin
  17.         Write('(DoHandleCommunication) LOCK... ');
  18.         CL:=Connections.Lock;
  19.         Writeln('...locked');
  20.         try
  21.         begin
  22.           Writeln('Count='+CL.Count.ToString);
  23.           if CL.Count>0 then
  24.              for i:=0 to CL.Count-1 do
  25.                writeln(i.ToString+' '+CL[i].UserID);
  26.         end;
  27.         finally
  28.           Write('(DoHandleCommunication) UNLOCK... ');
  29.           Connections.Unlock;
  30.           Writeln('...unlocked');
  31.         end;
  32.       end;
  33.  
  34.       if not ACommunication.Open then
  35.         Break;
  36.     end;
  37.     socket.Stop(True);
  38.   end;                    

Server output:
Quote
$ ./chatServer
Server is starting
Connected to 192.168.1.1                       <- Client A connects
Message from 192.168.1.1: ClientA          <- and everything is ok, it's able to send the Hello message
Hello message received
(MessageReceived) LOCK... ...locked
CL[0].UserID:='ClientA'
(MessageReceived) UNLOCK... ...unlocked
Connected to 192.168.1.3                        <-The same with Client B, everything is ok. It connects from another PC
Message from 192.168.1.3: ClientB
Hello message received
(MessageReceived) LOCK... ...locked
CL[1].UserID:='ClientB'
(MessageReceived) UNLOCK... ...unlocked
                                                                     <-here I fired Client C but it didn't reach the server. I ran it on the same PC that ClientA
cl                                                                   <-I typed 'cl' on the server to see what's in ConnectionList
(DoHandleCommunication) LOCK... ...locked
Count=2
0 ClientA
1 ClientB                                                          <-and there is no info about Client C
(DoHandleCommunication) UNLOCK... ...unlocked


Client A and Client B close without a problem but Client C doesn't, I need to kill the process.

In case of ClientC, according to logs, it connected and sent the message:
Quote
$ ./TestChat ClientC
1 MyUserID=ClientC
chat.FCommunicator.Open=true
Hello message sent

Another scenario, I ran just 2 clients, from the same PC:
Quote
$ ./chatServer
Server is starting
Connected to 192.168.1.3       <-Client1 connects and sends the Hello message but Server doesn't receive it
Connected to 192.168.1.3       <-Client2 connects
Message from 192.168.1.3: Client2   <-and Server receives the Hello message
Hello message received
(MessageReceived) LOCK... ...locked
CL[1].UserID:='Client2'                              <-it's added to the handler
(MessageReceived) UNLOCK... ...unlocked
cl                                                                 <-I fire 'cl' command and....
(DoHandleCommunication) LOCK... ...locked
Count=2
0                                                                   <-...both clients are connected but the Hello message from Client1 never reached Server
1 Client2                                                            so there is no user name.
(DoHandleCommunication) UNLOCK... ...unlocked

I performed several tests and random clients weren't able to connect or send the first message. It looks like I'm doing something wrong right in the beginning but I can't figure it out. Any clue?

 

TinyPortal © 2005-2018