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↩