[Java] Spraw kilka do wglądu i Wstęp: Nieblokujące sockety(buffory, idea Socketów)

Ostatnim czasu trochę zaniedbałem blogga, wiele postów jest zaczętych ale brak mi czasu/weny(tak to nazwijmy) by je dokończyć. Rozwojowo idę w programowanie sieci i wykorzystanie wielowątkowości w libGDX i być może niedługo ukaże się jakiś kolejny topik opisujący chociaż pobieżnie te zagadnienie. Mógłbym napisać także co nieco o tworzeniu UI(graficznego interfejsu) dla naszej gry. Bo mimo iż od początku tworzyłem własny system, to jednak na chwilę obecną przekonałem się do tego zawartego w libGDX scene2d.ui. Który na potrzeby sporej liczby produkcji powinien w zupełności wystarczeć. A że programować menusy nie lubię, to z niego korzystam i niedługo zastąpię nim moje własne gui; oczywiście trochę je usprawniając(uniwersalny styl, domyślna konfiguracja i dodatkowe bonusy). Także tyle wstępu, zapraszam do lektury, którą przygotowałem.



Nieblokującymi socketami zainteresowałem się dosyć niedawno, a było to związane z grą nad którą obecnie pracuję i która już nie bawem powinna ukazać się na urządzenia mobilne. Tworząc serwer do niej napotkałem problem z wykonaniem poczekalni w grze, bowiem chciałem aby gracze mogli się komunikować ze sobą za pomocą czatu ale wykonanego w jednym wątku. Sama java dostarcza począwszy od wersji 1.4  bibliotekę o nazwie .nio. Która służy do obsługi nieblokujących wejść i wyjść. W wersji 1.7 wyszła także nowsza wersja .nio2 ale z niej korzystać nie będziemy bo na androida zalecane jest pisanie w javie do 1.6.

W programowaniu nieblokującym zmienia się trochę koncepcja, bowiem nie czekamy tutaj na nasze dane tylko biegniemy cały czas naprzód. Dlatego potrzebujemy dla świeżo przybywających porcji danych jakiegoś akumulatora gdzie się będą te porcje gromadzić. Takim akumulatorem w naszym wypadku jest buffor, który przechowuje tyle informacji ile w danej chwili do niego trafiło. Oczywiście są różne rodzaje bufforów, o różnych pojemnościach i różnym przeznaczeniu. Podstawowym bufforem jest bufor bajtu. Ale są także bufory char, long, int, double czyli typów podstawowych. Oto ich lista:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

I gdy mamy interesujący nas bufor możemy wykorzystać do przechowywania w nim danych, powoli przybywających z obiegu pętli na obieg,
Gdy tworzymy buffor naszą podstawową czynnością powinno być określenie jego rozmiaru, które definiujemy bezpośrednio, lub pośrednio za pomocą interesującego nas obiektu:

ByteBuffer buffer1 = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);
ByteBuffer buffer3 = ByteBuffer.wrap(new String("hello").getBytes());

W ten sposób ustalamy pojemność naszego buffora.
Kwestia zapisu:

// Writing on a buffer
IntBuffer buffer = IntBuffer.allocate(10);
  for (int i=0; i < buffer.capacity(); i++) {
  buffer.put(i);
}

i odczytu:

// Reading from a buffer
buffer.position(0);
while (buffer.hasRemaining()) {
  int i = buffer.get();
  System.out.println("i="+i);
}

Teraz posiadając nasze akumulatory(bufory) możemy przejść do komunikacji sieciowej, choć warto dodać że pakiet nio jest wykorzystywana także do innych rzeczy np. do odczytywania/zapisywania danych z plików.
Nim jednak przejdziemy do pokazania kodu, warto porównać działanie tych dwóch przeciwnych podejść w ujęciu pracy programu:
Programowanie blokujące:


Programowanie nieblokujące:

Jak widzimy, nieblokujące programowanie komplikuje nam życie i o ile w tym przykładowym kodzie serwer/client możemy to z "pewną dozą łatwości" zrozumieć:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

public class HelloServer {

       /**
        * Tekst wysyłany do klienta
        */
       private static final String HELLO_REPLY = "Hello World!";
       public static void main(String[] args) {      
             // Deklarujemy buffor o rozmiarze buffora równym obiektowi String HELLO_REPLY
             ByteBuffer buffer = ByteBuffer.wrap(HELLO_REPLY.getBytes());
             /*
              * Tworzymy obiekt klasy ServerSocketChannel, w programowaniu nie blokującym działamy na strumieniach!
              */
        ServerSocketChannel ssc = null;
        try {
             /*
              * Otwieramy strumień serwerowego socketa i bindujemy z nim nasz adres sieciowy i konkretny port
              * I teraz NAJWAŻNIEJSZA opcja to wyłączenie blokowania! bez tego nasz program będzie czekał na dane i się blokował do
              * czasu ich nadejścia
              */
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(8765));
            ssc.configureBlocking(false);
            
           
             /*
              * W pętli tworzymy ciało naszego serwera, czyli przemiał informacji.
              */
            while (true) {
            /*
             * Tutaj co obieg akceptujemy kanał socketa, i jeśli okaże się że nic tam nie ma to znaczy że żadna informacja do
             * nas jeszcze nie dotarła, a jeśli dostaniemy dostęp do kanału, to znak że pojawiła się tam conajmniej 1 porcja
             * danych i musimy ją przetworzyć.
             */
                SocketChannel sc = ssc.accept();
                // if sc == null, that means there is no connection yet
                // do something else
                if (sc == null) {
                    // pretend to do something useful here
                    System.out.println("Doing something useful....");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else { // received an incoming connection
                    System.out.println("Received an incoming connection from " +
                        sc.socket().getRemoteSocketAddress());
                    printRequest(sc);
                    buffer.rewind();
                    sc.write(buffer);
                    sc.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ssc != null) {
                try {
                    ssc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
       }
       /**
        * Napisanie odpowiedzi
        * @param sc
        * @throws IOException
        */
       private static void printRequest(SocketChannel sc) throws IOException {
             /*
              * Do kanału bajtowego odczytu bindujemy nasz strumień
              */
        ReadableByteChannel rbc = Channels.newChannel(
            sc.socket().getInputStream());
        // To samo robimy z kanałem zapisu
        WritableByteChannel wbc = Channels.newChannel(System.out);
       // System.out.println(" ");
        //Tworzymy dynamiczny buffor dla odczytanych danych, które nastepnie od razu wyświetlamy w konsoli
        ByteBuffer b = ByteBuffer.allocate(8); // read 8 bytes
        while (rbc.read(b) != -1) {
            b.flip();
            while (b.hasRemaining()) {
                wbc.write(b);
            }
            b.clear();
        }
    }
}

---------------------------------------------------------------------------------------------------------

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class HelloClient {

        public static final String HELLO_REQUEST = "Hello!";
           public static void main(String[] args) {
               SocketChannel sc = null;
               try {
                   sc = SocketChannel.open();
                   sc.configureBlocking(false);
                   // make sure to call sc.connect() or else
                   // calling sc.finishConnect() will throw
                   // java.nio.channels.NoConnectionPendingException
                   sc.connect(new InetSocketAddress(8765));
                   // if the socket has connected, sc.finishConnect() should
                   // return false
                   while (!sc.finishConnect()) {
                       // pretend to do something useful here
                       System.out.println("Doing something useful...");
                       try {
                           Thread.sleep(3000);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
                   while(true){
                         System.out.println("Sending a request to HelloServer");
                         ByteBuffer buffer = ByteBuffer.wrap(HELLO_REQUEST.getBytes());
                         sc.write(buffer);
                         try {
                                        Thread.sleep(3000);
                                  } catch (InterruptedException e) {
                                        // TODO Auto-generated catch block
                                        e.printStackTrace();
                                  }
                   }
               } catch (IOException e) {
                   e.printStackTrace();
               } finally {
                   if (sc != null) {
                       try {
                           sc.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
               }
           }

}


Tak już sprawa z Selectorami nie wygląda zbyt intuicyjne. Teoretycznie jest ona logiczna. Ponieważ z naszej aplikacji mogą korzystać dziesiątki czy setki użytkowników.



to powstaje problem segregacji danych przychodzących z różnych źródeł. W tym celu stosuje się właśnie Selector, który nam to wszystko segreguje:

jednak powoduje on kilka problemów, które omówię w kolejnej części.

Bibliografia:
http://www.onjava.com/pub/a/onjava/2002/09/04/nio.html?page=1
http://tutorials.jenkov.com/java-nio/nio-vs-io.html