przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
sk2
»
java
sk2:java
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
======= Obsługa sieci w Javie ======= Java posiada dwa API do obsługi sieci: ''java.io'' oraz ''java.nio''. Obsługa wielu gniazd, operacje blokujące / nieblokujące: * ''java.io'' pozwala tylko na blokującą obsługę gniazd i nie ma możliwości sprawdzenia czy możliwe jest wykonanie żądanej operacji bez blokowania (tzn. nie ma możliwości skorzystania z funkcji typu ''select''/''poll''). \\ W praktyce oznacza to, że dla każdego gniazda potrzebny jest osobny wątek. * ''java.nio'' oferuje mechanizm analogiczny w działaniu do funkcji ''poll''. Przy jego wykorzystaniu narzucana jest nieblokująca obsługa gniazd. Odczyt / zapis danych: * ''java.io'' używa strumieni - wysyłanie i odbieranie realizuje się przez obiekty z klas ''InputStream'' i ''OutputStream'' * ''java.nio'' udostępnia metody wysyłające dane z i odbierające dane do buforów – obiektowo opakowanych tablic bajtów. ''java.io'' jest prostszym API, które często utrudnia bądź nawet uniemożliwia napisanie wydajnego kodu. Dłuższe porównanie IO/NIO: http://tutorials.jenkov.com/java-nio/nio-vs-io.html ====== java.io ====== Java I/O – proste i ładne blokujące opakowanie obiektowe gniazd. ==== Klient TCP ==== <code java> //Socket sock = new Socket(); //sock.connect(new InetSocketAddress("example.com", 13)); Socket sock = new Socket("example.com", 13); InputStream is = sock.getInputStream(); byte[] bytearr = new byte[16]; while(true){ int len = is.read(bytearr); if(len == -1) break; System.out.write(bytearr, 0, len); } sock.close(); </code> ==== Serwer TCP ==== <code java> ServerSocket ssock = new ServerSocket(1313); Socket sock = ssock.accept(); ssock.close(); String s = new Date().toLocaleString() + '\n'; sock.getOutputStream().write(s.getBytes()); sock.close(); </code> ==== Klient UDP ==== <code java> DatagramSocket sock = new DatagramSocket(); InetSocketAddress addr = new InetSocketAddress("example.com", 13); DatagramPacket outpacket = new DatagramPacket(new byte [0], 0, addr); sock.send(outpacket); DatagramPacket inpacket = new DatagramPacket(new byte[1024], 1024); sock.receive(inpacket); System.out.write(inpacket.getData(), 0, inpacket.getLength()); sock.close(); </code> ==== Zadanie 1. ==== Napisz serwer czatu z użyciem ''java.io''. (Jako klienta użyj programu ''netcat'' lub ''socat''.) <html><small></html> [[https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html|synchronized (czyli zamki)]] \\ [[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/package-summary.html|java.util.concurrent]] \\ nowy wątek, używając lambdy: <code java> new Thread(() -> { // Kod uruchamiany wewnątrz wątku }).start(); </code> <html></small></html> ====== java.nio ====== Java New I/O – wydajne opakowanie obiektowe gniazd.\\ http://tutorials.jenkov.com/java-nio/index.html ==== Klasy obsługujące gniazda (kanały) ==== Ważne klasy (sieciowe): * ''[[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/channels/SocketChannel.html|SocketChannel]]'' – gniazdo TCP (//connect//, klient) * ''[[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/channels/ServerSocketChannel.html|ServerSocketChannel]]'' – gniazdo TCP (//listen//, serwer) * ''[[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/channels/DatagramChannel.html|DatagramChannel]]'' – gniazdo UDP * ''[[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/channels/Selector.html|Selector]]'' – multiplekser Obiekty z tych klas są tworzone metodą statyczną ''open()'', np: <code java> ServerSocketChannel ssc = ServerSocketChannel.open() </code> Kanały wprawdzie pozwalają na pracę w trybie blokującym i nieblokującym, ale przy użycie selektora wymusza pracę w trybie nieblokującym. ==== Praca z selektorem ==== Aby używać kanału NIO z selektorem, należy: - stworzyć selektor i kanały gniazd - przestawić kanały w tryb nieblokujący: ''configureBlocking(false)'' - dodać kanały gniazd do selektora – metoda ''register()'' na kanale - w pętli - wykonywać metodę ''select()'' selektora - metodą ''selectedKeys()'' pobierać kolekcję ''SelectionKey'' opisujących zdarzenia - obsłużyć zdarzenia - wyczyścić kolekcję kluczy <code java> /* 1. */ Selector selector = Selector.open(); /* 1. */ DatagramChannel channel = DatagramChannel.open(); /* 1. */ channel.bind(new InetSocketAddress(12345)); /* 2. */ channel.configureBlocking(false); /* 3. */ selector.register(channel, SelectionKey.OP_READ); /* 4. */ … /* 4.a. */ selector.select(); /* 4.b. */ for(SelectionKey key : selector.selectedKeys()) /* 4.c. */ … /* 4.d. */ selector.selectedKeys().clear(); </code> ==== Bufory ==== NIO używa dedykowanych klas buforów – np. ''[[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/ByteBuffer.html|ByteBuffer]]''. Klasy te służą opakowaniu tablic (np. tablicy bajtów) w sposób nie ograniczający wydajności. Bufor można tworzyć metodami statycznymi, np. ''ByteBuffer.allocate()'' i ''ByteBuffer.wrap()''. Każdy bufor ma stałą pojemność. Poza pojemnością każdy bufor ma kursor (''position'') i limit (''limit'')((oraz znacznik, ''mark'')). Pisanie i czytanie odbywa się zawsze od kursora i przesuwa kursor o ilość przeczytanych/zapisanych bajtów.\\ Do zmiany pozycji i limitu służą np. ''flip'', ''clear''. <code java> ByteBuffer buffer = ByteBuffer.allocate(16); // |................| // ^pos(ition) ^limit int bytesRead = channel.read(buffer); // |alamakota.......| assert bytesRead == 9; // ^pos ^limit buffer.flip(); // |alamakota.......| // ^pos ^limit short useless = buffer.getShort(); // |alamakota.......| assert useless == 0x616c; // ^pos ^limit Charset utf8 = Charset.forName("UTF-8"); // vlimit String str = utf8.decode(buffer).toString(); // |alamakota.......| assert str.equals(new String("amakota")); // ^pos buffer.clear(); // |................| // ^pos ^limit </code> ==== SelectionKey ==== W selektorze jeden kanał gniazdo może być zarejestrowane co najwyżej raz do oczekiwania na podane zdarzenia przy pomocy metody ''register()''. Metoda ta zwraca //klucz// – obiekt z klasy ''SelectionKey''. Ten sam obiekt po wywołaniu na selektorze metody ''select()'' jest kopiowany do ''selectedKeys()'' selektora. Klucz można też wydobyć wywołując na kanale metodę ''keyFor(selector)''.\\ Aby zmienić listę oczekiwanych zdarzeń, należy wykonać na kluczu metodę ''interestOps()'' z nową listą zdarzeń. Po wywołaniu metody ''select()'' selektora każdy klucz ma ustawiane ''readyOps()'' - listę gotowych operacji. Dla ułatwienia klasa ''SelectionKey'' ma też metody ''isReadable()'', ''isWriteable()'', ''isAcceptable()'', … Do usunięcia klucza z selektora należy użyć metody ''cancel()'' na kluczu (lub zamknąć kanał metodą ''close()''). <code java> SelectionKey key = selector.register(channel, SelectionKey.OP_READ); selector.select(); assert key == selector.selectedKeys().iterator().next(); if(key.isValid() && key.isReadable()) … key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); </code> Każdy klucz może mieć dołączony jeden załącznik, albo podany przy rejestracji kanału w selektorze, albo dołączony metodą ''attach(object)''. Można go później pobrać metodą ''attachment()''. <code java> MyClass myObj = … selector.register(myObj.myChannel(), SelectionKey.OP_READ, myObj); selector.select(); SelectionKey key = selector.selectedKeys().iterator().next(); assert key.attachment() instanceof MyClass; ((MyClass)key.attachment()).myFunction(key); </code> ==== "klient tcp" ==== java.nio nie pozwala na współpracę ze standardowym wejściem. ++++Przykład obsługujący jedno gniazdo | <code java NioCli.java> package sk2; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; public class NioCli { private SocketChannel sock; // gniazdo w NIO private Selector sel; // selektor – opakowuje mechanizm typu 'select' private SelectionKey sockKey; private ByteBuffer bb = ByteBuffer.allocate(8); // bufor – odpowiednio opakowana tablica bajtów public NioCli(String[] args) throws Throwable{ // Selektory, gniazda, etc. są tworzone przez metody statyczne, używające dostarczanej // przez VM implementacji klasy SelectorProvider sel = Selector.open(); sock = SocketChannel.open(new InetSocketAddress(args[0], Integer.parseInt(args[1]))); sock.configureBlocking(false); // Używanie Selectora wymaga nieblokującego I/O sockKey = sock.register(sel, SelectionKey.OP_READ); // Tak każe się czekać na zdarzenie } private void select() throws Throwable{ sel.select(); // oczekiwanie na zdarzenie // selectedKeys() zawiera listę kluczy dla których można wykonać żądaną operację // i ustawia im dostępne zdarzenia (readyOps, isReadable, is…) assert sel.selectedKeys().size() == 1; assert sel.selectedKeys().iterator().next() == sockKey; bb.clear(); // przygotowanie bufora do pracy int count = sock.read(bb); if(count == -1) { // -1 oznacza EOF sockKey.cancel(); // cancel usuwa klucz z selektora return; } System.out.write(bb.array(), 0, bb.position()); sel.selectedKeys().clear(); // klucze są w zbiorze do momentu usunięcia } public static void main(String[] args) throws Throwable{ NioCli cli = new NioCli(args); while(cli.sockKey.isValid()) cli.select(); } } </code> ++++ ==== Serwer czatu ==== ++++ Przykład serwera czatu | <code java NioServ.java> package sk2; import java.net.InetSocketAddress; 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.HashSet; public class NioServ { ServerSocketChannel ssc; Selector sel; HashSet<SocketChannel> clients = new HashSet<SocketChannel>(); ByteBuffer commonReadBuffer = ByteBuffer.allocate(256); public NioServ(int port) throws Throwable { sel = Selector.open(); ssc = ServerSocketChannel.open(); ssc.bind(new InetSocketAddress(port)); ssc.configureBlocking(false); ssc.register(sel, SelectionKey.OP_ACCEPT); } private void loop() throws Throwable { while(true){ select(); } } private void select() throws Throwable { sel.select(); for(SelectionKey key : sel.selectedKeys()){ if(key.isAcceptable()){ accept(); } if(key.isReadable()){ SocketChannel sc = (SocketChannel) key.channel(); read(key, sc); } } sel.selectedKeys().clear(); } private void accept() throws Throwable { SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(sel, SelectionKey.OP_READ); sendAll("Connected:" + sc.getRemoteAddress().toString() + "\n"); clients.add(sc); } private void read(SelectionKey key, SocketChannel sc) throws Throwable { commonReadBuffer.clear(); commonReadBuffer.put(sc.getRemoteAddress().toString().getBytes()); commonReadBuffer.putChar(':'); commonReadBuffer.putChar(' '); int count = sc.read(commonReadBuffer); if(count == 0) return; if(count == -1){ clients.remove(sc); sendAll("Disconnected:" + sc.getRemoteAddress().toString() + "\n"); key.cancel(); sc.close(); return; } sendAll(commonReadBuffer); } private void sendAll(String msg) throws Throwable { ByteBuffer bb = ByteBuffer.wrap(msg.getBytes()); bb.position(bb.limit()); sendAll(bb); } private void sendAll(ByteBuffer bb) throws Throwable { bb.flip(); for(SocketChannel sc : clients){ sc.write(bb); bb.rewind(); } } public static void main(String[] args) throws Throwable{ NioServ s = new NioServ(Integer.parseInt(args[0])); s.loop(); } } </code> ++++ ==== Zadanie 2. ==== Pobierz kod prostego serwera key-value store: {{:sk2:simplekv.java|SimpleKV.java}}. <html><small> </html> ([[https://en.wikipedia.org/wiki/Key-value_database]]) <html></small></html> \\ Przeczytaj kod, połącz się programem ''netcat'' / ''socat'' i przetestuj działanie. \\ Następnie dodaj do programu brakujące: * komunikaty diagnostyczne – pojawił się nowy klient, klient się rozłączył, zmieniono wartość * obsługę błędów – tzn. usuń ''throws Throwable'' i dodaj kod łapiący i obsługujący wyjątki * buforowanie wejścia – obsłuż sytuację, w której read zwróci tylko część linii \\ <html><small> </html>Testuj używając jako klienta: \\ ''socat tcp://host//://port//,ignoreeof **-** '' \\ Wpisanie EOF (ctrl+d) spowoduje wysłanie dotychczas wpisanych danych<html></small></html> * buforowanie wyjścia – obsłuż sytuację, w której write zapisze tylko część żądanych danych
sk2/java.1606156064.txt.gz
· ostatnio zmienione: 2020/11/23 19:27 (edycja zewnętrzna)
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry