→ Slide 1

Programowanie zorientowane sieciowo

→ Slide 2

Programowanie oparte na gniazdach (sockets)

  • komunikacja klient-serwera oparta na gniazdach z wykorzystaniem TCP, UDP lub innych protokołów
  • po obu stronach tworzymy gniazda, powiązane z adresami IP i portami
  • komunikacja dwukierunkowa za pomocą strumieni wejścia/wyjścia

Przetwarzanie adresów URL (HTTP)

  • praca z adresami URL, pobieranie danych z sieci, obsługa protokołów takich jak HTTP, HTTPS, FTP
  • żądania HTTP, obsługa odpowiedzi, nagłówków, itp.
→ Slide 3

TCP - połączeniowy, niezawodny, strumieniowy, kontrola kolejności

  • wykorzystuje protokół IP do odbierania i wysyłania danych, dzieląc przesyłane dane na fragmenty, jeśli jest to potrzebne
  • API, czat, transfer plików, HTTP, HTTPS, SMTP, itp.

UDP - bezpołączeniowy, szybki, datagramowy, brak retransmisji

  • szybsza transmisja bez dodatkowych narzutów, ale bez gwarancji dostarczenia, kolejności czy integralności danych
  • gry, telemetria, VoIP, wykrywanie usług, rozgłaszanie, itp.
→ Slide 4
  • IPv4: 192.168.1.10, IPv6: 2001:db8::1
  • Nazwy domenowe: example.com → rozwiązywane na adresy IP przez DNS
  • Porty znane: 0-1023 (HTTP 80, HTTPS 443) - zarezerwowane dla systemu i usług
  • Porty zarejestrowane: 1024-49151 - aplikacje użytkowe
  • Porty dynamiczne: 49152-65535 - tymczasowe, często używane przez klientów
  • localhost, 127.0.0.1 - adres hosta lokalnego
→ Slide 5
→ Slide 6
→ Slide 7

InetAddress - reprezentuje adres IP i nazwę hosta. Kluczowe metody:

  • getByName(String host) - rozwiązywanie DNS
  • getByAddress(byte[] addr) - tworzenie z surowych bajtów
  • getLocalHost() - adres hosta lokalnego
  • getHostAddress() i getHostName()
InetAddress local = InetAddress.getLocalHost();
System.out.println(local.getHostName());
System.out.println(local.getHostAddress());
 
InetAddress google = InetAddress.getByName("www.google.com");
System.out.println(google.getHostAddress());
→ Slide 8
  • serwer tworzy gniazdo (ServerSocket) na konkretnym porcie
  • serwer rozpoczyna nasłuch (accept())
  • klient, znając adres i port serwera, tworzy gniazdo (Socket) i łączy się z serwerem
  • po zaakceptowaniu połączenia przez serwer, obie strony mają otwarte gniazda do komunikacji
  • komunikacja odbywa się za pomocą strumieni wejścia/wyjścia
  • po zakończeniu komunikacji, gniazda powinny być zamknięte, aby zwolnić zasoby systemowe

→ Slide 9

ServerSocket tworzy gniazdo serwera TCP. Konstruktorzy:

  • ServerSocket(int port) - nasłuch na porcie (IOException jeśli port zajęty)
  • ServerSocket(int port, int backlog) - nasłuch z określoną liczbą oczekujących połączeń
  • ServerSocket(int port, int backlog, InetAddress bindAddr) - nasłuch z określonym adresem (gdy serwer ma wiele interfejsów sieciowych)

Kluczowe metody:

  • accept() - czeka na klienta i zwraca Socket (SocketTimeoutException jeśli timeout)
  • bind(SocketAddress endpoint) - wiąże z adresem i portem (jeśli nie podano w konstruktorze)
  • setSoTimeout(int timeout) - timeout dla accept()
  • getLocalPort() - numer portu lokalnego
  • close() - zamknięcie serwera (try-with-resources lub ręcznie)
→ Slide 10

Socket tworzy gniazdo klienta TCP

  • Socket(String host, int port) - łączy z serwerem na adresie host i porcie port
  • Socket(InetAddress host, int port) - łączy z serwerem na adresie host i porcie port
  • Socket() - tworzy gniazdo bez połączenia
  • Socket(String host, int port, InetAddress localAddr, int localPort) - łączy z serwerem i wiąże z lokalnym adresem i portem

Metody:

  • connect(SocketAddress host, int timeout) - łączy z serwerem (jeśli nie podano w konstruktorze)
  • getInputStream() / getOutputStream() - strumienie do komunikacji
  • getInetAddress(), getPort(), getLocalPort(), getRemoteSocketAddress() - informacje o połączeniu
  • close() - zamyka gniazdo
→ Slide 11
ServerSocket server = new ServerSocket(5000);
Socket client = server.accept();
 
// strumień wejściowy (tekstowy) do komunikacji z klientem
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line = in.readLine();
 
// strumień wyjściowy (tekstowy) do komunikacji z klientem
PrintWriter out = new PrintWriter(client.getOutputStream(), true); 
out.println("ECHO: " + line);
→ Slide 12
try (Socket socket = new Socket("127.0.0.1", 5000);
     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
     PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
 
    out.println("ping");
 
    String response = in.readLine();
    System.out.println("Odpowiedz z serwera: " + response);
}
→ Slide 13

UDP stosujemy, gdy nie jest potrzebna niezawodność i kolejność, a liczy się szybkość i niskie opóźnienia.

  • serwer i klient nie tworzą trwałego połączenia, a komunikacja odbywa się za pomocą pojedynczych pakietów (datagramów)
  • każdy pakiet jest niezależny, może być dostarczony w dowolnej kolejności, może zostać zgubiony lub zduplikowany

→ Slide 14

DatagramSocket tworzy gniazdo UDP:

  • DatagramSocket() - tworzy gniazdo i wiąże je z wolnym portem
  • DatagramSocket(int port) - wiąże z konkretnym portem
  • DatagramSocket(int port, InetAddress address) - wiąże z portem i adresem
  • Metody do komunikacji:
    send(DatagramPacket dp), receive(DatagramPacket dp)

DatagramPacket - pakiet danych UDP:

  • DatagramPacket(byte[] barr, int length) - utworzenie pakietu do odbioru danych
  • DatagramPacket(byte[] barr, int length, InetAddress address, int port) - utworzenie pakietu do wysłania danych do konkretnego adresu i portu
→ Slide 15
try (DatagramSocket sender = new DatagramSocket()) {
    byte[] data = "hello udp".getBytes(StandardCharsets.UTF_8);
    DatagramPacket packet = new DatagramPacket(
        data,
        data.length,
        InetAddress.getByName("127.0.0.1"),
        6000
    );
    sender.send(packet);
}
→ Slide 16
try (DatagramSocket receiver = new DatagramSocket(6000)) {
    byte[] buffer = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    receiver.receive(packet);
    String msg = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
    System.out.println("UDP: " + msg);
}
→ Slide 17
  • URI - identyfikator zasobu (często bez pobierania danych)
  • URL - lokalizacja zasobu i możliwość otwarcia połączenia
  • URLConnection - bazowy interfejs pracy na połączeniu
  • HttpURLConnection - klasyczny klient HTTP (przed Java 11)
URL url = new URL("https://example.com/api/users?page=1");
System.out.println(url.getProtocol()); // https
System.out.println(url.getHost());     // example.com
System.out.println(url.getPath());     // /api/users
System.out.println(url.getQuery());    // page=1
System.out.println(url.getPort());     // -1 (domyślny port)
System.out.println(url.getDefaultPort()); // 443
System.out.println(url.getFile());      // /api/users?page=1
System.out.println(url.getRef());       // null (część po #)
System.out.println(url.getAuthority()); // example.com
→ Slide 18
  • Metoda openConnection() na obiekcie URL zwraca instancję URLConnection, która pozwala na interakcję z zasobem sieciowym
  • Możemy użyć URLConnection do pobierania danych, odczytywania nagłówków, wysyłania danych (w przypadku HTTP) i innych operacji sieciowych
URL url = new URL("https://api.github.com");
URLConnection conn = url.openConnection();
try (InputStream in = conn.getInputStream()) {
    String content = new String(in.readAllBytes(), StandardCharsets.UTF_8);
    System.out.println(content);
}
→ Slide 19
  • getContent() - zwraca zawartość połączenia z URL
  • getContent(Class[] classes) - próbuje zwrócić zawartość połączenia jako jeden z typów podanych w parametrze classes
  • getContentEncoding() - zwraca zawartość nagłówka content-encoding
  • getContentLength() - zwraca zawartość nagłówka content-length
  • getContentType() - zwraca zawartość nagłówka content-type
  • getLastModified() - zwraca zawartość nagłówka last-modified
  • getExpiration() - zwraca zawartość nagłówka expires
  • getInputStream() - zwraca strumień wejściowy z URL (pozwala na czytanie)
  • getOutputStream() - zwraca strumień wyjściowy z URL (pozwala na wysyłanie)
  • getURL() - zwraca adres URL do którego podłączony jest obiekt klasy URLConnection
→ Slide 20

HttpURLConnection to podklasa URLConnection, która dodaje funkcje specyficzne dla protokołu HTTP, takie jak obsługa metod HTTP, nagłówków, kodów statusu i innych aspektów komunikacji HTTP.

Metody:

  • setRequestMethod(String method) - GET, POST, itp.
  • setRequestProperty(String key, String value) - nagłówki
  • getResponseCode() - kod statusu HTTP
  • getInputStream() - strumień odpowiedzi (dla 2xx)
  • getContent() - zawartość odpowiedzi
  • getErrorStream() - strumień błędu (dla 4xx/5xx)
  • disconnect() - zamyka połączenie
→ Slide 21

java.net.http to nowoczesny klient HTTP wprowadzony w Javie 11, który oferuje bardziej elastyczne i wydajne API do pracy z protokołem HTTP/1.1 i HTTP/2

  • HttpClient - główna klasa do tworzenia i konfigurowania klienta HTTP
  • HttpRequest - żądanie HTTP, pozwala na ustawienie metody, URI, nagłówków i ciała
  • HttpResponse<T> - odpowiedź HTTP, zawiera status, nagłówki i ciało odpowiedzi
  • Obsługa zarówno synchroniczna (send), jak i asynchroniczna (sendAsync)
  • metody wytwórcze (builder()) do łatwego tworzenia żądań i konfiguracji klienta
HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();
 
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.github.com"))
    .header("Accept", "application/json")
    .GET()
    .build();
 
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
→ Slide 22
  • Lekki serwer: com.sun.net.httpserver.HttpServer
  • Dobre do testów, demo i lokalnych API
  • Produkcyjnie: zwykle Spring Boot / Quarkus / Micronaut
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/hello", exchange -> {
    String body = "{\"message\":\"hello\"}";
    exchange.getResponseHeaders().add("Content-Type", "application/json; charset=utf-8");
    exchange.sendResponseHeaders(200, body.getBytes(StandardCharsets.UTF_8).length);
    try (OutputStream os = exchange.getResponseBody()) {
        os.write(body.getBytes(StandardCharsets.UTF_8));
    }
});
server.start();
→ Slide 23
→ Slide 24

Stwórz prosty komunikator, który pozwala na bezpośrednią wymianę wiadomości tekstowych między dwiema aplikacjami korzystając z protokołu TCP.

  • Jedna aplikacja będzie działać jako serwer, a druga jako klient.
  • Serwer po uruchomieniu nasłuchuje na określonym porcie i czeka na połączenie od klienta.
  • Klient po uruchomieniu łączy się z serwerem na określonym porcie.
  • Po nawiązaniu połączenia, obie aplikacje powinny umożliwiać wysyłanie i odbieranie wiadomości w czasie rzeczywistym.
  • Stwórz możliwie najprostszy interfejs graficzny umożliwiający wysyłanie wiadomości (pole tekstowe i przycisk „Wyślij”) oraz wyświetlanie wiadomości przychodzących, wysłanych i komunikatów diagnostycznych. GUI może byc oparte na dowolnej technologii (AWT, Swing, JavaFX)
  • Wystarczające jest aby aplikacja komunikowała się z inną instancją uruchomioną na tym samym komputerze (localhost).

Opcjonalnie:

  • Dodaj możliwość komunikacji między różnymi komputerami w sieci lokalnej. Wymaga podania w aplikacji klienta adresu IP (i ewentualnie portu) serwera i przycisku „Połącz”.

Przykładowe rozwiązanie: