Programowanie gniazd w Javie: samouczek

ten samouczek jest wprowadzeniem do programowania gniazd w Javie, zaczynając od prostego przykładu Klient-Serwer demonstrującego podstawowe funkcje We/Wy Java.zostaniesz zapoznany zarówno z oryginalnym java.io pakietem i NIO, nieblokującym We/Wy (java.nio) API wprowadzone w Javie 1.4. Na koniec, zobaczysz przykład, który demonstruje sieci Java zaimplementowane z Java 7 forward, W NIO.2.,

programowanie gniazd sprowadza się do dwóch systemów komunikujących się ze sobą. Ogólnie rzecz biorąc, komunikacja sieciowa występuje w dwóch odmianach: Transport Control Protocol (TCP) i User Datagram Protocol (UDP). TCP i UDP są używane do różnych celów i oba mają unikalne ograniczenia:

  • TCP jest stosunkowo prostym i niezawodnym protokołem, który umożliwia klientowi nawiązanie połączenia z serwerem, a oba systemy do komunikacji. W TCP każdy podmiot wie, że jego ładunki komunikacyjne zostały odebrane.,
  • UDP jest protokołem bezpołączeniowym i jest dobry w scenariuszach, w których niekoniecznie każdy pakiet dociera do miejsca docelowego, takich jak przesyłanie strumieniowe multimediów.

aby docenić różnicę między TCP i UDP, zastanów się, co by się stało, gdybyś streamował wideo ze swojej ulubionej strony internetowej i upuszczał klatki. Czy wolisz, aby Klient zwolnił Twój film, aby otrzymać brakujące klatki, czy wolisz, aby film nadal był odtwarzany? Protokoły przesyłania strumieniowego wideo zazwyczaj wykorzystują UDP., Ponieważ TCP gwarantuje dostarczanie, jest to protokół z wyboru dla HTTP, FTP, SMTP, POP3 i tak dalej.

w tym tutorialu przedstawiam wam programowanie socket w Javie. Przedstawiam serię przykładów klient-serwer, które demonstrują funkcje z oryginalnego frameworka I/o Javy, a następnie stopniowo przechodzą do korzystania z funkcji wprowadzonych w NIO.2.

stare gniazda Java

w implementacjach poprzedzających NIO Kod gniazda klienta Java TCP jest obsługiwany przez klasęjava.net.Socket., Poniższy kod otwiera połączenie z serwerem:

Socket socket = new Socket( server, port );

gdy nasza instancja socket jest podłączona do serwera, możemy zacząć uzyskiwać strumienie wejściowe i wyjściowe do serwera. Strumienie wejściowe są używane do odczytu danych z serwera, podczas gdy strumienie wyjściowe są używane do zapisu danych na serwer., Możemy wykonać następujące metody, aby uzyskać strumienie wejściowe i wyjściowe:

InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();

ponieważ są to zwykłe strumienie, te same strumienie, których używamy do odczytu i zapisu do pliku, możemy przekonwertować je do formy, która najlepiej służy naszemu przypadku użycia. Na przykład, możemy zawinąć OutputStream z PrintStream tak, że możemy łatwo pisać tekst za pomocą metod takich jak println()., W innym przykładzie możemy zawinąć InputStream z BufferedReader, za pomocą InputStreamReader, aby łatwo odczytać tekst za pomocą metod takich jak readLine().

Pobierz

kod źródłowy dla „Socket programming in Java: a tutorial.”Stworzony przez Stevena Hainesa dla JavaWorld.

przykład Java socket client

przejdźmy do krótkiego przykładu, który wykonuje HTTP GET na serwerze HTTP., HTTP jest bardziej wyrafinowany niż pozwala na to nasz przykład, ale możemy napisać kod klienta, aby obsłużyć najprostszy przypadek: zażądać zasobu z serwera, a serwer zwraca odpowiedź i zamyka strumień. Ten przypadek wymaga następujących kroków:

  1. Utwórz gniazdo na serwerze sieciowym nasłuchującym na porcie 80.
  2. uzyskaj PrintStream do serwera i wyślij żądanie GET PATH HTTP/1.0, gdzie PATH jest żądanym zasobem na serwerze., Na przykład, gdybyśmy chcieli otworzyć katalog główny strony internetowej, ścieżka byłaby /.
  3. uzyskajInputStream na serwer, zawiń go za pomocąBufferedReader i odczytaj odpowiedź linia po linii.

Listing 1 pokazuje kod źródłowy tego przykładu.

notowanie 1. SimpleSocketClientExample.java

Listing 1 akceptuje dwa argumenty wiersza poleceń: serwer do połączenia (zakładając, że łączymy się z serwerem na porcie 80) oraz zasób do pobrania., Tworzy on Socket, który wskazuje na serwer i jawnie określa port 80. Następnie wykonuje polecenie:

GET PATH HTTP/1.0

na przykład:

GET / HTTP/1.0

co się właśnie stało?

Po pobraniu strony internetowej z serwera www, takiego jak , klient HTTP używa serwerów DNS, aby znaleźć adres serwera: zaczyna się od zapytania serwera domeny najwyższego poziomu o com domeny, gdzie autorytatywny serwer nazwy domeny jest dla ., Następnie pyta, że serwer nazw domenowych dla adresu IP (lub adresów) dla . Następnie otwiera gniazdo do tego serwera na porcie 80. (Lub, jeśli chcesz zdefiniować inny port, możesz to zrobić, dodając dwukropek, a następnie numer portu, na przykład: :8080.) Na koniec klient HTTP wykonuje określoną metodę HTTP, na przykład GET, POST, PUT, DELETE, HEAD, lub OPTI/ONS. Każda metoda ma swoją składnię., Jak pokazano w powyższym fragmencie kodu, metoda GET wymaga ścieżki, po której następuje HTTP/version number I pustej linii. Gdybyśmy chcieli dodać nagłówki HTTP, moglibyśmy to zrobić przed wejściem do nowej linii.

w liście 1 pobraliśmyOutputStream I zawiniliśmy go wPrintStream, abyśmy mogli łatwiej wykonywać nasze polecenia tekstowe., Nasz kod uzyskał InputStream, zawinięty w InputStreamReader, który przekonwertował go na Reader, a następnie zawinięty w BufferedReader. Użyliśmy PrintStream aby wykonać naszą metodę GET, a następnie użyliśmy BufferedReader aby odczytać odpowiedź linia po linii, aż otrzymaliśmy odpowiedź null, wskazując, że gniazdo zostało zamknięte.,

teraz wykonaj tę klasę i podaj jej następujące argumenty:

java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /

powinieneś zobaczyć wyjście podobne do tego, co jest poniżej:

to wyjście pokazuje stronę testową na stronie JavaWorld. Odpowiedział, że mówi HTTP w wersji 1.1, a odpowiedź to 200 OK.

przykład Java socket server

omówiliśmy stronę klienta i na szczęście aspekt komunikacji po stronie serwera jest równie łatwy., Z uproszczonej perspektywy proces wygląda następująco:

  1. UtwórzServerSocket, określając port do nasłuchu.
  2. wywołajServerSocket’saccept() metodę nasłuchiwania skonfigurowanego portu dla połączenia klienta.
  3. gdy klient łączy się z serwerem, metoda accept() zwraca Socket za pomocą której serwer może komunikować się z klientem., Jest to ta sama klasa Socket, której użyliśmy dla naszego klienta, więc proces jest taki sam: uzyskać InputStream do odczytu z klienta i OutputStream do zapisu do klienta.
  4. Jeśli serwer musi być skalowalny, będziesz chciał przekazać Socket do innego wątku do przetworzenia, aby twój serwer mógł kontynuować nasłuchiwanie dodatkowych połączeń.
  5. wywołanie metodyServerSocketaccept() aby ponownie nasłuchać innego połączenia.,

jak już wkrótce się przekonacie, postępowanie NIO w tym scenariuszu byłoby nieco inne., Na razie możemy jednak bezpośrednio utworzyć ServerSocket przekazując mu port do nasłuchu (więcej o ServerSocketFactorys w następnej sekcji):

ServerSocket serverSocket = new ServerSocket( port );

i teraz możemy akceptować połączenia przychodzące za pomocą accept() metoda:

Socket socket = serverSocket.accept();// Handle the connection ...

programowanie wielowątkowe za pomocą gniazd Java

listing 2, poniżej, umieszcza cały kod serwera do tej pory razem w nieco bardziej solidnym przykładzie, który używa wątków do obsługi wielu żądań., Wyświetlany serwer jest serwerem echo, co oznacza, że odbija On każdą otrzymaną wiadomość.

o ile przykład z listingu 2 nie jest skomplikowany, to jednak przewiduje niektóre z tego, co pojawi się w następnej sekcji na NIO. Zwróć szczególną uwagę na ilość kodu wątkowego, który musimy napisać, aby zbudować serwer, który może obsługiwać wiele jednoczesnych żądań.

notowanie 2. SimpleSocketServer.java

w liście 2 tworzymy nową instancję SimpleSocketServer I uruchamiamy serwer., Jest to wymagane, ponieważSimpleSocketServer rozszerzaThread, aby utworzyć nowy wątek do obsługi blokowaniaaccept() wywołanie, które widzisz wread(). Metodarun() znajduje się w pętli, akceptując żądania klientów i tworzącRequestHandler wątki do przetworzenia żądania. Ponownie, jest to stosunkowo prosty kod, ale również wymaga sporej ilości programowania wątkowego.,

zauważ również, że RequestHandler obsługuje komunikację z klientem, podobnie jak kod z listy 1: owija OutputStream z PrintStream w celu ułatwienia łatwego zapisu i podobnie owija InputStream z BufferedReader dla łatwego odczytu. Jeśli chodzi o serwer, odczytuje linie od klienta i wysyła je z powrotem do klienta. Jeśli klient wyśle pustą linię, rozmowa zostanie zakończona iRequestHandler zamyka Gniazdo.

NIO i NIO.,2

dla wielu aplikacji, podstawowy model programowania gniazd Java, który właśnie zbadaliśmy, jest wystarczający. W przypadku aplikacji wymagających bardziej intensywnego wejścia/wyjścia lub asynchronicznego wejścia/wyjścia należy zapoznać się z nieblokującymi interfejsami API wprowadzonymi w Javie NIO i NIO.2.

pakiet JDK 1.4 NIO oferuje następujące kluczowe funkcje:

  • kanały są zaprojektowane do obsługi masowych transferów z jednego bufora NIO do drugiego.
  • bufory reprezentują sąsiadujący blok pamięci połączony prostym zestawem operacji.,
  • Non-Blocking Input / Output to zestaw klas, które wystawiają kanały na wspólne źródła wejścia / wyjścia, takie jak pliki i gniazda.

podczas programowania z NIO otwierasz kanał do miejsca docelowego, a następnie wczytujesz dane do bufora z miejsca docelowego, zapisujesz dane do bufora i wysyłasz je do miejsca docelowego., W niedługim czasie zajmiemy się konfiguracją gniazda i uzyskaniem kanału do niego, ale najpierw przyjrzyjmy się procesowi używania bufora:

  1. zapis danych do bufora
  2. wywołanie metody bufora flip() aby przygotować go do odczytu
  3. odczytanie danych z bufora
  4. wywołanie metody bufora clear() lub compact() metoda przygotowania go do odbioru większej ilości danych

Gdy dane są zapisane do bufora, bufor zna ilość danych do niego zapisanych., Zachowuje trzy właściwości, których znaczenia różnią się, jeśli bufor jest w trybie odczytu lub zapisu:

  • Position: w trybie zapisu początkowa pozycja wynosi 0 i utrzymuje bieżącą pozycję zapisywaną w buforze; po odwróceniu bufora, aby umieścić go w trybie odczytu, resetuje pozycję do 0 i utrzymuje bieżącą pozycję w buforze, z którego jest odczytywany,
  • Capacity: stały rozmiar bufora
  • Limit: w trybie zapisu limit określa, ile danych można zapisać do bufora; tryb odczytu, limit określa ile danych można odczytać z bufora.,

Java i / o demo: Serwer Echo z NIO.2

NIO.2, który został wprowadzony w JDK 7, rozszerza nieblokujące Biblioteki We/Wy Javy, aby dodać obsługę zadań systemu plików, takich jak pakiet java.nio.file I java.nio.file.Path I udostępnia nowe API systemu plików. Mając to na uwadze, napiszmy nowy serwer Echo używając NIO.2 ' s AsynchronousServerSocketChannel.

AsynchronousServerSocketChannel zapewnia nieblokujący kanał asynchroniczny dla gniazd nasłuchowych zorientowanych strumieniowo., Aby go użyć, najpierw wykonujemy jego statyczną metodę open() a następnie bind() do określonego portu. Następnie wykonamy metodę accept(), przekazując do niej klasę implementującą interfejsCompletionHandler. Najczęściej znajdziesz ten handler stworzony jako anonimowa Klasa wewnętrzna.

Listing 3 pokazuje kod źródłowy naszego nowego asynchronicznego serwera Echo.

notowanie 3. SimpleSocketServer.,java

w liście 3 najpierw tworzymy nowy AsynchronousServerSocketChannel a następnie bindujemy go do portu 5000:

 final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));

z tego AsynchronousServerSocketChannel wywołujemy accept() aby powiedzieć mu, aby zaczął nasłuchiwać połączeń, przekazując mu niestandardową instancję CompletionHandler. Gdy wywołujemy accept(), zwraca natychmiast., Zauważ, że ten przykład różni się od klasy ServerSocket w liście 1; podczas gdy metoda accept() jest zablokowana do czasu podłączenia do niej klienta, metoda AsynchronousServerSocketChannel accept() obsługuje ją za nas.

obsługa zakończenia

naszym następnym zadaniem jest utworzenie klasyCompletionHandlerI dostarczenie implementacji metodcompleted()Ifailed()., Metodacompleted() jest wywoływana, gdyAsynchronousServerSocketChannel otrzymuje połączenie od klienta i zawieraAsynchronousSocketChannel do klienta. Metoda completed() najpierw przyjmuje połączenie z AsynchronousServerSocketChannel I rozpoczyna komunikację z klientem. Pierwszą rzeczą, którą robi, jest wypisanie komunikatu „Hello”: buduje łańcuch znaków, konwertuje go na tablicę bajtów, a następnie przekazuje go do ByteBuffer.wrap(), aby zbudować ByteBuffer., MetodaByteBuffer może być następnie przekazanaAsynchronousSocketChannelwrite().

aby odczytać z klienta, tworzymy nowy ByteBuffer wywołując jego allocate(4096) (który tworzy bufor 4K), następnie wywołujemy AsynchronousSocketChannel’sread() metoda. read() zwraca Future<Integer> na którym możemy wywołać get() aby pobrać liczbę bajtów odczytanych z klienta., W tym przykładzie przekazujemy get() wartość limitu czasu 20 sekund: jeśli nie otrzymamy odpowiedzi w ciągu 20 sekund, to get() metoda rzuci TimeoutException. Naszą zasadą dla tego serwera echo jest to, że jeśli zachowamy 20 sekund ciszy, zakończymy rozmowę.

następnie sprawdzamy pozycję bufora, która będzie lokalizacją ostatniego bajtu otrzymanego od klienta. Jeśli klient wyśle pustą linię, to otrzymamy dwa bajty: powrót karetki i kanał linii., Kontrola zapewnia, że jeśli klient wyśle pustą linię, przyjmujemy ją jako wskaźnik, że klient zakończył rozmowę. Jeśli mamy sensowne dane, to wywołujemy metodę ByteBuffer’s flip() aby przygotować je do odczytu. Tworzymy tymczasową tablicę bajtów, która przechowuje liczbę bajtów odczytanych z klienta, a następnie wywołujemyByteBuffer’sget() aby załadować dane do tej tablicy bajtów. Na koniec konwertujemy tablicę bajtów na ciąg znaków, tworząc nową instancję String., Możemy echo linii z powrotem do klienta, konwertując łańcuch znaków do tablicy bajtów, przekazując go do metody ByteBuffer.wrap() I wywołując AsynchronousSocketChannel’s write(). Teraz clear() ByteBuffer, co oznacza, że repozycjonuje positiondo zera i umieszczaByteBuffer w trybie zapisu, a następnie odczytujemy następną linię od klienta.,

należy pamiętać tylko o tym, że metodamain(), która tworzy serwer, ustawia również 60 sekund timera, aby aplikacja działała. PonieważAsynchronousSocketChannel’saccept() metoda zwraca natychmiast, jeśli nie mamy Thread.sleep() wtedy nasza aplikacja zatrzyma się natychmiast.,

aby to przetestować, uruchom serwer i połącz się z nim za pomocą klienta telnet:

telnet localhost 5000

wyślij kilka ciągów do serwera, obserwuj, że są one odbite od ciebie, a następnie wyślij pustą linię, aby zakończyć rozmowę.

podsumowując

w tym artykule przedstawiłem dwa podejścia do programowania gniazd w Javie: tradycyjne podejście wprowadzone w Javie 1.0 oraz nowsze, nieblokujące NIO i NIO.2 podejścia wprowadzone odpowiednio w Javie 1.4 i Javie 7., Widziałeś kilka iteracji klienta gniazd Java i przykładu serwera gniazd Java, demonstrując zarówno użyteczność podstawowych We/Wy Java, jak i niektóre scenariusze, w których nieblokujące We/Wy ulepszają model programowania gniazd Java. Za pomocą nieblokujących We/Wy można zaprogramować aplikacje sieciowe Java do obsługi wielu jednoczesnych połączeń bez konieczności zarządzania wieloma kolekcjami wątków. Możesz również skorzystać z nowej skalowalności serwera wbudowanej w NIO i NIO.2.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *