A TCP/IP alternatívája adatszállítás tekintetében az UDP, ami datagramokban továbbítja az adatokat és az adat sikeres célba éréséről nem kér visszaigazolást a klienstől. Ez első hangzásra problémás lehet, mivel hálózati adatátvitel során könnyen keletkezhetnek hibák. De ez még nem jelenti azt, hogy minden hiba egyformán fontos lenne. Sok múlik a kontextuson. Például egy élő internetes hang vagy videó stream közben, ha egy csomag kimarad, akkor hiába tud róla a feladó, újraküldeni nem érdemes, mert mire megérkezik a kimaradt csomag, már rég máshol kellene tartania a közvetítésnek.
Éppen ezért élő közvetítések továbbítására kiváló protokoll az UDP. Ezen felül számos más célra is használható. Például a DHCP hálózat konfigurációs protokoll is UDP-n keresztül működik.
Hasonlóan a TCP/IP programozásához a .NET is több lehetőséget ad, de van ami itt is háttérbe szorult, szóval itt is a Task alapú megoldásokat szemléltetem, amelyek megértéséhez szintén szükséges egy minimális hálózati alapismeret.
A példaprogramok egy idő szervert és a hozzá tartozó klienst implementálják:
using NetworkCommon;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace UDPServer
{
internal static class Program
{
static void Main(string[] args)
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
CancellationTokenSource tokenSource = new CancellationTokenSource();
try
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
socket.Bind(new IPEndPoint(IPAddress.Parse(Constants.LocalhostIpv4), Constants.UDPProgramPort));
Task.Run(() => ServerTask(socket, tokenSource.Token));
Console.WriteLine("A szerver fut. Nyomj egy gombot a kilépéshez.");
Console.ReadKey();
}
finally
{
socket.Close();
tokenSource.Cancel();
tokenSource.Dispose();
}
}
private static async Task ServerTask(Socket socket, CancellationToken token)
{
IPEndPoint any = new IPEndPoint(IPAddress.Any, Constants.UDPProgramPort);
byte[] buffer = new byte[4096];
while (true)
{
if (token.IsCancellationRequested)
{
break;
}
SocketReceiveFromResult result = await socket.ReceiveFromAsync(buffer, any, token);
string msg = Encoding.UTF8.GetString(buffer, 0, result.ReceivedBytes);
if (msg == "time")
{
byte[] response = BitConverter.GetBytes(DateTime.Now.ToBinary());
await socket.SendToAsync(response, result.RemoteEndPoint);
}
else
{
await socket.SendToAsync(Array.Empty<byte>(), result.RemoteEndPoint);
}
}
}
}
}
A szerverünk itt is egy külön szálon van megvalósítva. UDP kommunikációt a Socket osztály megfelelő konfigurációjával tudunk létrehozni. A Socket az egy porton folyó hálózati kommunikációt reprezentál és akár TCP-re is konfigurálhatnánk. A konfigurációban a SocketType.Dgram és ProtocolType.Udp párban járnak. A SetSocketOption hívással bekonfiguráljuk, hogy kétirányú a kommunikáció és más kliens is csatlakozhat hozzánk, majd a Bind metódussal ténylegesen el is kezdjük figyelni a bejövő kapcsolatokat.
A ReceiveFromAsync metódus egy blokkoló hívás, ami addig fog várakozni, amíg nem kaptunk adatot. Éppen ezért, hogy lehetőségünk legyen megszakítani a figyelést, ennek a metódusnak továbbadjuk a CancellationToken változónkat. A ReceiveFromAsync metódus esetén meg kell mondanunk azt is, hogy honnan fogadunk adatot. Ezt az any változó úgy konfigurálja, hogy bármilyen IPv4 címről fogadunk egészen addig, amíg a megfelelő porton van továbbítva.
Ha sikeres volt a fogadás, akkor egy SocketReceiveFromResult típusban megkapjuk azt, hogy a bufferbe mennyi byte adat lett írva, illetve a kliens IP címét is (RemoteEndPoint tulajdonság). Ezen két adat felhasználásával kiolvassuk a kliens által küldött adatot, ami jelen esetben egy UTF-8 kódolású parancsszó (time). Ha ez az, amit várunk, akkor a jelenlegi időt long formátumba konvertáljuk a DateTime osztály ToBinary() metódusával, majd a BitConverter.GetBytes hívással kapott byte tömböt átküldjük a kliensnek. Hibás parancs esetén üres választ továbbítunk.
using NetworkCommon;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
internal static class Program
{
static async Task Main(string[] args)
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
await socket.ConnectAsync(new IPEndPoint(IPAddress.Parse(Constants.LocalhostIpv4), Constants.UDPProgramPort));
await socket.SendAsync(Encoding.UTF8.GetBytes("time"));
byte[] buffer = new byte[4096];
int bytes = await socket.ReceiveAsync(buffer);
if (bytes == sizeof(long))
{
DateTime ServerTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0));
Console.WriteLine(ServerTime);
}
}
}
}
Kliens oldalon a ConnectAsync metódussal csatlakozunk a konfiguráció után a szerverhez és adatot a ReceiveAsync metódussal fogadunk, ami egy egész számként (int) adja vissza, hogy mennyi adat került írásra a bufferbe. Ezt felhasználva érdemes ellenőrizni, hogy a kapott adatmennyiség megegyezik-e egy long hosszával. Ha igen, akkor a szerveren elvégzett átalakítás fordítottját elvégezzük.
Figyelem: A bemutatott kódrészlet hasznosnak tűnhet, de csak egy példaprogram és ebből adódóan nem alkalmas számítógépek idejének hálózaton keresztüli szinkronizálására, mivel a kommunikáció nem veszi figyelembe azt, hogy az adatátvitel nem azonnal történik meg. Ha ilyen jellegű alkalmazásra lenne szükségünk, akkor az NTP protokollt1 használó alkalmazások képesek ennek a kezelésére.
-
"A hálózati idő protokoll (angolul Network Time Protocol, NTP) számítógépes rendszerek óráinak szinkronizálására szolgáló hálózati protokoll. A protokoll csomagkapcsolt hálózaton keresztül működik. Az NTP az egyik legrégebbi internetprotokoll." – https://hu.wikipedia.org/wiki/H%C3%A1l%C3%B3zati_id%C5%91_protokoll↩