35
Tags:
java
Skrevet af
Bruger #1425
@ 28.03.2005
Introduktion:
Denne artikel vil omhandle netværksprogrammering i Java via to protokoller, TCP og UDP. Til dem der ikke ved hvad TCP og UDP præcis er for nogle protokoller er der et kort afsnit om hvert af dem nedenunder.
Artiklens gennemgående eksempel vil være en echo-server samt en klient. En echo-server er et meget simpelt program der blot modtager data og sender det samme data tilbage til afsenderen.
Eksempel koden kan hentes på
http://udvikleren.dk/articlefiles/java-networking.zipUser Datagram Protocol (UDP):
UDP er en forbindelsesløs og såkaldt upålidelig protokol, hvilket vil sige at man reelt sender pakker i blinde uden at være klar over om modtageren eksisterer eller modtager de pakker man sender.
Der er ej heller nogen garanti for at de sendte pakker vil ankomme i samme rækkefølge på modtager siden.
En pakke er en klump data på maksimalt 64kb.
UDP anvendes ofte hvor der skal udveksles mindre datamængder ret hurtigt. Et eksempel er i forbindelse med DNS (Domain Name Service er det system der kan lave værtsnavne som Udvikleren.dk om til en IP adresse.)
Transmission Control Protocol (TCP):
TCP er en forbindelses orienteret og pålidelig protokol, hvilket vil sige at der skal oprettes en forbindelse før data kan sendes mellem to processer. En oprettelse foregår ved et 3-vejs håndtryk, der groft kan beskrives således: Process A vil oprette forbindelse til process B. Process A sender en pakke til B der siger ”Jeg vil gerne oprette en forbindelse”, hvortil B svarer ”Okay, det må du gerne”, til dette svarer A ”Godt, så kan vi tale sammen”.
Herefter kan både A og B sende data til hinanden.
Data sendes som ens strøm af tegn fra afsender til modtager. Det er garanteret at alle tegn når frem, og i samme rækkefølge som de blev sendt.
TCP anvendes ofte hvor der skal overføres større datamængder, så som en browser-session eller anden filoverførsel.
Enkelt trådet echo server
For at vi kan acceptere indkomne forbindelser skal vi lave en instans af ServerSocket. Porten der skal lyttes på angives i konstruktoren.
Eksempel 1:
ServerSocket srv = new ServerSocket(10000);
Dette vil dog ikke virke da konstruktoren kaster IOException’s som vi er nødt til at fange. I mange tilfælde vil det være en god idé at fange en subtype af IOException først, og derefter fange resten af de IOExceptions der måtte blive kastet. I eksempel 1.1 fanger vi BindException’s så vi kan fortælle brugeren hvorfor vores ServerSocket ikke kunne oprettes.
Eksempel 2:
int port = 10000;
ServerSocket srv = null;
try {
srv = new ServerSocket(port);
} catch (BindException be) {
System.out.println("[server] Kunne ikke lytte på "+port);
System.exit(1);
} catch (IOException e) {
System.out.println("[server] Kunne ikke åbne server-socket");
e.printStackTrace();
System.exit(1);
}
Så er vi klar til at acceptere nye forbindeler! For at acceptere en forbindelse fra en klient skal vi kalde ServerSocket’s accept() metode, der returnerer en Socket for den nye forbindelse. Dette kan igen kaste IOExceptions, men gør det kun hvis ServerSocketen er lukket eller ikke lytter på en port.
Eksempel 3:
//ServerSocket srv er oprettet og lytter.
Socket sock = null;
try {
sock = srv.accept();
} catch (IOException e) {
//Håndter fejlen
}
Det vil unægteligt være meget rart at vide hvem en Socket er forbundet til, og det kan vi finde ud af ved hjælp af to metoder på Socket klassen:
Socket.getPort() – Returnerer fjernværtens port som en int.
Socket.getInetAddress – Returnerer fjernværtens adresse som en InetAddress
For at få adressen som en String kan InetAdress’ getHostAddress() metode bruges.
Som nævnt i indledningen foregår TCP kommunikation ved hjælp af data strømme. Derfor skal vi bruge en Socket’s tilhørende InputStream og OutputStream for at læse og skrive. Disse to streams kan tilgås via. getInputStream() og getOutputStream() på Socket. Begge metoder kaster IOExceptions som vi selvfølgelig skal fange.
For at læse bruger vi InputStream’s read(byte[] buffer, int off, int len) metode. Vi opretter først et byte-array til at gemme det indkomne data og kalder så metoden med parameterne byte-array, 0, byte-array.length. Metoden vil enten returnere antallet af læste tegn (Dog højst len tegn) eller -1 hvis inputstreamen har nået sin slutning (f.eks. hvis vores forbindelse er blevet afbrudt). Ergo kan vi loope indtil read-metoden returnerer -1, og derefter antage at forbindelse er blevet lukket.
For at skrive bruger vi OntputStream’s write metode. Da vi blot vil lave en echo-server skal vi selvfølgelig bare skrive samme byte-array som vi lige har læst.
Lad os sætte alle tingene sammen til en komplet TCP-echo server:
Eksempel 4:
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.net.BindException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class EchoServer {
public static void main(String[] args) {
final int port = 10000; //porten vi vil lytte på.
System.out.println("[server] Starter echo server på port "+port);
ServerSocket srv = null;
try {
srv = new ServerSocket(port);
} catch (BindException be) {
System.out.println("[server] Kunne ikke lytte på "+port);
System.exit(1);
} catch (IOException e) {
System.out.println("[server] Kunne ikke åbne server-socket");
e.printStackTrace();
System.exit(1);
}
System.out.println("[server] Lytter på port "+port);
Socket sock = null;
try {
while ((sock = srv.accept()) != null) {
InetAddress remoteHost = sock.getInetAddress();
int remotePort = sock.getPort();
System.out.println("[server] Accepterede forbindelse fra "+
remoteHost.getHostAddress()+
":"+remotePort);
InputStream in = sock.getInputStream(); //Stream til indkommende data
OutputStream out = sock.getOutputStream(); //Stream til udgående data
byte[] buffer = new byte[16384]; //16kb buffer
while (true) {
/* InputStream's read(byte[], int, int) metode returnerer enten:
Antallet af læste tegn
-1, hvis der ikke kunne læses data (Hvis slutningen af streamen er nået).
*/
int read = in.read(buffer, 0, buffer.length); //læs op til MAX_SIZE tegn.
if (read == -1)
break; //echo ikke hvis der ikke kunne læses data
out.write(buffer, 0, read); //skriv tegn tilbage til klienten
}
System.out.println("[server] Forbindelse afbrudt..");
}
} catch (IOException e) {
System.out.println("[server] Fejl: Input/Output fejl");
e.printStackTrace();
System.exit(1);
}
}
}
Du vil måske bemærke at denne echo-server kun understøtter én klient af gangen. Dette problem råder vi bod på senere i eksempel ????? med en multitrådet echo-server.
Simpel TCP klient:
Når vi skal oprette en forbindelse til en fjernvært skal vi bruge en Socket. Vi angiver værtsnavn samt portnummer som parametre i konstruktoren. Vi er nødt til at fange to typer exceptions, nemlig UnknownHostException der opstår hvis værtnavnets IP adresse ikke kunne findes og IOException hvis der opstod en I/O-fejl.
Eksempel 5:
Socket sock=null;
try {
sock = new Socket("localhost", 10000); //Opret forbindelse til localhost:10000
} catch (UnknownHostException uhe) {
System.out.println("[client] Ukendt værtsnavn");
uhe.printStackTrace();
System.exit(1);
} catch (IOException ioe) {
System.out.println("[client] Kunne ikke oprette forbindelsen");
ioe.printStackTrace();
System.exit(1);
}
Bemærk at vi arbejder med Socket, som vi jo også brugte i server delen. Derfor læser og skriver vi på præcis samme måde.
Vi har dermed alle delene til en fungerende TCP klient, som vi har i eksempel 6.
Idéen er at vi opretter en forbindelse, sender en String og læser et eventuelt svar i klumper af 16kb.
Eksempel 6:
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class TCPClient {
public static void main(String[] args) {
Socket sock=null;
try {
sock = new Socket("localhost", 10000); //Opret forbindelse til localhost:10000
} catch (UnknownHostException uhe) {
System.out.println("[client] Ukendt værtsnavn");
uhe.printStackTrace();
System.exit(1);
} catch (IOException ioe) {
System.out.println("[client] Kunne ikke oprette forbindelsen");
ioe.printStackTrace();
System.exit(1);
}
/* Nu har vi en forbindelse til fjerncomputeren */
try {
InputStream in = sock.getInputStream(); //Stream til indkommende data
OutputStream out = sock.getOutputStream(); //Stream til udgående data
out.write("Dette er en test".getBytes());
int MAX_SIZE = 16384; //16k
byte[] buffer = new byte[MAX_SIZE];
while (true) {
int read = in.read(buffer, 0, MAX_SIZE); //læs op til MAX_SIZE tegn.
System.out.println("Modtaget: "+new String(buffer, 0, read));
/* Hop ud af while-løkken hvis vi har læst mindre end MAX_SIZE */
if (read < MAX_SIZE)
break;
}
sock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Multitrådet TCP echo server:
Som lovet løser vi nu et problem i eksempel 4 – nemlig at vi kun understøttede én klient af gangen. Løsningen er at dele koden ud så alle blocking-events for hver socket klares i separate tråde. Tråd klassen kan findes i eksempel 7 og programmet der starter trådene findes i eksempel 8. Tilsammen er der tilføjet én linie kode – for at starte nye tråde:
new Thread(new EchoThread(sock)).start();
Eksempel 7:
import java.net.Socket;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
class EchoThread implements Runnable {
private Socket m_socket;
public EchoThread(Socket socket) {
m_socket = socket;
}
public void run() {
try {
InputStream in = m_socket.getInputStream(); //Stream til indkommende data
OutputStream out = m_socket.getOutputStream(); //Stream til udgående data
byte[] buffer = new byte[16384]; //16kb buffer
while (true) {
int read = in.read(buffer, 0, buffer.length); //læs op til MAX_SIZE tegn.
if (read == -1)
break; //echo ikke hvis der ikke kunne læses data
out.write(buffer, 0, read); //skriv tegn tilbage til klienten
}
System.out.println("[server] Forbindelse afbrudt..");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Eksempel 8:
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.net.BindException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class ThreadedEchoServer {
public static void main(String[] args) {
final int port = 10000; //porten vi vil lytte på.
System.out.println("[server] Starter echo server på port "+port);
ServerSocket srv = null;
try {
srv = new ServerSocket(port);
} catch (BindException be) {
System.out.println("[server] Kunne ikke lytte på "+port);
System.exit(1);
} catch (IOException e) {
System.out.println("[server] Kunne ikke åbne server-socket");
e.printStackTrace();
System.exit(1);
}
Socket sock = null;
try {
while ((sock = srv.accept()) != null) {
InetAddress remoteHost = sock.getInetAddress();
int remotePort = sock.getPort();
System.out.println("[server] Accepterede forbindelse fra "+
remoteHost.getHostAddress()+
":"+remotePort);
new Thread(new EchoThread(sock)).start();
}
} catch (IOException e) {
System.out.println("[server] Fejl: Input/Output fejl");
e.printStackTrace();
System.exit(1);
}
}
}
UDP echo server:
For at vi kan modtage UDP pakker (Datagrammer) skal vi oprette en DatagramSocket og binde den til en bestemt port. Lige som i TCP delen angiver vi porten i konstruktoren. Vi skal også fange en eventuel SocketException.
Eksempel 9:
DatagramSocket sock = null;
try {
sock = new DatagramSocket(10000);
} catch (SocketException e) {
System.out.println("[server] Kunne ikke lytte på porten");
e.printStackTrace();
System.exit(1);
}
Dette eksempel vil oprette en DatagramSocket bundet til port 10000.
Vi skal også have oprettet en byte-buffer til at indeholde data som vil blive indkapslet i en DatagramPacket. Vi lader bufferen være af en størrelse så en hel UDP pakke kan være i den.
Eksempel 10:
/* Opret buffer til at modtage data */
byte[] buffer = null;
try {
buffer = new byte[sock.getReceiveBufferSize()];
} catch (SocketException se) {
System.out.println("[server] Kunne ikke oprette buffer");
se.printStackTrace();
System.exit(1);
}
/* Opret en DatagramPacket med en given buffer */
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
Nu er vi klar til at modtage data, hvilket gores via DatagramSocket’s receive() metode. Som altid er det rart at vide hvem afsenderen er, hvilket vi går i følgende eksempel.
Eksempel 11:
sock.receive(dp); //modtag datagram
/* Find pakkens kilde og udskriv den */
InetAddress remoteHost = dp.getAddress();
int remotePort = dp.getPort();
Så mangler vi blot at sende UDP pakker – hvilket gøres vha. DatagramSocket’s send() metode. Nu har vi alle delene til en UDP Echo server.
Eksempel 12:
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.SocketException;
import java.io.IOException;
import java.net.InetAddress;
class EchoServer {
public static void main(String[] args) {
int port = 10000; //porten vi vil lytte på.
System.out.println("[server] Starter echo server på port "+port);
/* Opret en DatagramSocket der lytter efter beskeder på porten "port" */
DatagramSocket sock = null;
try {
sock = new DatagramSocket(port);
} catch (SocketException e) {
System.out.println("[server] Kunne ikke lytte på porten");
e.printStackTrace();
System.exit(1);
}
/* Opret buffer til at modtage data */
byte[] buffer = null;
try {
buffer = new byte[sock.getReceiveBufferSize()];
} catch (SocketException se) {
System.out.println("[server] Kunne ikke oprette buffer");
se.printStackTrace();
System.exit(1);
}
/* Opret en DatagramPacket med en given buffer */
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
System.out.println("[server] Lytter på port "+port);
/* Loop og lyt efter pakker */
while (true) {
try {
sock.receive(dp); //modtag datagram
/* Find pakkens kilde og udskriv den */
InetAddress remoteHost = dp.getAddress();
int remotePort = dp.getPort();
System.out.println("[server] Modtog data fra "+
remoteHost.getHostAddress()+":"+remotePort);
sock.send(dp); //send pakken tilbage.
} catch (IOException ioe) {
System.out.println("[server] Der opstod en I/O fejl.");
ioe.printStackTrace();
System.exit(1);
}
}
}
}
UDP klient:
For at kunne sende UDP pakker skal vi som det første finde en InetAddress for den process vi vil sende pakken til.
Eksempel 13:
/* Vi skal først have en InetAddress for fjernværten */
InetAddress remoteHost = null;
try {
remoteHost = InetAddress.getByName("localhost");
} catch (UnknownHostException uhe) {
System.out.println("[server] Kunne ikke resolve værtsnavnet");
uhe.printStackTrace();
System.exit(1);
}
InetAddress.getByName() kaster en UnknownHostException hvis et værtsnavns tilhørende InetAddress ikke kan bestemmes.
Vi skal igen oprette en DatagramSocket som pakker kan sendes igennem. Vi skal ikke angive en destination i denne omgang – det skal i stedet gøres i hver enkelt DatagramPacket.
Eksempel 14:
/* Opret en DatagramSocket */
DatagramSocket sock = null;
try {
sock = new DatagramSocket();
} catch (SocketException e) {
System.out.println("[client] Kunne ikke oprette en socket");
e.printStackTrace();
System.exit(1);
}
For at sende data skal dette selvfølgelig pakkes ind i en DatagramPacket, men først skal det gemmes i et byte-array:
Eksempel 15:
/* Opret buffer til at data der skal sendes og modtages */
byte[] buffer = null;
try {
buffer = new byte[sock.getSendBufferSize()];
} catch (SocketException e) {
System.out.println("[client] Kunne ikke oprette buffer.");
}
String msg = "Dette er en test."; //Beskeden vi vil sende.
/* Kopier msg til buffer */
System.arraycopy(msg.getBytes(), 0, buffer, 0, msg.length());
Så er det tid til at lave en DatagramPacket vi kan sende. Denne DatagramPacket skal indeholde 3 ting: Data, fjernvært og portnummer:
Eksempel 16:
DatagramPacket dp = new DatagramPacket(buffer, msg.length(), remoteHost, remotePort);
Sidste skridt er at sende pakken og modtage et eventuelt svar. Disse metoder blev beskrevet i serverdelen. Vi har dermed en komplet UDP klient:
Eksempel 17:
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.SocketException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
class UDPClient {
public static void main(String[] args) {
/* Vi skal først have en InetAddress for fjernværten */
InetAddress remoteHost = null;
try {
remoteHost = InetAddress.getByName("localhost");
} catch (UnknownHostException uhe) {
System.out.println("[server] Kunne ikke resolve værtsnavnet");
uhe.printStackTrace();
System.exit(1);
}
int remotePort = 10000; //port vi vil sende til
/* Opret en DatagramSocket */
DatagramSocket sock = null;
try {
sock = new DatagramSocket();
} catch (SocketException e) {
System.out.println("[client] Kunne ikke oprette en socket");
e.printStackTrace();
System.exit(1);
}
/* Opret buffer til at data der skal sendes og modtages */
byte[] buffer = null;
try {
buffer = new byte[sock.getSendBufferSize()];
} catch (SocketException e) {
System.out.println("[client] Kunne ikke oprette buffer.");
}
String msg = "Dette er en test."; //Beskeden vi vil sende.
/* Kopier msg til buffer */
System.arraycopy(msg.getBytes(), 0, buffer, 0, msg.length());
/* Opret Datagram socket der indeholder:
- Vores besked
- Adresse samt portnummer til fjernvært
*/
DatagramPacket dp = new DatagramPacket(buffer, msg.length(),
remoteHost, remotePort);
try {
sock.send(dp); //Send UDP pakken
sock.receive(dp); //Modtag en UDP pakke
/* Lav en String med det indkomne data og udskriv den */
String recv = new String(dp.getData(), 0, dp.getLength());
System.out.println(recv);
} catch (IOException ioe) {
System.out.println("[server] Der opstod en I/O fejl.");
ioe.printStackTrace();
System.exit(1);
}
}
}
Konklusion
Det var så det. Jeg håber du har fået noget ud af artiklen
Som nævnt i indledningen kan eksempelkoden hentes på
http://udvikleren.dk/articlefiles/java-networking.zip
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (7)
meget interesant artikel. Eksemplerne er korte og konkrete.
Nice!
jeg har altid godt kunne tænke mig at lave en TelNet-agtig klient i JAVA... Dette her er godt lavet!
Nice!
jeg har altid godt kunne tænke mig at lave en TelNet-agtig klient i JAVA... Dette her er godt lavet!
Det toh noget tid at læse. Og god læse stof. Og fin forklaring.
Denne artikel har helt sikkert kickstartet min forståelse af TCP i java... god artikel!
Nice... top fed artikkel
God artikel
Du skal være
logget ind for at skrive en kommentar.