Client <-> Server - jak to wykonać?

Client <-> Server - jak to wykonać?
RE
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:8
0

Witam.
Mam już dosyć spore doświadczenie jeśli idzie o pisanie w javie, ale jak do teraz nie wykonywałem żadnych projektów działających na zasadzie komunikacji client <-> server.

Planuję stworzyć grę 2d działającą tylko online, klient będzie tylko wyświetlał grafikę, odtwarzał dźwięki i zarządzał urządzeniami wejścia/wyjścia, a serwer będzie zajmował się całą mechaniką gry.
Obydwa elementy potrafię stworzyć, ale z powodu braku doświadczenia nie jestem w stanie ich połączyć.
Z tego co wiem, istnieje technika polegająca na przesyłaniu Object'ów i chciałbym aby moja gra działała właśnie na takiej zasadzie.

Dlatego mam prośbę o wytłumaczenie, podanie linków do tutoriali, czy choćby podanie nazw zagadnień jakie muszę opanować.

Z góry dziękuję za pomoc :)

edytowany 1x, ostatnio: regzand
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
2

Po pierwsze musisz zaprojektować swój własny protokół komunikacji, czyli format, w jakim dane będą szły przez sieć.
Coś w stylu:

Kopiuj
uint8 - typ pakietu
uint16 - rozmiar pakietu

Jeżeli typ pakietu to `LOG_IN` (`1`), wtedy dalsza część to:
   uint16 - przydzielone ID gracza

Jeżeli typ pakietu to `ENTITY_MOVE` (`2`), wtedy dalsza część to:
   uint16 - ID jednostki w grze
   int32 - nowa pozycja X
   int32 - nowa pozycja Y

Jeżeli typ pakietu to `ENTITY_DESTROYED` (`3`), wtedy dalsza część to:
   uint16 - ID jednostki w grze

(i tak dalej)

Klient otrzymując taki pakiet musi go jedynie przeparsować i nanieść poprawki na swoją aktualną wersję mapy etc., etc.

  • protokół musi działać w obie strony, więc w przypadku, gdy to gracz wysyła coś do serwera, do nagłówka protokołu dojdzie dodatkowo pole uint16 - ID gracza.

Wszystko wystarczy ładnie opakować w obiekt i bez większych przeszkód będzie można to wysyłać w obie strony, a jak już zrealizować samo wysyłanie, poszukaj w internecie.


kult
  • Rejestracja:prawie 22 lata
  • Ostatnio:ponad rok
1

Albo zamiast odkrywać koło na nowo skorzystać z czegoś co już jest, np lekkiego protobuf: https://code.google.com/p/protobuf/
@regzand skoro piszesz, że masz doświadczenie w javie, to spokojnie poradzisz sobie z biblioteką którą Ci podałem, na stronie jest dokładny opis + przykłady.
https://developers.google.com/protocol-buffers/docs/javatutorial

edytowany 2x, ostatnio: kult
RE
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:8
0

@Patryk27 Dzięki, ale to rozumiem :)

@kult Właśnie o to mi chodziło, dzięki. Już na to patrzę :)

RE
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:8
0

Poczytałem sobie o tym i jestem pewny ze mi się przyda :D

Tylko problem polega na tym że ta biblioteka tylko serializuje dane, czyli potrzebuję jeszcze systemu nasłuchu/wysyłania pakietów... Mógł by ktoś podać link to jakiegoś tutka?

airborn
  • Rejestracja:ponad 15 lat
  • Ostatnio:prawie 7 lat
  • Postów:274
1
RE
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:8
0

@airborn Dzięki, poczytam o tym.

Może ma ktoś jeszcze jakieś tutki/biblioteki? Wolał bym jakieś w miarę proste rozwiązania na początek.

kult
  • Rejestracja:prawie 22 lata
  • Ostatnio:ponad rok
1

@regzand przecież dalej to zwykła obsługa socketów... możesz to zrobić z wykorzystaniem wątków lub z nio ;) kawałek kodu aby dokumentacja była bardziej lekka:

Kopiuj
package core;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.*;
import device.*;
import configuration.*;

public class YourServer {
    
    public final int MAX_DEVICES = 100000;
    
    private InetAddress addr;
    private int port;
    private Selector selector;
    public Map<SocketChannel,List<byte[]>> dataMap;
    private Map<SocketChannel, Device> deviceMap;
    private DeviceManager deviceManager;
    private ExecutorService executorService;
    public Configuration configuration;
    
    public int clientsCounter = 0;

    public YourServer(InetAddress addr, int port) throws IOException {
        this.addr = addr;
        this.port = port;
        dataMap = new HashMap<SocketChannel,List<byte[]>>();
        deviceMap = new HashMap<SocketChannel, Device>();
        this.deviceManager = new DeviceManager(this);
        this.configuration = new Configuration();
        try{
            this.configuration.loadConfiguration();
        }
        catch( Exception ignored ) {
            
        }
        
        //ustawiamy pule 32 wątków
        executorService = Executors.newFixedThreadPool(32);
        startServer();
        executorService.shutdown();
    }

    private void startServer() throws IOException {
        this.selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
        serverChannel.socket().bind(listenAddr);
        serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);

        while (true) {
            this.selector.select();

            Iterator keys = this.selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = (SelectionKey) keys.next();

                keys.remove();

                if (! key.isValid()) {
                    continue;
                }
                
                if (key.isAcceptable()) {
                    this.accept(key);
                }
                else if (key.isReadable()) {
                    this.read(key);
                }
                else if (key.isWritable()) {
                    this.write(key);
                }
            }
        }
    }

    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel channel = serverChannel.accept();
        channel.configureBlocking(false);

        Socket socket = channel.socket();
        SocketAddress remoteAddr = socket.getRemoteSocketAddress();
        log(clientsCounter++ +" Connected to: " + remoteAddr);

        dataMap.put(channel, new ArrayList<byte[]>());
        channel.register(this.selector, SelectionKey.OP_READ);
        
        deviceMap.put(channel, new Device(channel));
        //jesli operujemy na metodach klasy Device, powinnismy to robic jako nowe zadanie egzekutora  
        deviceManager.sendWelcome(executorService, deviceMap.get(channel));
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();

        ByteBuffer buffer = ByteBuffer.allocate(8192);
        int numRead = -1;
        try {
            numRead = channel.read(buffer);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        if (numRead == -1) {
            this.dataMap.remove(channel);
            Socket socket = channel.socket();
            SocketAddress remoteAddr = socket.getRemoteSocketAddress();
            log("Connection closed by client: " + remoteAddr);
            channel.close();
            key.cancel();
            return;
        }

        byte[] data = new byte[numRead];
        System.arraycopy(buffer.array(), 0, data, 0, numRead);
        deviceManager.task(executorService, deviceMap.get(key.channel()), data);
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        List<byte[]> pendingData = this.dataMap.get(channel);
        Iterator<byte[]> items = pendingData.iterator();
        while (items.hasNext()) {
            byte[] item = items.next();
            items.remove();
            channel.write(ByteBuffer.wrap(item));
        }
        key.interestOps(SelectionKey.OP_READ);
    }
    
    private void addDevice(SelectionKey key) {
	
    }

    private void doEcho(SelectionKey key, byte[] data) {
        SocketChannel channel = (SocketChannel) key.channel();
        List<byte[]> pendingData = this.dataMap.get(channel);
        pendingData.add(data);
        key.interestOps(SelectionKey.OP_WRITE);
    }

    private static void log(String s) {
        System.out.println(s);
    }

    public static void main(String[] args) throws Exception {
	new YourServer(null, 1234);
    }

}

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 14 godzin
1

A ja polecam zajrzeć na: http://akka.io/

Jest to mechanizm aktorów ( http://en.wikipedia.org/wiki/Actor_model ), którzy przesyłają pomiędzy sobą wiadomości ( http://en.wikipedia.org/wiki/Message_passing ). Dla uproszczenia można na to spojrzeć jako na serializowalne obiekty (wiadomości) i wątki nasłuchujące na te obiekty i je wysyłające (aktorzy).

Na stronie Akki jest dużo przykładów w języku Scala. Sama Akka jest napisana w języku Scala, języku który bardzo dobrze integruje się z językiem Java i można w Scali pisać biblioteki, które można potem używać z poziomu języka Java (sama Akka jest przykładem takiej biblioteki). Kod Scalowy kompiluje się do Javowego bajtkodu i wymaga tylko dołączenia JARa z biblioteką standardową Scali do projektu.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
KK
  • Rejestracja:około 12 lat
  • Ostatnio:około 11 lat
  • Postów:135
1

Nawet na stronie Oracle jest opisana komunikacja po TCP
http://docs.oracle.com/javase/tutorial/networking/sockets/

RE
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:8
0

Dzięki wszystkim :D
Teraz mam dobry punkt zaczepienia z którego mogę wystartować. Jeszcze raz THX

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)