Classica applicazione inutile ma divertente da sviluppare :-)

Qualche giorno fa ho iniziato a chiedermi come il mio amato Quake3 Arena lavorasse per ricercare i server per il gioco in multiplayer sulla rete locale.

Dopo una breve ricerca su Google mi sono imbattuto in questo articolo che spiega a grandi linee come funziona il protocollo utilizzato dal motore di Q3A: tutto basato su UDP (scelta obbligata, per la maggiore velocità) e a dire il vero abbastanza semplice e lineare.

To query a server is very simple. Send a connectionless (UDP) packet with 4 OOB header bytes (0xff) and the text string getstatus. There are many sites which contain a thorough description of this so I won’t go into details.

Quindi, per rilevare se su una macchina remota sia attivo un server è sufficiente forgiare un pacchetto UDP come descritto sopra, inviarlo sulla porta 27960 e attendere una eventuale risposta (non all’infinito, essendo UDP un protocollo ‘connection-less’).

Con queste informazioni la realizzazione di una semplice funzione in VB.NET è cosa da poco:

[sourcecode language=’vb’]

Imports System.Net

Public Function CheckServer(ByVal hostaddress As String)

        Dim _UdpClient As New System.Net.Sockets.UdpClient

        Dim client As New Sockets.Socket(Sockets.AddressFamily.InterNetwork, Sockets.SocketType.Dgram, Sockets.ProtocolType.Udp)

        client.ReceiveTimeout = 5

        client.Connect(IPAddress.Parse(hostaddress), 27960)

        Dim bytCommand As Byte() = System.Text.Encoding.ASCII.GetBytes(“xxxxxgetstatus”)

        bytCommand(0) = Byte.Parse(“255”)

        bytCommand(1) = Byte.Parse(“255”)

        bytCommand(2) = Byte.Parse(“255”)

        bytCommand(3) = Byte.Parse(“255”)

        bytCommand(4) = Byte.Parse(“02”)

        Dim remoteEndPoint As New IPEndPoint(IPAddress.Any, 0)

        Dim pret As String = client.Send(bytCommand, socketFlags:=Sockets.SocketFlags.None)

        Dim bufferRec(65000) As Byte

        Try

            client.Receive(bufferRec)

            Return System.Text.Encoding.ASCII.GetString(bufferRec)

        Catch ex As Exception

            Return “”

        End Try

    End Function

[/sourcecode]

Da notare che ho settato manualmente il timeout della connessione e ‘trappato’ l’errore di connessione, verificando in questo modo se il server sia in funzione o meno.

La funzione mi restituisce una stringa contenente (qualora sia attivo un server sulla macchina esaminata) una serie di informazioni sulla partita in corso; nel caso la connessione vada in timeout, restituisce una stringa vuota.

Ora so verificare se su un determinato sistema stia girando Q3A in modalità multiplayer, il passo successivo è ripetere questa procedura per tutti quelli presenti sulla mia rete locale.

Per farlo questo mi sono affidato a una soluzione ‘sporca’ ma funzionale: utilizzo il comando ‘net view’ di windows, ne analizzo il risultato ottenendo un elenco di indirizzi:

[sourcecode language=’vb’]

Public Function GetIpAddresses()

        Dim addresses As New ArrayList

        Dim MyAdd As String = Dns.GetHostByName(Dns.GetHostName()).AddressList(0).ToString

        Dim psi As System.Diagnostics.ProcessStartInfo = New System.Diagnostics.ProcessStartInfo()

        psi.FileName = (“C:\WINDOWS\System32\cmd.exe”)

        psi.Arguments = “/c net view > lista.txt”

        psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden

        Application.DoEvents()

        System.Diagnostics.Process.Start(psi)

        Dim sr As System.IO.StreamReader = Nothing

        Dim run As Boolean = False

        While run = False

            Application.DoEvents()

            Try

                System.Threading.Thread.Sleep(1000)

                sr = New System.IO.StreamReader(Application.StartupPath & “\lista.txt”)

                run = True

            Catch ex As Exception

                run = False

            End Try

        End While

        While sr.ReadLine().StartsWith(“–”) True

            Application.DoEvents()

        End While

        Dim str As String = “”

        Dim comp(64) As String

        Dim i As Integer = 0

        While str.StartsWith(“The”) True

            Application.DoEvents()

            str = sr.ReadLine

            comp(i) = str.Split(Char.Parse(“ “))(0)

            comp(i) = comp(i).Substring(2, comp(i).Length - 2)

            If comp(i) = “e” Then

                Application.DoEvents()

                comp(i) = Nothing

            End If

            i = i + 1

        End While

        sr.Close()

        sr = Nothing

        For Each s As String In comp

            If s Nothing Then

                If s.ToUpper Dns.GetHostName.ToUpper Then

                   addresses.Add(Dns.GetHostByName(s).AddressList(0).ToString)

                End If

            End If

        Next

        Return addresses

    End Function

[/sourcecode]

a questo punto non mi resta che sottoporre l’array di indirizzi restituito dalla funzione GetIpAddresses a CheckServer e, se viene rilevato un server, leggere i dati restituiti e formattarli adeguatamente:

[sourcecode language=’vb’]

 Dim lista As ArrayList = GetIpAddresses()       

        For Each riga As String In lista.ToArray

            Dim server As String = CheckServer(riga)

            If server “” Then

                Dim ServerName As String = server.Split(“")(Array.IndexOf(server.Split(“"), “sv_hostname”) + 1)

                Dim MapName As String = server.Split(“")(Array.IndexOf(server.Split(“"), “mapname”) + 1)

                Dim FragLimit As String = server.Split(“")(Array.IndexOf(server.Split(“"), “fraglimit”) + 1)

                Dim TimeLimit As String = server.Split(“")(Array.IndexOf(server.Split(“"), “timelimit”) + 1)

                Dim Version As String = server.Split(“")(Array.IndexOf(server.Split(“"), “version”) + 1)

                risultato = risultato & “— Trovato Server —” & vbCrLf

                risultato = risultato & “Ip Address:” & riga & vbCrLf

                risultato = risultato & “Server Name: “ & ServerName & vbCrLf

                risultato = risultato & “Mappa: “ & MapName & vbCrLf

                risultato = risultato & “FragLimit: “ & FragLimit & vbCrLf

                risultato = risultato & “TimeLimit: “ & TimeLimit & FragLimit & vbCrLf

                risultato = risultato & “Versione: “ & Version & FragLimit & vbCrLf

                TextBox1.Text =  TextBox1.Text  & risultato

            End If

            Application.DoEvents()

        Next   

    End Sub

[/sourcecode]

Ho completato il tutto con il codice necessario a ridurre l’applicazione nella system tray (facendo ripetere la procedura di scan ogni 5 secondi) e a far riapparire un form qualora la ricerca fornisca esito positivo.

Il sorgente completo è scaricabile da QUI .

(e sperate che non finisca mai tra le mani del vostro capoufficio!) :-D