Mówiąc o sieciach, nie sposób nie wspomnieć o modelu OSI.

Dziś w tym modelu najbardziej interesuje nas warstwa transportowa (4).

Na tym poziomie pracujemy z danymi, które idą „z punktu A do punktu B”. Głównym zadaniem warstwy transportowej jest zapewnienie dostarczenia wiadomości do miejsca docelowego przy zachowaniu prawidłowej kolejności. Istnieją 2 najpopularniejsze protokoły warstwy transportowej: TCP i UDP. Działają koncepcyjnie na różne sposoby, ale każdy ma swoje zalety, co pozwala na ich wykorzystanie w celu rozwiązania określonych problemów.

Najpierw przyjrzyjmy się, jak działa protokół TCP.

TCP (Transmission Control Protocol) to protokół sieciowy, który zapewnia ustanowienie połączenia między hostami przed wymianą danych.

Jest to bardzo niezawodny protokół, ponieważ za każdym razem, gdy wysyła kolejny pakiet danych, musi sprawdzić, czy dotarł poprzedni pakiet.

Przesyłane pakiety są porządkowane, aw przypadku problemów z danym pakietem (gdy odbiorca nie potwierdza, że ​​pakiet dotarł) wysyłany jest ponownie. Z tego powodu szybkość transmisji jest stosunkowo niska, ponieważ uporządkowanie i ścisła kontrola transmisji danych zajmuje więcej czasu.

I tu pojawia się jego „brat antagonista” – protokół UDP. W przeciwieństwie do TCP, kolejność i status każdego pakietu nie jest dla niego tak ważna, po prostu wysyła dane bez potwierdzenia dostarczenia. Co więcej, nie nawiązuje połączenia i w żaden sposób nie zależy od jego statusu.

Jego zadaniem jest po prostu wysłanie danych na adres. Wynika z tego główna wada protokołu - niska niezawodność, ponieważ może on po prostu gubić fragmenty danych. Ponadto odbiorca musi być przygotowany na to, że dane mogą dotrzeć nie po kolei. Protokół ma również zaletę - wyższą szybkość transmisji, dzięki temu, że zakres zadań tego protokołu ogranicza się do przesyłania danych.

Istnieją również różnice w sposobie samych przesyłanych danych. W protokole TCP dane są przesyłane strumieniowo, co oznacza, że ​​dane nie mają granic. W przypadku UDP dane są przesyłane w postaci datagramów i mają granice, a strona odbierająca sprawdza ich integralność, ale tylko wtedy, gdy wiadomość zostanie pomyślnie odebrana.

Podsumowując:

TCP to niezawodny i dokładny protokół, który nie daje szans na utratę danych. Wiadomość zawsze zostanie dostarczona z maksymalną dokładnością lub nie zostanie dostarczona wcale. Strona odbierająca może nie mieć logiki, aby uporządkować dane, ponieważ dane przychodzące będą już uporządkowane. UDP nie jest tak niezawodny, ale jest szybszym protokołem przesyłania danych. Logika strony wysyłającej i odbierającej musi być uzupełniona kilkoma sztuczkami, aby pracować z tym protokołem. Ale spójrzmy na jego twórczość na przykładzie gry komputerowej lub mobilnej przez sieć. Możemy już nie dbać o to, co powinno być 5 sekund temu, a jeśli nie mają czasu, możemy pominąć kilka pakietów - gra zwalnia, ale można grać!

Java używa obiektów klas DatagramSocket i DatagramPacket do pracy z datagramami do transmisji przez UDP .

W celu wymiany danych nadawca i odbiorca tworzą gniazda typu datagram – obiekty klasy DatagramSocket . Klasa ma kilka konstruktorów, z których różnica polega na tym, gdzie zostanie podłączone utworzone gniazdo:

gniazdo datagramowe() Do dowolnego wolnego portu na komputerze lokalnym
DatagramSocket (port int) Do określonego portu na komputerze lokalnym
DatagramSocket(int port, adres InetAddress) Do wskazanego portu pod jednym z adresów maszyny lokalnej (addr)

Klasa zawiera wiele metod dostępu i zarządzania parametrami gniazda (przyjrzymy się im nieco później), a także metod odbierania i wysyłania datagramów:

wyślij (pakiet DatagramPacket) Wysyła datagramy spakowane w pakiety
odbierać (pakiet DatagramPacket) Odbiera datagramy spakowane w pakiety

DatagramPacket to klasa reprezentująca pakiet datagramów. Pakiety datagramowe służą do implementacji usługi bezpołączeniowego dostarczania pakietów. Każda wiadomość jest kierowana z jednej maszyny do drugiej wyłącznie na podstawie informacji zawartych w tym pakiecie. Wiele pakietów wysyłanych z jednego komputera do drugiego może być kierowanych w różny sposób i może docierać w dowolnej kolejności. Dostawa paczek nie jest gwarantowana.

Konstruktorzy:

DatagramPacket(bajt [] bufor, długość int) Tworzy DatagramPacket do akceptowania pakietów o długości length .
DatagramPacket(bajt [] buf, długość int, adres InetAddress, port int) Tworzy pakiet datagramu w celu wysłania pakietów o długości do określonego numeru portu na określonym hoście.
DatagramPacket(bajt [] buf, przesunięcie int, długość int) Tworzy DatagramPacket do akceptowania pakietów o długości length , biorąc pod uwagę przesunięcie w buforze.
DatagramPacket(bajt [] buf, przesunięcie int, długość int, adres InetAddress, port int) Konstruuje pakiet datagramu w celu wysłania pakietów o długości z przesunięciem offsetowym do określonego numeru portu na określonym hoście.
DatagramPacket(bajt [] buf, przesunięcie int, długość int, adres SocketAddress) Konstruuje pakiet datagramu w celu wysłania pakietów o długości z przesunięciem offsetowym do określonego numeru portu na określonym hoście.
DatagramPacket(bajt [] buf, długość int, adres SocketAddress) Tworzy pakiet datagramu w celu wysłania pakietów o długości do określonego numeru portu na określonym hoście.

Ze schematu UDP pamiętamy, że połączenie nie jest nawiązywane, a pakiety są wysyłane na szczęście, w oczekiwaniu, że odbiorca na nie czeka. Ale używając metody klasy DatagramSocket connect(InetAddress addr, int port) można nawiązać połączenie.

Nawiązywane jest jednokierunkowe połączenie z hostem na podstawie adresu i portu: w celu wysyłania lub odbierania datagramów. Takie połączenie można zakończyć za pomocą metody rozłącz() .

Spróbujmy napisać kod serwera (strony odbierającej) w oparciu o DatagramSocket :

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

class Recipient {

   public static void main(String[] args) {
       try {
           DatagramSocket ds = new DatagramSocket(1050);

           while (true) {
               DatagramPacket pack = new DatagramPacket(new byte[5], 5);
               ds.receive(pack);
               System.out.println(new String(pack.getData()));
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

Tworzymy obiekt DatagramSocket do nasłuchiwania na porcie 1050. Gdy otrzyma wiadomość, wypisuje ją na konsoli. Prześlemy słowo „Hello”, więc ograniczamy rozmiar bufora do pięciu bajtów.

Teraz tworzymy klasę nadawcy:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class Sender {
   private String host;
   private int port;

   Sender(String host, int port) {
       this.host = host;
       this.port = port;
   }

   private void sendMessage(String mes) {
       try {
           byte[] data = mes.getBytes();
           InetAddress address = InetAddress.getByName(host);
           DatagramPacket pack = new DatagramPacket(data, data.length, address, port);
           DatagramSocket ds = new DatagramSocket();
           ds.send(pack);
           ds.close();
       } catch (IOException e) {
           System.err.println(e);
       }
   }

   public static void main(String[] args) {
   Sender sender = new Sender("localhost", 1050);
   String message = "Hello";

   Timer timer = new Timer();
   timer.scheduleAtFixedRate(new TimerTask() {
       @Override
       public void run() {
           sender.sendMessage(message);
       }
   }, 1000, 1000);
}

}

W metodzie sendMessage tworzymy DatagramPacket , DatagramSocket i wysyłamy naszą wiadomość. Zauważ, że po wysłaniu DatagramSocket jest zamykany metodą close() .

W konsoli odbiorcy co sekundę widzimy przychodzącą wiadomość „Hello” wysłaną przez nadawcę. Oznacza to, że interakcja została nawiązana i wszystko działa poprawnie.