Broadcast och chatt

Inledning

I denna artikelserie ska vi undersöka hur man använder .NET för att utveckla program som kommunicerar via nätverket. För att använda nätverket så behövs en hel del kunskaper om IP, TCP och UDP. Vi skall göra vårt bästa för att kortfattat förklara nätverkstekniken bakom.

Målet är att skapa ett så enkelt, men ändå fungerande, chattprogram på det lokala nätverket med ett Console Application.

Broadcast

Server & klient är ett vanliga termer inom nätverk. En server tillhandahåller en tjänst som en eller flera klienter kan koppla upp sig till. Föreställ dig en spelserver som du ansluter till för att spela multiplayer.

Vi vill undvika en server-klient lösning när vi skapar chattprogrammet. Varje enskild användare skall kunna ta emot alla meddelanden samt även skicka till alla. För att lösa detta så måste vi använda oss av en teknik som kallas broadcast.

Ett broadcast-meddelande skickas ut i nätverket med avsikt att nå alla anslutna datorer. För detta så finns det en specifik adress specificerad i IP-standarden, nämligen 255.255.255.255. Det finns ytterligare en broadcast-adress i IP för nätverket som beror på dina IP-inställningar. T.ex om du har IP 192.168.0.20 med nätmasken 255.255.255.0 så är nätets broadcast-adress 192.168.0.255.

För att komplicera saken ytterligare så finns det en broadcast-adress på MAC-nivå (en nivå lägre i OSI-modellen). Den adressen är FF:FF:FF:FF:FF:FF. IP-paket med en IP-broadcast ges automatiskt adressen FF:FF:FF:FF:FF:FF på MAC-nivå.

Vi tänker oss en lösning där användarna "skriker" ut sina meddelanden som broadcast i nätet. Samtidigt så lyssnar alla efter sådan trafik. Alltså två funktioner; lyssna och skicka. Chatten kommer då bara att fungera i det lokala nätet, dvs. det nät som du är ansluten till, och kommer att vara extremt lättavlyssnat.

Vidare läsning

Om du är nybörjare inom nätverk så rekommenderar vi följande videos:

Trådar

När man sysslar med kommunikation så kommer vi snabbt in på begreppet trådar. En tråd är en aktivitet som kan köras oberoende av andra aktiviteter. Man säger ibland att man vill köra något parallellt. Ett program startar en process på datorn. En process i sin tur kan dela upp sig själv i ett flertal trådar.

Alla program som presenteras på csharpskolan kör bara i en enda tråd. Man måste aktivt skapa en tråd eller använda delar av .NET som körs i egna trådar för att skapa parallella aktiviteter i C#. Men nu är det alltså dags att skapa en tråd eftersom vi vill göra två saker samtidigt (parallellt). Vi vill lyssna och kunna skicka.

UDP

UDP står för User Datagram Protocol och kräver ingen kontinuerlig förbindelse, så kallad session. Det betyder att med UDP så vet man inte om kommunikationen har nått fram. Använder man istället TCP som i sin tur använder sessions så får man alltid ett ACK (acknowledge) som svar på varje paket. Man vet också då om informationen nått fram.

Portar

Förutom att paket som skickas med IP använder IP-adresser så används också ett portnummer. Ett portnummer är en 16-bitars (0-65535) siffra som anger vilken tjänst man vill kommunicera med. En server erbjuder en tjänst på en specifik port. Som exempel kan vi ta en webbserver. Webbservern ligger normalt och lyssnar på port 80. Klienter som ansluter kommunicerar med servern på just port 80. Nästan all HTTP-trafik går via port 80 (om den inte är krypterad).

Alltså för att skicka data på nätverket så måste vi bestämma oss för vilken port som skall användas. De 1024 första portarna är redan "paxade" för specifik trafik. Utöver dessa så finns det ännu fler registrerade portar som vanligtvis används för en viss sorts trafik.

Vi kan faktiskt ta vilken port vi vill men om vi väljer en "vanlig" port så riskerar vi att snappa upp en del onödig trafik. Sannolikheten är dock liten för då måste någon specifikt kommunicera med oss på UDP med just den port vi hade tänkt oss.

Det vi inte kan göra är att ligga och lyssna på en port som redan är upptagen på din maskin! T.ex. om du redan har en webbserver igång (IIS eller Apache) på port 80 så kan ditt program inte lyssna på port 80.

Vi kommer att använda oss av port 11000.

Koden

Vi presenterar programmet i sin helhet här först för att senare diskutera viktiga saker i detalj.

BroadcastChat.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace BroadcastChat
{
    class Program
    {
        private const int ListenPort = 11000;

        static void Main(string[] args)
        {
            var listenThread = new Thread(Listener);
            listenThread.Start();

            Socket socket = new Socket(AddressFamily.InterNetwork, 
                SocketType.Dgram, ProtocolType.Udp);
            socket.EnableBroadcast = true;
            IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 11000);

            Thread.Sleep(1000);

            while(true)
            {
                Console.Write(">");
                string msg = Console.ReadLine();

                byte[] sendbuf = Encoding.UTF8.GetBytes(msg);
                socket.SendTo(sendbuf, ep);
                Thread.Sleep(200);
            }
        }

        static void Listener()
        {
            UdpClient listener = new UdpClient(ListenPort);

            try
            {
                while (true)
                {
                    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, ListenPort);
                    byte[] bytes = listener.Receive(ref groupEP);

                    Console.WriteLine("Received broadcast from {0} : {1}\n", groupEP.ToString(),
                        Encoding.UTF8.GetString(bytes, 0, bytes.Length));
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            finally
            {
                listener.Close();
            }
        }
    }
}

Lyssna

Det första programmet gör är att skapa en Thread samt startar den (rad 16). Tråden binds till metoden Listener och kommer att köras parallellt med det övriga programmet som fortsätter sin exekvering.

I metoden Listener skapas en UdpClient som är en användbar klass just för UDP. I konstruktorn så anges porten 11000 som vi kommer att lyssna på.

Tråden sköter sedan en felhantering med try, catch och finally men det intressanta sker i den eviga loop som startar på rad 42. Här skapas en IPEndPoint med ipadressen IPAddress.Any. Denna adress är i själva verket 0.0.0.0 och betyder att vi är beredda att lyssna på alla nätverksinterface som vår dator har. Vanligtvis så finns det flera nätverk tillgängliga, inte bara fysiska utan även så kallade "loopback" (127.0.0.1) interface. Utöver detta så räknas IPv4 och IPv6 som separata interface.

Så istället för att behöva lista ut vilket nätverkskort vi vill lyssna på så säger vi helt enkelt att vi är intresserade av all UDP-trafik på port 11000. Till vår IPEndPoint anger vi också port 11000. Det behöver vi faktiskt inte, vissa anger port 0 här. Objektet groupEP kommer att skickas som en referens när vi lyssnar på rad 45 och kommer att fyllas i med information om IP och port. Detta är aningen förvirrande att man skickar med ett objekt som anger information om IP men man bryr sig inte om port samt att detta objekt därefter har ändrats med information om mottagaren.

Själva lyssnandet sker via metoden Receive från UdpClient på rad 45. Här kommer tråden att stanna och vänta på något slags svar. Skulle det dyka upp något så fångas svaret i fältet bytes. Sedan tolkar vi datan som text via Encoding.UTF8s.GetString och skriver ut denna text plus information om mottagaren i konsollen.

Skicka

Vår "huvudtråd" fortsätter in i en oändlig loop på rad 25 men först skapas det en Socket. Vårt socket skapas som en IPv4 UPD socket. Vi skapar även här en IPEndpoint som anger vilken adress samt port vi vill skicka till. Porten är såklart 11000 och adressen är IPAddress.Broadcast.

IPAddress.Broadcast är adressen 255.255.255.255. Istället för att behöva lista ut vilket nät vi ska kommunicera på så överlåter vi det till systemet att avgöra vilket nätverkskort som har högst prioritet. Det är på detta nätverk som paketet kommer att skickas på. Ett alternativ hade varit att manuellt ange t.ex. IPAddress.Parse("192.168.0.255") för ett visst näts broadcast men då måste man kompilera om programmet varje gång man byter uppkoppling.

I loop'en så väntar vi på ett enter och översätter texten till bytes som sedan direkt skickas på vår socket. Thread.Sleep på rad 32 behövs inte.

bild

När programmet körs och man skriver något så kommer man automatiskt att få ett eko tillbaka. Kontrollera att IP-adressen stämmer med det förväntade nätet. Det kan vara som så att paketet skickas på fel interface (se VirtualBox problem nedan).

Brandväggen

bild

När programmet startar första gången så kommer Windows automatisk upptäcka att programmet vill lyssna på port 11000. Du får därför en säkerhetsfråga om du vill öppna upp i brandväggen för detta program. Du bör tillåta annars kommer du inte att lyckas kommunicera.

Windows gillar att klassificera nätverken i olika kategorier, t.ex. Privata, Domän, Offentliga, etc. När dialogen dyker upp så kan du kryssa i alla rutor om du vill. Det nätverk som du sitter på just nu är det ikryssade alternativet i dialogen.

Windows kommer att komma ihåg denna inställning för just detta program (se bilden nedan).

bild

Router/Switch problem

Det kan var så att du sitter kopplad till en router (trådlöst) eller switch (trådat) som inte tillåter all broadcast-trafik. Just broadcast-trafik vill man undvika då det försämrar prestandan i nätet. Viss broadcast-trafik måste tillåtas t.ex. DHCP (automatisk tilldelning av IP-inställningar) etc. Men i övrigt vill man inte ha det.

Alltså om du sitter på ett skolnät eller företagsnät så är det inte säkert att paketen når ut till alla. Då är det tyvärr inte så mycket att göra annat än att sätta upp ett eget nätverk och testa eller möjligtvis testa lite andra portar. Eftersom port 67 broadcast används till DHCP så är denna port garanterat inte blockerad. Du kommer dock att fånga upp en massa DHCP-paket som skrivs ut som rappakalja.

VirtualBox problem

Ett problem som vi stötte på var att VirtualBox saboterade broadcast-kommunikation på 255.255.255.255. Istället för att skicka paketen på rätt interface så skickades de på "VirtualBox Host-Only Network" vilket är ett virtuellt nätverkskort som installeras i samband med VirtualBox.

Kör man kommandot netstat -nr för man en lista på inlagda routing-vägar (se bild nedan). Systemet försöker via dessa vägar avgöra vart ett paket ska skickas. Normalt sett så prioriteras trafik rätt, dvs. den anslutning med högst hastighet som har internetaccess prioriteras främst.

bild

Det som har hänt med VirtualBox är att nätvärket "VirtualBox Host-Only Network" har lagt in en routing för 255.255.255.255 med en prioritet som matchar eller är högre än anslutningen till internet. För att inte riskera att paketen skickas fel så kan man justera prioriteringen för "VirtualBox Host-Only Network" genom följande:

1. Öppna "Nätverks och delningscenter"
2. Klicka "Ändra inställningar för nätverkskort" till vänster.
3. Högerklicka på "Virtualbox Host-Only Network", välj egenskaper.
4. Dubbelklicka "Internet Protocol Version 4 (TCP/IPv4)".
5. Klicka "Avancerat..." på fliken egenskaper.
6. Under fliken "IP Inställningar", avmarkera boxen "Automatiskt mått" och ange 800 eller högre.

Avslutning

Detta var en introduktion till nätverksprogrammering. Tyvärr finns det många felkällor som kan ställa till det; Vi skickar trafik som inte alla routrar tycker om, VirtualBox kan vara installerat, vi kanske har någon elak brandvägg emellan som sätter stopp, etc... Hursomhelst så hoppas vi att du tyckte artikeln var intressant!

Övning 1

Skriv ut en tidsstämpel i utskriften så du vet när du tog emot ett meddelande.

Övning 2

Försök skilja på dina egna meddelanden och andras genom att skriva ut dem med olika färger. Använd Console.ForegroundColor.

Övning 3

Gå över till att använda port 67 (DHCP). Du får nu problemet att det dyker upp en massa annan trafik som inte har med chatten att göra. Märk dina paket på ett sätt som du bestämmer så du kan urskilja din trafik från övrig.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top