UDP w Javie

Unicast/Broadcast

Note

Java automaycznie wyśle pakiet broadcast jeśli poprosimy ją o wysłanie danych na adres broadcast.

Klasa z konfiguracją

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Config {
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 1024;

    public static final InetAddress MULTICAST_ADDRESS;
    public static final int MULTICAST_PORT = 9000;
    static {
        try{
            MULTICAST_ADDRESS = InetAddress.getByName("239.255.42.99");
        }catch (UnknownHostException e){
            throw new RuntimeException(e);
        }
    }


}

Server

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

public class UDPServer {

    public static void main(String[] args) throws Exception{

        //Otwarcie gniazda z okreslonym portem
        DatagramSocket datagramSocket = new DatagramSocket(Config.PORT);

        byte[] byteResponse = "OK".getBytes("utf8");

        while (true){

            DatagramPacket recievedPacket = new DatagramPacket( new byte[Config.BUFFER_SIZE], Config.BUFFER_SIZE);

            datagramSocket.receive(recievedPacket);

            int length = recievedPacket.getLength();
            String message = new String(recievedPacket.getData(), 0, length, "utf8");

            // Port i host, ktory wyslal nam zapytanie
            InetAddress address = recievedPacket.getAddress();
            int port = recievedPacket.getPort();

            System.out.println(message);
            Thread.sleep(1000); //oczekiwanie nie jest niezbedne

            DatagramPacket response = new DatagramPacket(byteResponse, byteResponse.length, address, port);

            datagramSocket.send(response);

        }


    }
}

Ważniejsze miejsca programu

Przygotowanie do odbierania połączeń UDP na zadanym portcie:

DatagramSocket datagramSocket = new DatagramSocket(Config.PORT);

Stworzenie pakietu który będzie odbierał dane:

DatagramPacket recievedPacket = new DatagramPacket( new byte[Config.BUFFER_SIZE], Config.BUFFER_SIZE);

Odebraie pakietu:

datagramSocket.receive(recievedPacket);

Tutaj jest kolejny detal implementacyjny w Javie: musimy przekształcić ciąg bajtów do instancji klasy string. Zakładamy że dane w pakiecie kodowane są za pomocą utf-9.

int length = reclievedPacket.getLength();
String message =
    new String(reclievedPacket.getData(), 0, length, "utf8");

Wysłanie odpowiedzi:

byte[] byteResponse = "OK".getBytes("utf8");
DatagramPacket response = new DatagramPacket(byteResponse, byteResponse.length, address, port);

Klient

Oprogramowanie klienta UDP jest również relatywnie proste.

Note

Nasz klient przyjmuje z linii komend dwa argumenty: adres na który wysyła wiadomość oraz treść wiadomości.

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

public class UDPClient {

    public static void main(String[] args) throws IOException {



        String message = "tekst";
        InetAddress serverAddress = InetAddress.getByName("194.29.174.217"); //adres serwera
        System.out.println(serverAddress);

        DatagramSocket socket = new DatagramSocket(); //Otwarcie gniazda
        byte[] stringContents = message.getBytes("utf8"); //Pobranie strumienia bajtow z wiadomosci

        DatagramPacket sentPacket = new DatagramPacket(stringContents, stringContents.length);
        sentPacket.setAddress(serverAddress);
        sentPacket.setPort(Config.PORT);
        socket.send(sentPacket);

        DatagramPacket receivedPacket = new DatagramPacket( new byte[Config.BUFFER_SIZE], Config.BUFFER_SIZE);
        socket.setSoTimeout(1010);

        try{
            socket.receive(receivedPacket);
            int length = receivedPacket.getLength();
            String receivedMessage = new String(receivedPacket.getData(), 0, length, "utf8");
            System.out.println("Serwer otrzymal wiadomosc: "+receivedMessage);
        }catch (SocketTimeoutException ste){
            System.out.println("Serwer nie odpowiedzial, byc moze dostal wiadomosc albo nie...");
        }

    }
}

Ważniejsze miejsca programu

Wybieramy do jakiego adresu wysyłamy informacje:

InetAddress serverAddress = InetAddress.getByName("194.29.174.217");

Wysłanie pakietu:

byte[] stringContents = message.getBytes("utf8"); //Pobranie strumienia bajtów z wiadomosci
DatagramPacket sentPacket = new DatagramPacket(stringContents, stringContents.length);
sentPacket.setAddress(serverAddress);
sentPacket.setPort(Config.PORT);
socket.send(sentPacket);

Odebranie odpowiedzi.

Tutaj musimy się na chwilę zatrzymać: w TCP moglibyśmy po prostu poczekać na odpowiedź od serwera, tutaj nie możemy tak zrobić — przecież odpowiedź od serwera może po prostu nie nadejść... należy więc powiedzieć socketowi: Poczekaj określony czas na odpowiedż, jeśli nie nadejdzie ona zgłoś wyjątek.

Ustawienie okresu oczekiwania na odpowiedź:

Note

Argument metody setSoTimeout to maksymalny czas oczekiwania na odpowiedź w milisekundach.

socket.setSoTimeout(1010);

Odebranie danych:

DatagramPacket receivedPacket = new DatagramPacket( new byte[Config.BUFFER_SIZE], Config.BUFFER_SIZE);
socket.setSoTimeout(1010);
try{
    socket.receive(receivedPacket);
    int length = receivedPacket.getLength();
    String receivedMessage = new String(receivedPacket.getData(), 0, length, "utf8");
    System.out.println("Serwer otrzymal wiadomosc: "+receivedMessage);
}
catch (SocketTimeoutException ste){
    System.out.println("Serwer nie odpowiedzial, byc moze dostal wiadomosc albo nie...");
}

Zadanie 1

(Unicast) W parach modyfikujemy powyższe przykłady:

Klient:

  1. Klient po uruchomieniu wysyła “Imię Nazwisko” (np. “Adam Szczurek”) użytkownika do serwera. Następnie odbiera potwierdzenie otrzymania wiadomości z serwera.
  2. Dalej klient odczytuje informację z konsoli: jeśli użytkownik wpisze “x”, to wysyła zapytanie do serwera o przesłanielisty wszystkich użytkowników; jeśli “z”, to program się kończy; jeśli użytkownik wpisze cokolwiek innego - jest to przesyłane jako kolejna nazwa użytkownika do serwera.

Serwer:

  1. Serwer dodaje kolenych użytkowników do tablicy: ArrayList<String> clients = new ArrayList<String>(); i przesyła do klienta tekst “Dodano klienta o nazwie: <nazwa>”.
  2. Jeśli serwer otrzyma “x”, to przesyła do klienta zawartość tablicy clients.

Zadanie 2

(Broadcast) Tworzymy program, który pozwala na sprawdzenie listy obecności na zajęciach (widocznej dla każdego użytkownika sieci).

W parach tworzymy jeden program, który wysyła i odbiera dane jednocześnie (2 wątki):

  1. Pierwszy wątek (odbierający): w pętli nasłuchuje na wybranym porcie (np. 9111) i jeśli coś otrzyma to wyświetla na ekran w formacie <Wiadomosc> <IP> <PORT>.
  2. Drugi wątek (wysyłający): w pętli co 1 s wysyła na adres Broadcast pakiet z informacją o loginie (tekst <Imie>).

Multicast

Serwer

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;

public class MulticastServer {

public static void main(String[] args) throws Exception{
        DatagramSocket s = new DatagramSocket();

        byte[] message = "Test".getBytes("utf8");

        while (true){
                DatagramPacket packet = new DatagramPacket(message, message.length);
                packet.setPort(9200);
                packet.setAddress(InetAddress.getByName("224.0.0.40"));
                s.send(packet);

                System.out.println("Wyslalem pakiet");
                Thread.sleep(1000); //w ms
        }

    }
}

Ważniejsze miejsca programu

Wysłanie pakietu na grupę multicast:

DatagramPacket packet = new DatagramPacket(message, message.length);
packet.setPort(9200);
packet.setAddress(InetAddress.getByName("224.0.0.40"));
s.send(packet);

Klient

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class MulticastClient {
    public static void main(String[] args) throws Exception{

        byte[] responseBytes = "ACK".getBytes();

        InetAddress group = InetAddress.getByName("224.0.0.40");
        MulticastSocket s = new MulticastSocket(9200);
        s.joinGroup(group);

        try{
                while(true)
                {
                DatagramPacket recv = new DatagramPacket(new byte[1024], 1024);
                s.receive(recv);
                String stringMsg = new String(recv.getData(), 0, recv.getLength(), "utf8");
                System.err.println("Got message: \"" + stringMsg);
                DatagramSocket responseSocket = new DatagramSocket();
                DatagramPacket response = new DatagramPacket(responseBytes, responseBytes.length);
                response.setAddress(recv.getAddress());
                response.setPort(recv.getPort());
                Thread.sleep(1000);
                responseSocket.send(response);
                }
        }finally {
            s.leaveGroup(group);
        }
    }
}

Ważniejsze miejsca programu

Stworzenie gniazda multicast — oraz dołączenie do grupy mulitcast

InetAddress group = InetAddress group = InetAddress.getByName("224.0.0.40");
MulticastSocket s = new MulticastSocket(9200);
s.joinGroup(group);

Zadanie 3

(Multicast) Przerabiamy program z zadania 2, aby działał na multicast.