sik06STREAM.rs

Sieci Komputerowe

Przesyłanie plików. Streaming *

Streaming

Do przesyłania danych video najczęściej stosuje się protokoły warstwy aplikacji wspomagające transmisje danych video i dźwięku RTP

oraz RTSP

implementacje RTSP w javie można znaleźć np. tutaj

Co nie znaczy, że nie możemy przesyłać ramek video korzystając z innych protokołów, np. HTTP.

Kamera

Do utworzenia serwera video na naszym telefonie może posłużyć np. aplikacja IP WebCam

Z reguły rozwiązania takie pozwalają na przesyłanie video tylko w sieci lokalnej, ale nadal dają duże możliwości. Aplikacja ta wykorzystuje protokół HTTP do przesyłania Video.

Klient

Prosty klient korzystający z biblioteki OpenCV do odbioru ramek Video:

import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

public class Stream  {

    public static BufferedImage getBufferedImage(Frame frame) {
        OpenCVFrameConverter.ToIplImage toIplImage = new OpenCVFrameConverter.ToIplImage();
        Java2DFrameConverter frameConverter = new Java2DFrameConverter();
        return frameConverter.convert(frame);
    }

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

        FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber("http://192.167.1.100:8080/video");
        frameGrabber.start();

        BufferedImage image = getBufferedImage(frameGrabber.grabFrame());

        CanvasFrame canvasFrame = new CanvasFrame("Camera");
        canvasFrame.setCanvasSize(image.getWidth(), image.getHeight());

        int czy = 1000;
        while (canvasFrame.isVisible() && (image != null)) {
            image = getBufferedImage(frameGrabber.grabFrame());
            canvasFrame.showImage(image);
        }

        frameGrabber.stop();
        canvasFrame.dispose();
        System.exit(0);
    }
}

Utwórz nowy projekt Java typu Maven Project z danymi

<groupId>SIK</groupId>
<artifactId>SIKSTREAM</artifactId>

Zależności w Mavenowym pliku POM potrzebne do uruchomienia pliku przechwytującego ramki:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>SIK</groupId>
    <artifactId>SIKSTREAM</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>3.2.0-0</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv</artifactId>
            <version>3.4.0-1.4</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.4.1</version>
        </dependency>

    </dependencies>

</project>

Przesyłanie plików binarnych w sieci

Serwer odbiera plik jako strumień bitów:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;

public class FileSerwer {

    public static void main(String[] args) throws IOException {
        ServerSocket servsock = new ServerSocket(8888);
        while (true) {
            Socket sock = servsock.accept();

            System.out.println("Accepted connection : " + sock);

            (new Thread() {
                public void run() {
                    try {
                        InputStream is = sock.getInputStream();
                        byte[] mybytearray = new byte[1000000000];
                        String name = new BufferedReader(new InputStreamReader(is))
                                .readLine();
                        System.out.println(name);
                        OutputStream os = sock.getOutputStream();
                        String mes = "ok\n";
                        os.write(mes.getBytes(Charset.forName("UTF-8")));
                        FileOutputStream fos = new FileOutputStream(name + ".png");
                        BufferedOutputStream bos = new BufferedOutputStream(fos);

                        // read 4 bytes containing the file size
                        byte[] bSize = new byte[4];
                        int offset = 0;
                        while (offset < bSize.length) {
                            int bRead = is.read(bSize, offset, bSize.length - offset);
                            offset += bRead;
                        }
                        // Convert the 4 bytes to an int
                        int fileSize;
                        fileSize = (int) (bSize[0] & 0xff) << 24
                                | (int) (bSize[1] & 0xff) << 16
                                | (int) (bSize[2] & 0xff) << 8
                                | (int) (bSize[3] & 0xff);

                        // buffer to read from the socket
                        // 8k buffer is good enough
                        byte[] data = new byte[8 * 1024];

                        int bToRead;
                        while (fileSize > 0) {
                            // make sure not to read more bytes than filesize
                            if (fileSize > data.length) bToRead = data.length;
                            else bToRead = fileSize;
                            int bytesRead = is.read(data, 0, bToRead);
                            if (bytesRead > 0) {
                                bos.write(data, 0, bytesRead);
                                fileSize -= bytesRead;
                            }
                        }
                        bos.close();

                        is.close();
                        bos.close();
                        sock.close();
                    } catch (Exception e) {
                        System.out.println(e.getStackTrace());
                    }
                }
            }).start();
        }
    }
}

Klient przesyła plik bitowo:

import java.io.*;
import java.net.Socket;

public class FileClient {

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

        Socket sock = new Socket("127.0.0.1", 8888);
        sock.setTcpNoDelay(true);

        File myFile = new File("saved.png");
        byte[] mybytearray = new byte[(int) myFile.length()];
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myFile));

        int fSize = (int) myFile.length();
        // Send the file's size
        byte[] bSize = new byte[4];
        bSize[0] = (byte) ((fSize & 0xff000000) >> 24);
        bSize[1] = (byte) ((fSize & 0x00ff0000) >> 16);
        bSize[2] = (byte) ((fSize & 0x0000ff00) >> 8);
        bSize[3] = (byte) (fSize & 0x000000ff);
        // 4 bytes containing the file size
        os.write(bSize, 0, 4);

        int size = bis.read(mybytearray, 0, mybytearray.length);
        sock.getOutputStream().write(mybytearray, 0, size);
        sock.getOutputStream().flush();
        bis.close();
        os.close();
        sock.close();
    }
}

Zadanie (10 pkt)

Prowadzący uruchomi na swojej komórce serwer streamujący obraz. Twoim zadaniem jest napisać kod, który wykona Twoje zdjęcie oraz prześle je do prowadzącego na podany adres.

Proponuję wykorzystać metodę taką, żeby zasłonić kamerę (wykryć czarne piksele) i zachowywać ramkę następującą ileś po tym zasłonięciu.

Do wykrycia koloru piksela można wykorzystać:

Do zapisuj ramki na dysk:

Klient powinien przesłać do serwera podanego przez prowadzącego plik ze zdjęciem.

Klient powinien najpierw przesłać jako tekst Państwa nazwisko (ten kod trzeba dopisać do powyższego), na które serwer po odebraniu odpowie komunikatem "ok", następnie powinien być przesłany plik ze zdjęciem.

Proszę o przesłanie PUSTEGO pliku na adres:

Projekt

Projektem jest stworzenie serwera i klienta dla uproszczonej wersji gry zatacka .

W grze brać będzie udział 5 graczy. Plansza, na której odbywa się gra ma mieć wielkość 50x50 pól. Gracze pojawiają się na planszy w losowych miejscach. Każdy gracz okupuje na początku pojedyncze pole gry, następnie w każdej rundzie przesuwa się na sąsiednie pole zostawiając za sobą ślad. Gracz przegrywa jeżeli wejdzie w ścianę lub w ślad pozostawiony przez któregoś z graczy. Na początku serwer przesyła do wszystkich graczy ich numer ID oraz listę początkową ustawienia graczy.

Gracze odpowiadają kierunkiem w którym chcą wystartować. Gra odbywa się w turach. Tura trwa 100ms. Co 100 ms serwer przesyła graczom listę aktywnych graczy oraz postać planszy. Gracze odpowiadają serwerowi kierunkiem ruchu w tej turze. W przypadku braku odpowiedzi utrzymywany jest kierunek z rundy poprzedniej (za wyłączeniem początku gry, gdy gracz MUSI podać kierunek).

Plansza jest numerowana od pola (1,1) lewe górne pole do (50,50) prawe dolne pole.

Serwer każdą poprawną komendę potwierdza komunikatem "OK", każdą niepoprawną oznacza wysyłając "ERROR".

Protokół:

W momencie połączenia serwer przesyła do użytkownika komunikat (nie używamy polskich znaków!!)

S: CONNECT

Przed włączeniem się do gry każdy gracz musi się "zalogować" podając swój login (niepusty ciąg znaków)

C: LOGIN <login>

W momencie, gdy 5 osób włączy się do gry, gra zostaje uruchomiona a serwer przesyła do zawodników komunikat START

S: START <your_ID>

i zaraz po nim

S: PLAYERS <lista współrzędnych graczy>

Następnie każdy użytkownik wybiera początkowy kierunek ruchu w formie

C: BEGIN <move>

gdzie <move> to jedno z S (południe - rosnąco po y), N (północ - malejąco po y), E (wschód - rosnąco po x), W (zachód - malejąco po x)

Gra rozpoczyna się komunikatem

S: GAME

Co 100 ms serwer przesyła do aktywnych graczy stan planszy w postaci

S: BOARD <lista_pól>

gdzie lista pól to opis planszy od 1,1 do 50,50 wierszami, gdzie numer 0 oznacza wolne pole, a numery od 1 do 5 oznaczają ślady graczy o ID od 1 do 5.

W każdej turze serwer oczekuje od klienta, w którą stronę chce się udać

C: MOVE <move>

Gdzie <move> to jedno z (R (skręć w prawo), L (skręć w lewo), S (prosto)).

W przypadku braku komunikatu serwer utrzymuje poprzedni kierunek ruchu użytkownika.

W przypadku przegranej serwer przesyła do gracza komunikat

S: LOST <pozycja>

Gdzie pozycja to numer pozycji na której ukończył grę. W przypadku remisu przyznawane są pozycje ex-aequo. Od tego momentu serwer przestaje klientowi wysyłać stan planszy.

Gracz, który zostaje jako ostatni otrzymuje komunikat

S: WIN

Po zakończeniu jednej gry uruchamiana jest następna runda, przesyłany jest komunikat o kolejnej rundzie

S: ROUND <nr_rundy>

Po którym następuje komunikat

S: PLAYERS <lista współrzędnych graczy>

I serwer oczekuje na komunikat BEGIN od klientów.

Gdy rozegrane zostanie 5 gier, rozgrywka zostaje zakończona i wszyscy gracze otrzymują komunikat.

S: KONIEC <uszeregowane_loginy_graczy_od wygranego do ostatniego miejsca>

np. KONIEC s123456 s654321 s123987 ...

Ranking tworzony jest następująco: sumowane są pozycje graczy w poszczególnych grach, gracz o najmniejszej sumie wygrywa. W przypadku remisu wyższą pozycję uzyskuje gracz o niższym ID.

Następnie serwer kończy działanie.

Dodatkowe założenia:

* w przypadku, gdy klient nie odpowiada przez 3 sekundy jest wyrzucany z gry,
* w przypadku gdy klient wyśle powyżej 100 złych komunikatów pod rząd jest on wyrzucany z gry,

Państwa serwer i klient musi implementować powyższy protokół komunikacji. Dodatkowo mają Państwo za zadanie stworzyć drugi program kliencki, którego celem jest wywołanie błędu, oszukanie, lub spowodowanie błędu serwera. Wszelkie sposoby (dozwolone prawnie), w tym ataki na serwer są akceptowane. Program ten jednak musi się najpierw z sukcesem zalogować na serwerze (wysłać poprawnie przynajmniej komunikat LOGIN i odebrać komunikat OK).

Dobra rada: Programu powinny logować informacje, które sprawią że w przypadku sytuacji konfliktowych będziemy w stanie rozstrzygnąć kto popełnił błąd (serwer, czy aplikacja klienta).

Serwer musi zapisywać (logować) do pliku całą komunikacje pomiędzy nim a klientami.

Każdy komunikat musi być zakończony znakiem końca linii!

Program może być wykonany w dowolnym języku programowania ale musi wykorzystywać do komunikacji protokół TCP. Termin wykonania zadania: 15.06.2017.

*

Wykorzystano materiały z:

https://www.youtube.com/watch?v=3_GNE0t_VzY

http://www.rgagnon.com/javadetails/java-0542.html