Networking for Cowards - Part 2 - Session detection

Previous topic - Next topic

Kitty Hello

OK, in this part of the tutorial "networking for cowards" we deal with sesstion detection. The main aim is to find a server in our LAN or the internet without having the user to type an IP address in some box.

LAN
The first thing we try is in our local network. That's pretty easy, because every network segment has a "broadcast" address. That is an IP address where you can send a UDP request to, and the request will be sent to _all_ computers connected to this network segment. Segment means, that it will stop at a router.

So, in order to find a server, we send a broadcast message "where are you" and wait for a resonse from the server.

First, we need 2 global ports and an application specific string.
Code (glbasic) Select

// 27910 = NET... commands
GLOBAL port_server% = 27911 // UDP for broadcasts
GLOBAL port_client% = 27912 // use a different one to enable 2 processes on one machine (debug on one computer)
GLOBAL session$ = "my_program_name" // a session name -> could change that for a session list



Code (glbasic) Select

// --------------------------------------------
// find a session using a broadcast
// --------------------------------------------
FUNCTION FindSession$:
LOCAL sock%, ip$
sock% = SOCK_UDPOPEN(port_client%) // open UDP client port (send broadcast port)
// Uh-oh. Port is still locked from old instance (happens on iPhone sometimes)
IF sock% <0
SLEEP 2000
RETURN ""
ENDIF

// find my own IP
ip$ = SOCK_GETIP$(SOCK_GETIP(""))
LOCAL ip_broadcast% = SOCK_GETIP("255.255.255.255") // 255.255.255.255 is the broadcast address of a network segment

STDOUT "find session\n"
FOR retry% = 1 TO 3 // retry some times - mostly works at 1st try
LOCAL rv%, msg$
rv% = SOCK_UDPSEND(sock%, session$+"\n"+ip$, ip_broadcast%, port_server%) // send the request
IF rv% = -1
STDOUT "SOCK_UDPSEND -1\n"
BREAK
ENDIF

// wait for a response
FOR reread% = 1 TO 10
rv% = SOCK_RECV(sock%, msg$, 128)
IF rv%>0
SOCK_CLOSE(sock%)
STDOUT "server at"+msg$+"\n"
RETURN msg$ // return what the server said
ENDIF

IF rv% = -1
STDOUT "SOCK_RECV -1\n" // connection error!
GOTO failed
ENDIF

IF rv% = -2 // would block, try again later
STDOUT "."
SLEEP 100
ENDIF
NEXT

// inform about multiple read loops (usually one is enough)
SLEEP 200
NEXT

STDOUT "timed out\n"
@failed:

SOCK_CLOSE(sock%)
RETURN ""
ENDFUNCTION



The server should eventually read that port and replay with "I'm here" or so.
Code (glbasic) Select

// --------------------------------------------
// Wait for a client broadcasting a "Where are you" message.
// Then return the current IP address, so the client
// could connect.
// --------------------------------------------
FUNCTION AnnounceSession: bClose%
STATIC sock%
STATIC serverip$

IF bClose% AND sock% // request to shut down
SOCK_CLOSE(sock%)
sock=0
RETURN
ENDIF

IF sock% = 0
serverip$ = SOCK_GETIP$(SOCK_GETIP("")) // open the listening port
sock% = SOCK_UDPOPEN(port_server%)
IF sock% <0
STDERR "can't host - port locked?\n"
END
ENDIF
ENDIF

// wait for a request
LOCAL rv%, msg$
rv% = SOCK_RECV(sock%, msg$, 512)
IF rv% > 0
STDOUT "incomming broadcast, session: "+msg$+"\n"

// compare session
LOCAL pos% = INSTR(msg$, "\n")
IF pos%>0
IF MID$(msg$, 0, pos%) = session$
LOCAL clientip$ = MID$(msg$, pos%+1, -1)
LOCAL clientip% = SOCK_GETIP(clientip$)
// reply
STDOUT "client accepted\n"

// send twice - just to be sure
SOCK_UDPSEND(sock%, serverip$, clientip%, port_client%)
SLEEP 3
SOCK_UDPSEND(sock%, serverip$, clientip%, port_client%)
ENDIF
ENDIF
ENDIF

IF rv=-1 // error -> reconnect
STDOUT "announce_session - error. Try reconnect\n"
SOCK_CLOSE(sock%)
sock% = 0 // display error message here. (Wait for a few seconds, then retry)
ENDIF
ENDFUNCTION


In out game we would use it like:

Code (glbasic) Select

full_again:

IF SOCK_INIT()=FALSE
STDERR "sock init failed\n"
PRINT lang$("No Wifi", "Kein WLan"),32,screeny-fy-8; SHOWSCREEN
WHILE TRUE; SLEEP 1000; WEND
ENDIF


session_ip$ = FindSession$()
IF LEN(session_ip$)>4 AND NETJOINGAME(session_ip$, 0)
gIsServer%=FALSE
ELSE
PRINT "Hosting session", 32, 32; SHOWSCREEN
IF NETHOSTGAME(0)
gIsServer%=TRUE
SLEEP 100
ELSE
STDERR "can't host, port seems still open\n"
PRINT "Port busy, please wait.", 32,32; SHOOWSCREEN
NETSHUTDOWN
SLEEP 2000
GOTO full_again
ENDIF
ENDIF

// ... ok, the game is connected and on.



INTERNET
OK, long time no update. Here's how to find a seccion over the internet using a very simple php script:
(Uses an .ini file - no database)

Code (glbasic) Select

<?php
  
// http://www.GLBasic.com/netsession.php?session=test&host=0
// host= 0 -> find server - return server IP if there's a session
// host= 1 -> host new session - returns "HOSTED" on success
// host=-1 -> stop session - I forgot if it returns something usefull

    
$ses $_GET['session'];
    
session_id('glbs'.$ses);
    
$host = (int)$_GET['host'];
    

    
$timeout 15*60// [sec]
    
session_start();

    if(isset(
$_GET['host']) && $host == 1)
    {
        
// if(PHP <= 4.0.6) use $HTTP_SESSION_VARS
        
$_SESSION[$ses] = $_SERVER['REMOTE_ADDR'];
        
$_SESSION[$ses.'@TimE'] = time();
        echo 
'HOSTED';
    }
    else if(
$host == -1// stop a session
    
{
        
$_SESSION[$ses] = '0';
        
$_SESSION[$ses.'@TimE'] = 0;
        echo 
'STOPPED';
    }
    else 
// find a session
    
{
        if (!isset(
$_SESSION[$ses]) || !isset($_SESSION[$ses.'@TimE']) || (int)$_SESSION[$ses.'@TimE'] < (time()-$timeout) )
        {
            echo 
'0';
        }
        else
        {
            
// seems a good session, 
            
echo $_SESSION[$_GET['session']];
        }
    }
?>



Please feel free to use my script, but notify me, please. I might put filters in, when traffic rises to steep.

The GLBasic side of the script is easy as:
Code (glbasic) Select


FUNCTION FindSessionINET$:
LOCAL ip$
// see if there is already a session
ip$ = NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=0", 0, 256)

IF LEN(ip$)>4 THEN RETURN ip$
RETURN ""
ENDFUNCTION


// Announce session in INET
// true/false
FUNCTION AnnounceSessionINET%: bClose%
LOCAL ip$
IF bClose% = FALSE
ip$ = NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=1", 0, 256)
IF ip$="HOSTED" THEN RETURN TRUE
RETURN FALSE
ELSE
// tell the server to stop it
NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=-1", 0, 256)
RETURN TRUE
ENDIF
ENDFUNCTION



... See the updated version here:
http://www.glbasic.com/forum/index.php?topic=2779.msg95840#msg95840



Schranz0r

I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

FutureCow


bigsofty

Arg, what happened to the 2ND lesson? :rant:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

bigsofty

Can anyone see Gernots post in this thread, Ive noticed a few of his posts are now blank when I read them?
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

Hemlos

Bing ChatGpt is pretty smart :O

FutureCow

There are various bits of threads that disappeared when the forum moved to the new layout. It's not just Gernot's posts though that are affected.
eg. The first post in this thread by Schranz0r http://www.glbasic.com/forum/index.php?topic=1103.0

bigsofty

Damn board hackers... now this sucks!  Gernot do you still have this on backup or did anyone save this page?  :rtfm:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

Kitty Hello

Schranz - can we have the old board in a new database so we can copy/paste some threads manually??

codegit

Does anybody have a copy of the original code posted by Gernot? Please!!!  =D
------------------------------------------
1 X Acer TravelMate 4270, laptop, XP PRO
1 X Dell Studio 17 laptop, Windows 7
1 X MacBook Pro 2,2 GHz Core 2 Duo, 2 GB RAM, 160 GB HDD, 9400M
2 X iTouch
1 X HTC Desire (Android 2.1)
iPad soon to be added

FutureCow

Just a thought. Have a look in the samples you get with GLBasic. I just found there's some networking ones there which might show you how to do whatever networking stuff you're looking for.

bigsofty

For myself, code only, is a pretty weak substitute for the original text. As it is a new subject to many, a good explanation is required in interpret the accompanying examples.  :(
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

bigsofty

Excellent, many thanks for repairing the tutorial!  :good:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

codegit

------------------------------------------
1 X Acer TravelMate 4270, laptop, XP PRO
1 X Dell Studio 17 laptop, Windows 7
1 X MacBook Pro 2,2 GHz Core 2 Duo, 2 GB RAM, 160 GB HDD, 9400M
2 X iTouch
1 X HTC Desire (Android 2.1)
iPad soon to be added

Kitty Hello

Here's an updated version that uses an extended version on my server. Feel free to directly use the GLBAsic.com server interface for your game, but please tell me before you release it.

Code (glbasic) Select


// --------------------------------- //
// Start: Wednesday, August 27, 2008
// IDE Version: 5.360


// ------------------------------------
// FIND A SESSION ON LAN or INTERNET
// ------------------------------------
// Open firewall ports:
// 27910 TCP - game data transfer - MUST open this one outgoing, and (if you're the server) ingoing
// 27911 UDP - LAN session anouncement, incomming (server)
// 27912 UDP - LAN session anouncement, incomming (client)

// *** HOW TO USE ***
// SessionInit("MyGame", "Chatroom-ID", "UserName", bInternetYesNo%)
// WHILE TRUE
// gPlayer% = SessionKeepAlive(TRUE)   // NETCREATEPLAYER("scribble")
// IF gPlayer<>0 THEN NETSENDMSG(gPlayer%, 0, "Hello World")
// UpdateGame()
// WEND

// *** Internals ***
// The app will try to find a session (chatroom). If a sever exists, it connects.
// If no server exists, it will start a server and ...
// -LAN-mode:      Read from a UDP port for incomming client requests.
// -Internet-mode: Send glbasic.com the IP of this device, registering app name, session-ID.
//                 The client in internet mode will connect to glbasic.com and gain the app-server's IP
//                 and connect to it.

// Set this SUB if you want to get notified on errors
GLOBAL gSessionErrorSUB$    = "" // this SUB is called when an error occours.
GLOBAL gSessionErrorString$ = "" // here you find the error details.




GLOBAL gSessionIsInternet% = FALSE // TRUE: Internet, FALSE: LAN
GLOBAL gSessionIsServer% = FALSE
GLOBAL gSession$ = "INVALID"
GLOBAL gSession_port_nethost% = 27910 // TCP - you must ***open this port***  for incomming connections on your firewall and router, if this computer is the server.
GLOBAL gSession_port_server% = 27911  // UDP - server reads from to this port in LAN network for incomming clients.
GLOBAL gSession_port_client% = 27912  // UDP - client reads from this port in LAN when finding the server.
GLOBAL gSession_net_player% = 0 // NET player id
GLOBAL gSession_net_playername$ = "joe"


// --------------------------------------------
// init the session
// App$     - your app name
// Session$ - the name of the session to find/host (consider that as a chatroom)
// App$+Session$ must be > 10 chars and < 24 chars! Otherwise you get the return value -1 in FindSessionINET$
// port_client - 0:default. UDP port for local LAN broadcasts
// port_server - 0:default. UDP port the server listens to
// --------------------------------------------
FUNCTION SessionInit: App$, Session$, PlayerName$, bIsInternet%, port_nethost%=0, port_client%=0, port_server%=0

STDOUT "session init "+App$+"/"+Session$+"/"+PlayerName$+". Internet: "+bIsInternet%+"\n"

// just to be sure, close connections
SessionKeepAlive(FALSE)

// make unique session string, with safe characters
gSession$ = LEFT$(UCASE$(App$) + "-" + URLENCODE$(UCASE$(Session$)), 24)

gSession_net_playername$ = PlayerName$

gSessionIsInternet% = bIsInternet%

IF port_nethost>0 THEN gSession_port_nethost% = port_nethost%
IF port_client>0  THEN gSession_port_client%  = port_client%
IF port_server>0  THEN gSession_port_server%  = port_server%

// just to be sure
SOCK_INIT()
ENDFUNCTION




// --------------------------------------------
// find a session using a broadcast
// returns "" - still searching
//         IP address - server found
// --------------------------------------------
FUNCTION SessionFindLAN$:
LOCAL sock%, ip$
STDOUT "open udp: "+gSession_port_client%+"\n"

sock% = SOCK_UDPOPEN(gSession_port_client%)
// Uh-oh. Port is still locked
IF sock% <0
SessionError("Can't open UDP port "+gSession_port_client%)
STDOUT NETGETLASTERROR$() + "\n"
STDOUT "can't open udp port (can't connect). Might be still locked from old session\n"
RETURN ""
ENDIF

// find my own IP
ip$ = SOCK_GETIP$(SOCK_GETHOSTIP$(""))
LOCAL ip_broadcast$ = SOCK_GETHOSTIP$("255.255.255.255")

STDOUT "find session\n"
FOR retry% = 1 TO 3
LOCAL rv%, msg$
rv% = SOCK_UDPSEND(sock%, gSession$+"\n"+ip$, ip_broadcast$, gSession_port_server%)
IF rv% = -1
STDOUT NETGETLASTERROR$() + "\n"
STDOUT "SOCK_UDPSEND -1\n"
BREAK
ENDIF

// wait for a response
FOR reread% = 1 TO 5
rv% = SOCK_RECV(sock%, msg$, 128)
IF rv%>0
SOCK_CLOSE(sock%)
STDOUT "server at"+msg$+"\n"
RETURN msg$ // return what the server said
ENDIF

IF rv% = -1
STDOUT "SOCK_RECV -1\n"
GOTO failed
ENDIF

IF rv% = -2 // would block, try again later
STDOUT "."
SLEEP 100
ENDIF
NEXT

// inform about multiple read loops (usually one is enough)
SLEEP 200
NEXT

STDOUT "timed out\n"
@failed:

SOCK_CLOSE(sock%)
RETURN ""
ENDFUNCTION



// --------------------------------------------
// Wait for a client broadcasting a "Where are you" message.
// Then return the current IP address, so the client
// could connect.
// does not return any value
// --------------------------------------------
FUNCTION SessionAnnounceLAN%: bClose%
STATIC sock%
STATIC serverip$

IF bClose%
IF sock% THEN SOCK_CLOSE(sock%)
sock=0
RETURN
ENDIF

IF sock% = 0
serverip$ = SOCK_GETIP$(SOCK_GETHOSTIP$(""))
STDOUT "open udp: "+gSession_port_server%+"\n"
sock% = SOCK_UDPOPEN(gSession_port_server%)
IF sock% <0
STDOUT NETGETLASTERROR$() + "\n"
STDERR "can't host - port locked?\n"
RETURN
ENDIF
ENDIF

// wait for a request
LOCAL rv%, msg$
rv% = SOCK_RECV(sock%, msg$, 512)
IF rv% > 0
STDOUT "incomming LAN broadcast, session: "+msg$+"\n"

// compare session
LOCAL pos% = INSTR(msg$, "\n")
IF pos%>0
IF MID$(msg$, 0, pos%) = gSession$
LOCAL clientip$ = MID$(msg$, pos%+1, -1)
LOCAL clientip_binary$ = SOCK_GETHOSTIP$(clientip$)
// reply
STDOUT "client accepted\n"

// send twice - just to be sure
SOCK_UDPSEND(sock%, serverip$, clientip_binary$, gSession_port_client%)
SLEEP 3
SOCK_UDPSEND(sock%, serverip$, clientip_binary$, gSession_port_client%)
ENDIF
ENDIF
ENDIF

IF rv=-1 // error -> reconnect
SessionError("Can't announce session. Try to reconnect.")
STDOUT "announce_session - error. Try reconnect\n"
SOCK_CLOSE(sock%)
sock% = 0
ENDIF
ENDFUNCTION


FUNCTION SessionFindINET$:
LOCAL ip$
// see if there is already a session
ip$ = NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=0", 0, 256)

STDOUT "Find internet session: "+ip$+"\n"

IF LEN(ip$)>4 THEN RETURN ip$
RETURN ""
ENDFUNCTION


// Announce session in INET
// true/false
FUNCTION SessionAnnounceINET%: bClose%
LOCAL ip$
STATIC bIsOpen%=FALSE
STATIC time_next_annc = 0 // don't connect to internet on every function call

IF bClose% = FALSE
bIsOpen% = FALSE

LOCAL now =GETTIMERALL()
IF now>time_next_annc
ip$ = NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=1", 0, 256)
STDOUT "internet hosting game: "+ip$+"\n"
IF ip$="HOSTED"
bIsOpen% = TRUE
time_next_annc = GETTIMERALL() + 5 * 60 * 1000
ELSE
SessionError("Can't announce in internet.")
STDOUT NETGETLASTERROR$() + "\n"
ENDIF
ENDIF

RETURN bIsOpen%
ELSE
// tell the server to stop it
IF bIsOpen%
ip$=NETWEBGET$("www.glbasic.com", "/netsession.php?session="+gSession$+"&host=-1", 0, 256)
STDOUT "Stopped hosting internet game: "+ip$+"\n"
ENDIF
bIsOpen% = FALSE
time_next_annc = 0
RETURN TRUE
ENDIF
ENDFUNCTION




// just call this function every now and then to get going
// this returns your player-ID for NETSEND and such...
FUNCTION SessionKeepAlive%: bKeepAlive%
STATIC bConnected% = FALSE
IF NOT bKeepAlive // Shutdown
STDOUT "Session shutdown\n"
IF gSessionIsInternet
SessionAnnounceINET(TRUE)
ELSE
SessionAnnounceLAN(TRUE)
ENDIF
bConnected = FALSE
NETSHUTDOWN
SOCK_SHUTDOWN
gSession_net_player = 0
gSessionIsServer% = FALSE
RETURN
ENDIF


IF NOT bConnected% // join session
gSessionIsServer% = FALSE
gSession_net_player%=0
LOCAL ip$
IF gSessionIsInternet
ip$ = SessionFindINET$()
ELSE
ip$ = SessionFindLAN$()
ENDIF

IF LEN(ip$)>4
STDOUT "found sesstion. Try to NETJOINGAME "+ip$+":"+gSession_port_nethost%+"...\n"
IF NETJOINGAME(ip$, gSession_port_nethost%)
STDOUT "...OK\n"
bConnected% = TRUE

// STDOUT "Number of players: "+NETNUMPLAYERS() + "\n"
ELSE
SessionError("Failed to join game session at "+ip$+":"+gSession_port_nethost%)
STDOUT NETGETLASTERROR$() + "\n"
ENDIF
ENDIF
ENDIF

IF NOT bConnected%
STDOUT "Trying to host a session...\n"
gSession_net_player%=0
bConnected% = NETHOSTGAME(gSession_port_nethost%)
IF bConnected%
NETALLOWJOINING TRUE
gSessionIsServer% = TRUE

// See if we can be reached from the internet
IF gSessionIsInternet
LOCAL isok$ = NETWEBGET$("www.glbasic.com", "/netsession.php?ping="+gSession_port_nethost%, 0, 256)
IF LEFT$(isok$, 2) <> "OK"
SessionError("FIREWALL blocked port "+gSession_port_nethost%)
STDOUT isok$+"\n"
ELSE
STDOUT "Firewall post "+gSession_port_nethost%+" is open. Clients can connect.\n"
ENDIF
ENDIF
ENDIF
ENDIF


IF bConnected% // announce session
IF gSessionIsInternet
SessionAnnounceINET(FALSE)
ELSE
SessionAnnounceLAN(FALSE)
ENDIF

IF NOT NETISACTIVE()
gSession_net_player%=0
bConnected=FALSE
ENDIF
ENDIF

// no player created
IF bConnected% AND gSession_net_player% = 0
gSession_net_player% = NETCREATEPLAYER(gSession_net_playername$)
STDOUT "created player"+gSession_net_player%+"\n"
ENDIF


RETURN gSession_net_player%

ENDFUNCTION



FUNCTION SessionError%: err$
STDOUT "ERROR: "+err$+"\n"
gSessionErrorString$ = err$
CALLBYNAME( gSessionErrorSUB$)
ENDFUNCTION