Pisanie kodu, który wykonuje się na określonym urządzeniu, jest bardzo satysfakcjonujące. Ale pisanie kodu, który jest wykonywany na kilku komunikujących się ze sobą urządzeniach, jest po prostu afirmacją życia. W tym artykule dowiesz się, jak łączyć się i wymieniać wiadomości przez sieć za pomocą protokołu kontroli transmisji (TCP).
W tym artykule skonfigurujesz aplikację, która połączy twój komputer z samym sobą i, w zasadzie, doprowadzi go do szaleństwa - porozmawia ze sobą. Dowiesz się również, jaka jest różnica między dwoma najczęściej używanymi strumieniami do pracy w sieci w Javie i jak one działają.
Strumienie danych i obiektów
Zanim zagłębimy się w kod, należy rozróżnić dwa strumienie użyte w artykule.
Strumienie danych
Strumienie danych przetwarzają prymitywne typy i ciągi danych. Dane przesyłane przez strumienie danych muszą być ręcznie serializowane i deserializowane, co utrudnia przesyłanie złożonych danych. Jednak strumienie danych mogą komunikować się z serwerami i klientami napisanymi w innych językach niż Java. Strumienie surowe są pod tym względem podobne do strumieni danych, ale strumienie danych zapewniają formatowanie danych w sposób niezależny od platformy, co jest korzystne, ponieważ obie strony będą w stanie odczytać przesłane dane.
Strumienie obiektów
Strumienie obiektów przetwarzają prymitywne typy danych i obiekty, które implementują
Możliwość serializacji
berło. Dane przesyłane strumieniami obiektów są automatycznie serializowane i deserializowane, co ułatwia przesyłanie złożonych danych. Jednak strumienie obiektów mogą komunikować się tylko z serwerami i klientami napisanymi w Javie. Także,
ObjectOutputStream
po inicjalizacji wysyła nagłówek do
Strumień wejściowy
drugiej strony, która po inicjalizacji blokuje wykonanie do momentu odebrania nagłówka.
Kroki
Krok 1. Stwórz klasę
Utwórz klasę i nazwij ją tak, jak chcesz. W tym artykule będzie się nazywać
Przykład aplikacji sieciowej
public class NetworkAppExample { }
Krok 2. Utwórz główną metodę
Utwórz główną metodę i zadeklaruj, że może zgłaszać wyjątki
Wyjątek
typ i dowolna jego podklasa - wszystkie wyjątki. Jest to uważane za złą praktykę, ale jest dopuszczalne w przypadku kadłubków.
public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { } }
Krok 3. Zadeklaruj adres serwera
W tym przykładzie zostanie użyty adres lokalnego hosta i dowolny numer portu. Numer portu musi mieścić się w zakresie od 0 do 65535 (włącznie). Jednak numery portów, których należy unikać, mieszczą się w zakresie od 0 do 1023 (włącznie), ponieważ są to zastrzeżone porty systemowe.
public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; } }
Krok 4. Utwórz serwer
Serwer jest powiązany z adresem i portem i nasłuchuje połączeń przychodzących. W Javie
Gniazdo serwera
reprezentuje punkt końcowy po stronie serwera, a jego funkcją jest przyjmowanie nowych połączeń.
Gniazdo serwera
nie ma strumieni do odczytu i wysyłania danych, ponieważ nie reprezentuje połączenia między serwerem a klientem.
import java.net. InetAddress; import java.net. ServerSocket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); } }
Krok 5. Inicjacja serwera dziennika
W celu rejestrowania wydrukuj do konsoli, że serwer został uruchomiony.
import java.net. InetAddress; import java.net. ServerSocket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); } }
Krok 6. Utwórz klienta
Klient jest powiązany z adresem i portem serwera i nasłuchuje pakietów (wiadomości) po nawiązaniu połączenia. W Javie
Gniazdo elektryczne
reprezentuje punkt końcowy po stronie klienta podłączony do serwera lub połączenie (z serwera) z klientem i służy do komunikacji ze stroną po drugiej stronie.
importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); } }
Krok 7. Zaloguj próbę połączenia
W celu rejestrowania wydrukuj na konsoli informację, że podjęto próbę połączenia.
importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); } }
Krok 8. Nawiąż połączenie
Klienci nigdy się nie połączą, chyba że serwer nasłuchuje i nie zaakceptuje, innymi słowy, nawiąże połączenia. W Javie połączenia są nawiązywane za pomocą
zaakceptować()
metoda
Gniazdo serwera
klasa. Metoda zablokuje wykonanie do momentu nawiązania połączenia przez klienta.
importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); } }
Krok 9. Zaloguj nawiązane połączenie
W celu rejestrowania wydrukuj na konsoli informację, że zostało ustanowione połączenie między serwerem a klientem.
importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); } }
Krok 10. Przygotuj strumienie komunikacji
Komunikacja odbywa się za pośrednictwem strumieni i, w tej aplikacji, nieprzetworzone strumienie (połączenia z) serwera (do klienta) i klienta muszą być połączone ze strumieniami danych lub obiektów. Pamiętaj, że obie strony muszą używać tego samego typu strumienia.
-
Strumienie danych
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); } }
-
Strumienie obiektów
Gdy używanych jest wiele strumieni obiektów, strumienie wejściowe muszą być inicjowane w tej samej kolejności co strumienie wyjściowe, ponieważ
ObjectOutputStream
wysyła nagłówek do drugiej strony i
ObjectInputStream
blokuje wykonanie, dopóki nie odczyta nagłówka.
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); } }
Kolejność określona w powyższym kodzie może być łatwiejsza do zapamiętania - najpierw zainicjuj strumienie wyjściowe, a następnie strumienie wejściowe w tej samej kolejności. Jednak inna kolejność inicjalizacji strumieni obiektów jest następująca:
ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream());
Krok 11. Zaloguj się, że komunikacja jest gotowa
W celu rejestrowania wydrukuj na konsoli informację, że komunikacja jest gotowa.
// pominięto kod import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); // kod pominięty System.out.println("Komunikacja jest gotowa."); } }
Krok 12. Utwórz wiadomość
W tej aplikacji
Witaj świecie
tekst zostanie wysłany na serwer jako
bajt
lub
Strunowy
. Zadeklaruj zmienną typu, który zależy od używanego strumienia. Posługiwać się
bajt
dla strumieni danych i
Strunowy
dla strumieni obiektów.
-
Strumienie danych
Korzystając ze strumieni danych, serializacja odbywa się poprzez konwersję obiektów na prymitywne typy danych lub a
Strunowy
. W tym przypadku,
Strunowy
jest konwertowany na
bajt
zamiast pisać używając
writeBytes()
metoda, aby pokazać, jak byłoby to zrobione z innymi obiektami, takimi jak obrazy lub inne pliki.
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); byte messageOut = "Witaj świecie".getBytes(); } }
-
Strumienie obiektów
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); String messageOut = "Witaj świecie"; } }
Krok 13. Wyślij wiadomość
Zapisz dane w strumieniu wyjściowym i opróżnij strumień, aby upewnić się, że dane zostały zapisane w całości.
-
Strumienie danych
Najpierw należy przesłać długość wiadomości, aby druga strona wiedziała, ile bajtów musi odczytać. Po wysłaniu długości jako podstawowego typu liczby całkowitej można wysłać bajty.
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); byte messageOut = "Witaj świecie".getBytes(); klientOut.writeInt(messageOut.length); klientOut.write(messageOut); klientOut.flush(); } }
-
Strumienie obiektów
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); String messageOut = "Witaj świecie"; klientOut.writeObject(messageOut); klientOut.flush(); } }
Krok 14. Zaloguj wysłaną wiadomość
W celu rejestrowania wydrukuj w konsoli wiadomość, która została wysłana.
-
Strumienie danych
import java.io. DataInputStream; import java.io. DataOutputStream; import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); byte messageOut = "Witaj świecie".getBytes(); klientOut.writeInt(messageOut.length); klientOut.write(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + nowy String(messageOut)); } }
-
Strumienie obiektów
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); String messageOut = "Witaj świecie"; klientOut.writeObject(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + messageOut); } }
Krok 15. Przeczytaj wiadomość
Odczytaj dane ze strumienia wejściowego i przekonwertuj je. Ponieważ dokładnie znamy rodzaj przesyłanych danych, albo utworzymy
Strunowy
z
bajt
lub obsada
Obiekt
do
Strunowy
bez sprawdzania, w zależności od używanego strumienia.
-
Strumienie danych
Ponieważ długość została wysłana jako pierwsza, a bajty później, odczyt należy wykonać w tej samej kolejności. Jeśli długość wynosi zero, nie ma nic do odczytania. Obiekt jest deserializowany, gdy bajty są konwertowane z powrotem na instancję, w tym przypadku
Strunowy
import java.io. DataInputStream; import java.io. DataOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); byte messageOut = "Witaj świecie".getBytes(); klientOut.writeInt(messageOut.length); klientOut.write(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + nowy String(messageOut)); int length = serverIn.readInt(); if (długość > 0) { bajt wiadomośćIn = nowy bajt[długość]; serverIn.readFully(messageIn, 0, messageIn.length); } } }
-
Strumienie obiektów
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); String messageOut = "Witaj świecie"; klientOut.writeObject(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + messageOut); String messageIn = (String) serverIn.readObject(); } }
Krok 16. Zaloguj przeczytaną wiadomość
W celach rejestrowania wydrukuj w konsoli wiadomość, która została odebrana i wydrukuj jej zawartość.
-
Strumienie danych
import java.io. DataInputStream; import java.io. DataOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); DataOutputStream clientOut = new DataOutputStream(client.getOutputStream()); DataInputStream clientIn = new DataInputStream(client.getInputStream()); DataOutputStream serverOut = new DataOutputStream(connection.getOutputStream()); DataInputStream serverIn = new DataInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); byte messageOut = "Witaj świecie".getBytes(); klientOut.writeInt(messageOut.length); klientOut.write(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + nowy String(messageOut)); int length = serverIn.readInt(); if (długość > 0) { bajt wiadomośćIn = nowy bajt[długość]; serverIn.readFully(messageIn, 0, messageIn.length); System.out.println("Wiadomość odebrana od klienta: " + nowy String(messageIn)); } } }
-
Strumienie obiektów
import java.io. ObjectInputStream; import java.io. ObjectOutputStream; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); ObjectOutputStream clientOut = new ObjectOutputStream(client.getOutputStream()); ObjectOutputStream serverOut = new ObjectOutputStream(connection.getOutputStream()); ObjectInputStream clientIn = new ObjectInputStream(client.getInputStream()); ObjectInputStream serverIn = new ObjectInputStream(connection.getInputStream()); System.out.println("Komunikacja jest gotowa."); String messageOut = "Witaj świecie"; klientOut.writeObject(messageOut); klientOut.flush(); System.out.println("Wiadomość wysłana do serwera: " + messageOut); String messageIn = (String) serverIn.readObject(); System.out.println("Wiadomość odebrana od klienta: " + messageIn); } }
Krok 17. Rozłącz połączenia
Połączenie jest rozłączane, gdy jedna ze stron zamyka swoje strumienie. W Javie zamknięcie strumienia wyjściowego powoduje również zamknięcie powiązanego gniazda i strumienia wejściowego. Gdy strona po drugiej stronie dowie się, że połączenie nie działa, musi również zamknąć strumień wyjściowy, aby zapobiec wyciekom pamięci.
// pominięto kod import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); // kod pominięty System.out.println("Komunikacja jest gotowa."); // pominięto kod clientOut.close(); serwerOut.zamknij(); } }
Krok 18. Rozłączenie dziennika
Na potrzeby rejestrowania połączenia drukowania do konsoli zostały rozłączone.
// pominięto kod import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); // kod pominięty System.out.println("Komunikacja jest gotowa."); // pominięto kod clientOut.close(); serwerOut.zamknij(); System.out.println("Połączenia zamknięte."); } }
Krok 19. Zakończ serwer
Połączenia są rozłączone, ale serwer nadal działa. Jak
Gniazdo serwera
nie jest powiązany z żadnym strumieniem, należy go jawnie zamknąć przez wywołanie
blisko()
metoda.
// pominięto kod import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); // kod pominięty System.out.println("Komunikacja jest gotowa."); // pominięto kod clientOut.close(); serwerOut.zamknij(); System.out.println("Połączenia zamknięte."); serwer.zamknij(); } }
Krok 20. Zakończenie serwera dziennika
Na potrzeby rejestrowania drukowanie na serwerze konsoli zostało zakończone.
// pominięto kod import java.net. InetAddress; import java.net. ServerSocket; import java.net. Socket; public class NetworkAppExample { public static void main(String args) wyrzuca wyjątek { String host = "localhost"; port wewnętrzny = 10430; ServerSocket server = new ServerSocket(port, 50, InetAddress.getByName(host)); System.out.println("Serwer uruchomiony."); Klient gniazda = nowy Socket(host, port); System.out.println("Łączenie z serwerem…"); Połączenie z gniazdem = server.accept(); System.out.println("Połączenie nawiązane."); // kod pominięty System.out.println("Komunikacja jest gotowa."); // pominięto kod clientOut.close(); serwerOut.zamknij(); System.out.println("Połączenia zamknięte."); serwer.zamknij(); System.out.println("Zakończono pracę serwera."); } }
Krok 21. Skompiluj i uruchom
Logowanie pozwoliło nam dowiedzieć się, czy aplikacja się powiodła, czy nie. Oczekiwany wynik:
Serwer uruchomiony. Trwa łączenie z serwerem… Nawiązano połączenie. Komunikacja jest gotowa. Wiadomość wysłana do serwera: Hello World Wiadomość odebrana od klienta: Hello World Połączenia zamknięte. Serwer zakończony.
W przypadku, gdy Twój wynik nie jest podobny do powyższego, co jest mało prawdopodobne, istnieje kilka rozwiązań:
-
Jeśli wyjście zatrzymuje się na linii
Połączenie nawiązane.
i strumienie obiektów są używane, opróżnij każdy
ObjectOutputStream
- natychmiast po inicjalizacji, ponieważ z jakiegoś powodu nagłówki nie zostały wysłane.
-
Jeśli wydruk zostanie wydrukowany
java.net. BindException: adres już używany
- wybierz inny numer portu, ponieważ podany jest już używany.
Porady
- Połączenie z serwerem w innej sieci odbywa się poprzez połączenie z zewnętrznym adresem IP urządzenia z uruchomionym serwerem, który ma przekierowany port.
- Połączenie z serwerem w tej samej sieci odbywa się poprzez połączenie z prywatnym adresem IP urządzenia z uruchomionym serwerem lub przekierowanie portu i połączenie z zewnętrznym adresem IP urządzenia.
- Istnieją programy, takie jak Hamachi, które umożliwiają połączenie z serwerem w innej sieci bez przekazywania portu, ale wymagają instalacji oprogramowania na obu urządzeniach.
Przykłady
Aplikacje sieciowe korzystające z blokowania wejścia/wyjścia muszą używać wątków. Poniższe przykłady pokazują minimalistyczną implementację serwera i klienta z wątkami. Kod sieciowy jest zasadniczo taki sam jak w artykule, z wyjątkiem niektórych fragmentów kodu, które zostały zsynchronizowane, przeniesione do wątków i obsługiwane są wyjątki.
Server.java
import java.io. IOException; importowanie adresu java.net. Inet; import java.net. ServerSocket; import java.net. SocketException; import java.net. UnknownHostException; import java.util. ArrayList; importować java.util. Collections; import java.util. Lista; /** * Klasa {@code Server} reprezentuje punkt końcowy serwera w sieci. {@code Server} po przypisaniu do określonego adresu IP* i portu nawiązuje połączenia z klientami i jest w stanie komunikować się z nimi lub rozłączać ich. *
* Ta klasa jest bezpieczna wątkowo. * * @wersja 1.0 * @see Klient * @see Połączenie */ public class Serwer implementuje Runnable { private ServerSocket server; Lista prywatna
znajomości; prywatny wątek wątku; private final Object connectionLock = new Object(); /** * Konstruuje {@code Server}, który współdziała z klientami na określonej nazwie hosta i porcie z określoną * żądaną maksymalną długością kolejki przychodzących klientów. * * @param host Adres hosta do użycia. * @param port Numer portu do użycia. * @param backlog Żądana maksymalna długość kolejki przychodzących klientów. * @throws NetworkException Jeśli wystąpi błąd podczas uruchamiania serwera. */ public Server(String host, int port, int backlog) throws NetworkException { try { server = new ServerSocket(port, backlog, InetAddress.getByName(host)); } catch (UnknownHostException e) { throw new NetworkException("Nazwa hosta nie może zostać rozwiązana: " + host, e); } catch (IllegalArgumentException e) { throw new NetworkException("Numer portu musi być z zakresu od 0 do 65535 (włącznie): " + port); } catch (IOException e) { throw new NetworkException("Nie można uruchomić serwera.", e); } połączenia = Collections.synchronizedList(new ArrayList()); wątek = nowy wątek(to); wątek.start(); } /** * Konstruuje {@code Server}, który współdziała z klientami na określonej nazwie hosta i porcie. * * @param host Adres hosta do powiązania. * @param port Numer portu do powiązania. * @throws NetworkException Jeśli wystąpią błędy podczas uruchamiania serwera. */ public Server(String host, int port) wyrzuca NetworkException { this(host, port, 50); } /** * Nasłuchuje, akceptuje i rejestruje połączenia przychodzące od klientów. */ @Override public void run() { while (!server.isClosed()) { try { connections.add(new Connection(server.accept())); } catch (SocketException e) { if (!e.getMessage().equals("Gniazdo zamknięte")) { e.printStackTrace(); } } catch (NetworkException | IOException e) { e.printStackTrace(); } } } /** * Wysyła dane do wszystkich zarejestrowanych klientów. * * @param data Dane do wysłania. * @throws IllegalStateException W przypadku próby zapisu danych, gdy serwer jest w trybie offline. * @throws IllegalArgumentException Jeśli dane do wysłania mają wartość NULL. */ public void broadcast(Object data) { if (server.isClosed()) { throw new IllegalStateException("Dane nie zostały wysłane, serwer jest w trybie offline."); } if (data == null) { throw new IllegalArgumentException("dane null"); } synced (connectionsLock) { for (Połączenie połączenia: połączenia) { try { connection.send(data); System.out.println("Dane wysłane do klienta pomyślnie."); } catch (NetworkException e) { e.printStackTrace(); } } } } /** * Wysyła wiadomość o rozłączeniu i rozłącza określonego klienta. * * @param connection Klient do rozłączenia. * @throws NetworkException Jeśli wystąpi błąd podczas zamykania połączenia. */ public void odłączenie(połączenie) zgłasza NetworkException { if (connections.remove(połączenie)) { connection.close(); } } /** * Wysyła wiadomość o rozłączeniu do wszystkich klientów, rozłącza ich i kończy działanie serwera. */ public void close() wyrzuca NetworkException { syncd (connectionsLock) { for (Connection connection: connections) { try { connection.close(); } catch (NetworkException e) { e.printStackTrace(); } } } połączenia.clear(); spróbuj { serwer.zamknij(); } catch (IOException e) { throw new NetworkException("Błąd podczas zamykania serwera."); } wreszcie { thread.interrupt(); } } /** * Zwraca czy serwer jest w trybie online. * * @return Prawda, jeśli serwer jest w trybie online. Fałsz, inaczej. */ public boolean isOnline() { return !server.isClosed(); } /** * Zwraca tablicę zarejestrowanych klientów. */ public Connection getConnections() { synced (connectionsLock) { return connections.toArray(new Connection[connections.size()]); } } }
Klient.java
import java.io. IOException; import java.net. Socket; import java.net. UnknownHostException; /** * Klasa {@code Client} reprezentuje punkt końcowy klienta w sieci. {@code Client} po połączeniu z określonym * serwerem gwarantuje, że będzie mógł komunikować się tylko z tym serwerem. To, czy inni klienci otrzymają dane *, zależy od implementacji serwera. *
* Ta klasa jest bezpieczna wątkowo. * * @wersja 1.0 * @see Serwer * @see Połączenie */ public class Klient { private Połączenie połączenie; /** * Konstruuje {@code Client} połączonego z serwerem na określonym hoście i porcie. * * @param host Adres hosta do powiązania. * @param port Numer portu do powiązania. * @throws NetworkException Jeśli wystąpi błąd podczas uruchamiania serwera. */ public Client(String host, int port) wyrzuca NetworkException { try { connection = new Connection(new Socket(host, port)); } catch (UnknownHostException e) { throw new NetworkException("Nazwa hosta nie może zostać rozwiązana: " + host, e); } catch (IllegalArgumentException e) { throw new NetworkException("Numer portu musi być z zakresu od 0 do 65535 (włącznie): " + port); } catch (IOException e) { throw new NetworkException("Nie można uruchomić serwera.", e); } } /** * Wysyła dane do drugiej strony. * * @param data Dane do wysłania. * @throws NetworkException Jeśli zapis do strumienia wyjściowego nie powiedzie się. * @throws IllegalStateException W przypadku próby zapisu danych po zamknięciu połączenia. * @throws IllegalArgumentException Jeśli dane do wysłania mają wartość NULL. * @throws UnsupportedOperationException W przypadku próby wysłania nieobsługiwanego typu danych. */ public void send(Object data) wyrzuca NetworkException { connection.send(data); } /** * Wysyła wiadomość o rozłączeniu i zamyka połączenie z serwerem. */ public void close() zgłasza NetworkException { connection.close(); } /** * Zwraca czy klient jest połączony z serwerem. * * @return Prawda, jeśli klient jest podłączony. Fałsz, inaczej. */ public boolean isOnline() { return connection.isConnected(); } /** * Zwraca instancję {@link Connection} klienta. */ public Connection getConnection() { return connection; } }
Połączenie.java
import java.io. DataInputStream; import java.io. DataOutputStream; import java.io. IOException; import java.net. Socket; import java.net. SocketException; /** * Klasa {@code Connection} reprezentuje połączenie od serwera do klienta lub punkt końcowy klienta w sieci * {@code Connection} po połączeniu może wymieniać dane z inną stroną lub stronami, w zależności od na serwerze * implementacja. *
* Ta klasa jest bezpieczna wątkowo. * * @wersja 1.0 * @see Serwer * @see Klient */ public class Połączenie implementuje Runnable { private Socket socket; prywatne wyjście DataOutputStream; prywatny DataInputStream w; prywatny wątek wątku; private final Object writeLock = new Object(); private final Object readLock = new Object(); /** * Konstruuje {@code Connection} przy użyciu strumieni określonego {@link Socket}. * * @param socket Gniazdo, z którego pobierane są strumienie.*/ public Connection(gniazdo) throws NetworkException { if (socket == null) { throw new IllegalArgumentException("gniazdo puste"); } this.socket = gniazdo; try { out = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { throw new NetworkException("Nie można uzyskać dostępu do strumienia wyjściowego.", e); } try { in = new DataInputStream(socket.getInputStream()); } catch (IOException e) { throw new NetworkException("Nie można uzyskać dostępu do strumienia wejściowego.", e); } wątek = nowy wątek(to); wątek.start(); } /** * Czyta wiadomości, gdy połączenie z drugą stroną jest aktywne. */ @Override public void run() { while (!socket.isClosed()) { spróbuj { int identyfikator; bajt bajty; synced (readLock) { identyfikator = in.readInt(); int length = in.readInt(); if (długość > 0) { bajty = nowy bajt[długość]; in.readFully(bajty, 0, bajty.długość); } inny { kontynuuj; } } switch (identyfikator) { case Identifier. INTERNAL: String command = new String(bytes); if (command.equals("rozłącz")) { if (!socket.isClosed()) { System.out.println("Odebrano pakiet rozłączający."); spróbuj { zamknij(); } catch (NetworkException e) { return; } } } przerwa; case Identifier. TEXT: System.out.println("Odebrana wiadomość: " + new String(bytes)); przerwa; domyślnie: System.out.println("Otrzymano nierozpoznane dane."); } } catch (SocketException e) { if (!e.getMessage().equals("Gniazdo zamknięte")) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } } /** * Wysyła dane do drugiej strony. * * @param data Dane do wysłania. * @throws NetworkException Jeśli zapis do strumienia wyjściowego nie powiedzie się. * @throws IllegalStateException W przypadku próby zapisu danych po zamknięciu połączenia. * @throws IllegalArgumentException Jeśli dane do wysłania mają wartość NULL. * @throws UnsupportedOperationException W przypadku próby wysłania nieobsługiwanego typu danych. */ public void send(Object data) throws NetworkException { if (socket.isClosed()) { throw new IllegalStateException("Dane nie zostały wysłane, połączenie jest zamknięte."); } if (data == null) { throw new IllegalArgumentException("dane null"); } int identyfikator; bajt bajty; if (instancja danych String) { identyfikator = Identyfikator. TEKST; bajty = ((ciąg) dane).getBytes(); } else { throw new UnsupportedOperationException("Nieobsługiwany typ danych: " + data.getClass()); } spróbuj { zsynchronizowane (writeLock) { out.writeInt(identyfikator); out.writeInt(bajty.długość); out.write(bajty); out.flush(); } } catch (IOException e) { throw new NetworkException("Nie można wysłać danych.", e); } } /** * Wysyła wiadomość o rozłączeniu i zamyka połączenie z drugą stroną. */ public void close() throws NetworkException { if (socket.isClosed()) { throw new IllegalStateException("Połączenie jest już zamknięte."); } try { byte message = "rozłącz".getBytes(); zsynchronizowane (writeLock) { out.writeInt(Identyfikator. INTERNAL); out.writeInt(wiadomość.długość); out.write(wiadomość); out.flush(); } } catch (IOException e) { System.out.println("Nie można wysłać komunikatu o rozłączeniu."); } spróbuj { zsynchronizowane (writeLock) { out.close(); } } catch (IOException e) { throw new NetworkException("Błąd podczas zamykania połączenia.", e); } wreszcie { thread.interrupt(); } } /** * Zwraca czy połączenie z drugą stroną jest aktywne. * * @return Prawda, jeśli połączenie jest aktywne. Fałsz, inaczej. */ public boolean isConnected() { return !socket.isClosed(); } }
Identyfikator.java
/** * Klasa {@code Identifier} zawiera stałe używane przez {@link Connection} do serializacji i deserializacji danych * przesyłanych przez sieć. * * @version 1.0 * @see Połączenie */ public final class Identyfikator { /** * Identyfikator wiadomości wewnętrznych. */ public static final int INTERNAL = 1; /** * Identyfikator wiadomości tekstowych. */ public static final int TEKST = 2; }
NetworkException.java
/** * Klasa {@code NetworkException} wskazuje na błąd związany z siecią. */ public class NetworkException extends Exception { /** * Konstruuje {@code NetworkException} z {@code null} jako komunikatem. */ public NetworkException() { } /** * Konstruuje {@code NetworkException} z określonym komunikatem. * * @param message Komunikat opisujący błąd. */ public NetworkException(String message) { super(message); } /** * Konstruuje {@code NetworkException} z określonym komunikatem i przyczyną. * * @param message Komunikat opisujący błąd. * @param Cause Przyczyna błędu. */ public NetworkException(String message, Throwable przyczyny) { super(message, Cause); } /** * Konstruuje {@code NetworkException} z określoną przyczyną. * * @param Cause Przyczyna błędu. */ public NetworkException(wyrzucana przyczyna) { super(przyczyna); } }
Przykład użycia.java
/** * Klasa {@code UsageExample} pokazuje użycie {@link Server} i {@link Client}. W tym przykładzie użyto * {@link Thread#sleep(long)}, aby zapewnić wykonanie każdego segmentu, ponieważ szybkie uruchamianie i zamykanie powoduje, że niektóre * segmenty nie są wykonywane. * * @wersja 1.0 * @see Serwer * @see Klient */ public class PrzykładUsytu { public static void main(String args) throws Exception { String host = "localhost"; port wewnętrzny = 10430; Serwer serwer = nowy Serwer(host, port); Klient klient = nowy Klient (host, port); Wątek.sen (100L); klient.send("Cześć."); server.broadcast("Hej kolego!"); Wątek.sen (100L); server.disconnect(server.getConnections()[0]); // lub client.close(), aby rozłączyć się z serwerem po stronie klienta.close(); } }