Java Socket: mrezni prikljucak koji prenosi poruke kao golubovi pismonoše
U davnim vremenima, zbog otezane komunikacije, neki snalazljivi ljudi su iskoristili cinjenicu da golubovi lete, i to brzo, te da umeju da pronadu put, pa su ih pripitomili i koristili za prenos poruka — poznato kao „golub pismonoša“. U Javi mrezni prikljucak (Socket) igra istu ulogu.

Prikljucak (Socket) je apstraktni sloj preko kojeg aplikacija moze da salje ili prima podatke; moze se otvarati, citati, pisati i zatvarati kao datoteka. Prikljucak omogucava aplikaciji da I/O primeni na mrezu i komunicira sa drugim aplikacijama. Mrezni prikljucak je kombinacija IP adrese i porta.
01. ping i telnet
„Stari Vang, mozes li mi pogledati ovaj problem, lokalno mrezna komunikacija radi, ali kad deployujem na server vise ne komunicira, satima kopam i ne znam uzrok, a cini mi se da kod nije problem.“ — glas Xiao Era bio je pun razocaranja.
„Da li si pingovao? Ili telnetovao?“ Stari Vang nije ni podigao glavu, samo je hladno bacio tu recenicu.
„Aha, idem da probam.“ Xiao Er oseti blag stid.
Komande ping i telnet od neprocenjive su pomoci pri debagovanju mreznih programa.
ping je alatka za racunarske mreze kojom se proverava da li paket preko IP protokola moze stici do odredjenog hosta. ping salje ciljnom hostu ICMP Echo Request paket i ceka da primi Echo Reply paket.
Na primer, pingovacemo sajt blogova. Snimak ekrana je ispod.

telnet je standardni protokol i glavni nacin za pristup usluzi udaljenog prijavljivanja na Internetu; omogucava nam da sa svog racunara kod kuce prijavimo na drugi, udaljeni racunar negde daleko u svetu.

U operativnom sistemu Windows telnet je obicno podrazumevano instaliran, ali nije aktiviran (mozes ga aktivirati u Control Panel-u).
Na primer, telnetujmo zajednicu Shuimu (bbs.newsmth.net). Snimak ekrana je ispod.

Prilikom prijavljivanja na udaljeni racunar pomocyu telnet-a, na udaljenom racunaru mora raditi servis koji neprekidno ceka mrezne zahteve klijenata koji zele da se povezu; kad primi mreznu konekciju od klijenta, budi serverski proces koji osluskuje zahteve za konekciju i uspostavlja vezu izmedju njih dvojice. Konekcija se odrzava dok je jedna strana ne prekine.
Treba ipak imati na umu da telnet u savremenim mreznim tehnologijama, koje posebno pazljivo tretiraju bezbednost, nije mnogo u upotrebi. Jer je telnet protokol koji prenosi podatke u otvorenom tekstu — svi korisnicki podaci (ukljucujuci korisnicko ime i lozinku) nisu sifrovani, sto predstavlja veliki bezbednosni rizik.
02. Primer klase Socket
Nisam siguran da li ste probali onu telnet komandu ka zajednici Shuimu — rezultat je prilicno zanimljiv. Isto mozemo postici klijentskim prikljuckom (Socket) u Javi. Primer koda je ispod.
try (Socket socket = new Socket("bbs.newsmth.net", 23);) {
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "gbk");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}- Uspostavljanje konekcije prikljuckom je vrlo jednostavno, potrebna je samo jedna linija koda:
Socket socket = new Socket(host, port)host je ime hosta, port je broj porta (23 je podrazumevani port za telnet). Ako se IP adresa hosta ne moze utvrditi, baca se izuzetak UnknownHostException; ako tokom kreiranja prikljucka dodje do IO greske, baca se izuzetak IOException.
Treba napomenuti da, ako udaljeni host nije dostupan, ovaj kod prilikom uspostavljanja konekcije dugo blokira, sve dok sistem ne dodje do ogranicenja i baci izuzetak. Zato se nakon uspostavljanja prikljucka obicno podesava tajm-aut.
Socket socket = new Socket(...);
socket.setSoTimeout(10000); // jedinica je milisekunda- Nakon uspesne konekcije, preko metode
getInputStream()klasejava.net.Socketmoze se dobiti ulazni tok. Kad jednom imamo objekatInputStream, mozemo pomocy klase za skeniranje teksta (Scanner) ispisati njegov sadrzaj.
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "gbk");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}Deo rezultata (kompletan rezultat probajte sami) prikazan je na slici ispod:

03. Primer klase ServerSocket
Zatim cemo simulirati udaljeni servis pomocyu klase java.net.ServerSocket. Primer koda je ispod.
try (ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(is)) {
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
pw.println("Zdravo, dobrodosli na javni nalog „Chenmo Wang Er“, odgovori kljucnom recju „2048“ i preuzmi paket materijala za napredne programe");
boolean done = false;
while (!done && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if ("2048".equals(line)) {
done = true;
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}- Kreiranje serverskog prikljucka je takodje prilicno jednostavno — dovoljno je zadati port koji moze biti rezervisan (portovi 0–1023 su vec rezervisani od strane sistema).
ServerSocket server = new ServerSocket(8888);- Pozivom metode
accept()objekta ServerSocket ceka se zahtev klijenta za konekciju. Cim se oseti zahtev klijenta, vraca se objekat Socket koji predstavlja uspostavljenu konekciju, iz kojeg se mogu preuzeti ulazni i izlazni tok.
Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();Sve poruke koje klijent posalje wrapped su u ulazni tok serverskog prikljucka; a sve poruke koje server posalje wrapped su u izlazni tok klijentskog prikljucka.
- Server moze sledecim kodom poslati poruku klijentu.
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
pw.println("Zdravo, dobrodosli na javni nalog „Chenmo Wang Er“, odgovori kljucnom recju „2048“ i preuzmi paket materijala za napredne programe");- Server moze sledecim kodom procitati poruke koje klijent posalje.
Scanner scanner = new Scanner(is);
boolean done = false;
while (!done && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if ("2048".equals(line)) {
done = true;
}
}Nakon pokretanja ovog servisa, mozete se povezati na njega komandom telnet localhost 8888 i, kao što se i ocekivalo, videcete sledecu poruku.

PS: u trenutnom prozoru komande mozete uneti 2048; kad server primi tu poruku, prekinuce tu socket konekciju (trenutni prozor ce pokazati „veza sa hostom je izgubljena“).
04. Opsluzivanje vise klijenata
Na zalost, u prethodnom primeru server je mogao da opsluzuje samo jednog klijenta — što ne ispunjava zahtev da server bude jedan-prema-vise.
Resenje za optimizaciju je takodje jednostavno (verovatno ti je i samo palo na pamet): kad server primi zahtev klijenta za konekciju, moze pokrenuti nit za obradu, dok glavni program nastavlja da ceka sledecu konekciju. Primer koda je ispod.
try (ServerSocket server = new ServerSocket(8888)) {
while (true) {
Socket socket = server.accept();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// obrada prikljucka
}
});
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}Unutar niti (u metodi run(){})) obradjuje se prikljucak; primer koda je ispod:
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(is);
// ostali kod izostavljen
// slanje poruke klijentu
// citanje poruke koju klijent posalje
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}Nakon optimizacije i ponovnog pokretanja serverskog koda, mozes testirati komandom telnet. Otvori jedan prozor komandne linije i unesi telnet localhost 8888, zatim otvori novi prozor i unesi telnet localhost 8888 — vise prozora moze komunicirati sa serverom sve dok se serverski kod ne prekine.
05. Dodavanje visenitnog resenja
Visenitno programiranje cemo detaljno obraditi kasnije; ovde je samo mali primer da vam bude zanimljivije, a zapravo je i prilicno jednostavno.
Pogledajmo server:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class MultiThreadedServer {
public static void main(String[] args) throws IOException {
int port = 12345;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("Server is listening on port " + port);
while (true) {
Socket socket = serverSocket.accept();
System.out.println("Client connected");
new ClientHandler(socket).start();
}
}
}
class ClientHandler extends Thread {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Received: " + line);
writer.println("Server: " + line);
}
socket.close();
} catch (IOException e) {
System.out.println("Client disconnected");
}
}
}U ovom primeru koristimo klasu ClientHandler koja nasledjuje klasu Thread. To omogucava da se svaka konekcija klijenta obradjuje u posebnoj niti, čime server istovremeno opsluzuje vise klijenata. Kad novi klijent konektuje na server, server kreira novi objekat ClientHandler i metodom start() pokrece nit. Metoda run() klase ClientHandler sadrzi logiku obrade klijentskog zahteva.
Pogledajmo klijentski kod:
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
String hostname = "localhost";
int port = 12345;
Socket socket = new Socket(hostname, port);
System.out.println("Connected to the server");
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);
writer.println("Hello, server!");
String response = reader.readLine();
System.out.println("Server response: " + response);
socket.close();
}
}Pokreni server, zatim pokreni vise klijenata i iskusices zabavu interakcije.

06. Primer klase DatagramSocket
Klasa DatagramSocket je jezgro implementacije UDP protokola u Javi. Za razliku od klasa Socket i ServerSocket koje se oslanjaju na TCP, klasa DatagramSocket pruza bezveze (connectionless) komunikacione servise za slanje i prijem paketa. Posto ne zahteva uspostavljanje konekcije, UDP je obicno brzi od TCP-a, ali moze biti manje pouzdan od TCP-a.
Evo jednostavnog primera DatagramSocket-a koji pokazuje kako se pomocyu UDP protokola salju i primaju poruke izmedju klijenta i servera.
Serverski kod:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) throws IOException {
int port = 12345;
DatagramSocket serverSocket = new DatagramSocket(port);
System.out.println("Server is listening on port " + port);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("Received: " + message);
serverSocket.close();
}
}Klijentski kod:
import java.io.IOException;
import java.net.*;
public class UDPClient {
public static void main(String[] args) throws IOException {
String hostname = "localhost";
int port = 12345;
InetAddress address = InetAddress.getByName(hostname);
DatagramSocket clientSocket = new DatagramSocket();
String message = "Hello, server!";
byte[] buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);
clientSocket.send(packet);
System.out.println("Message sent");
clientSocket.close();
}
}U ovom primeru server kreira objekat DatagramSocket i osluskuje na portu 12345. Zatim kreira objekat DatagramPacket za cuvanje pristiglih paketa. Metoda serverSocket.receive(packet) blokira dok ne stigne paket. Kad paket stigne, server iz njega izdvaja i ispisuje poruku.
Klijent prvo razresava IP adresu servera, a zatim kreira objekat DatagramSocket. Nakon toga, klijent kreira objekat DatagramPacket koji sadrzi poruku za slanje i navodi adresu i port primaoca. Na kraju, klijent salje paket pozivom metode clientSocket.send(packet).
Rezultat pokretanja je prikazan ispod:

07. Na kraju
Vecina danasnjih mreznih softvera — pregledaci, alati za instant poruke, cak i P2P preuzimanja — zasnovana je na Socket-u, pa jevljivanje Java Socket programiranja je prilicno bitno. Socket programiranje je i zabavno; mnogi pocetnici napise po koji mali „klijent-server“ program da bi podigli svoj nivo programiranja. Preporucujem i tebi da probaš.
