Socket programming in Java: Un tutorial

Questo tutorial è un’introduzione ai socket programming in Java, a partire da un semplice client-server di esempio che illustrano le caratteristiche di base di Java I/O. sarete introdotti sia originale java.io pacchetto e NIO, il non-blocking I/O (java.nio) Api introdotte in Java 1.4. Infine, vedrai un esempio che dimostra Java networking come implementato da Java 7 in avanti, in NIO.2.,

La programmazione del socket si riduce a due sistemi che comunicano tra loro. Generalmente, la comunicazione di rete è disponibile in due versioni: Transport Control Protocol (TCP) e User Datagram Protocol (UDP). TCP e UDP sono utilizzati per scopi diversi ed entrambi hanno vincoli unici:

  • TCP è un protocollo relativamente semplice e affidabile che consente a un client di effettuare una connessione a un server e ai due sistemi di comunicare. In TCP, ogni entità sa che i suoi payload di comunicazione sono stati ricevuti.,
  • UDP è un protocollo senza connessione ed è adatto per scenari in cui non è necessariamente necessario che ogni pacchetto arrivi a destinazione, come lo streaming multimediale.

Per apprezzare la differenza tra TCP e UDP, considera cosa accadrebbe se tu stessi streaming video dal tuo sito web preferito e cadessi fotogrammi. Preferisci che il client rallenti il tuo filmato per ricevere i fotogrammi mancanti o preferisci che il video continui a essere riprodotto? I protocolli di streaming video in genere sfruttano UDP., Poiché TCP garantisce la consegna, è il protocollo di scelta per HTTP, FTP, SMTP, POP3 e così via.

In questo tutorial, vi presento alla programmazione socket in Java. Presento una serie di esempi client-server che dimostrano le funzionalità del framework I/O Java originale, quindi avanzano gradualmente all’utilizzo delle funzionalità introdotte in NIO.2.

Socket Java vecchia scuola

Nelle implementazioni precedenti a NIO, il codice del socket client Java TCP viene gestito dalla classejava.net.Socket., Il seguente codice apre una connessione a un server:

Socket socket = new Socket( server, port );

Una volta che la nostra istanza socket è connessa al server, possiamo iniziare a ottenere flussi di input e output sul server. I flussi di input vengono utilizzati per leggere i dati dal server mentre i flussi di output vengono utilizzati per scrivere dati sul server., Possiamo eseguire i seguenti metodi per ottenere flussi di input e output:

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

Poiché questi sono flussi ordinari, gli stessi flussi che useremmo per leggere e scrivere su un file, possiamo convertirli nel modulo che meglio serve al nostro caso d’uso. Ad esempio, potremmo avvolgere OutputStream con un PrintStream in modo da poter scrivere facilmente testo con metodi come println()., Per un altro esempio, potremmo avvolgere il InputStreamcon unBufferedReader, tramite unInputStreamReader, al fine di leggere facilmente il testo con metodi comereadLine().

scarica

Codice sorgente per ” Programmazione socket in Java: un tutorial.”Creato da Steven Haines per JavaWorld.

Java socket client example

Lavoriamo attraverso un breve esempio che esegue un HTTP GET contro un server HTTP., HTTP è più sofisticato del nostro esempio, ma possiamo scrivere codice client per gestire il caso più semplice: richiedere una risorsa dal server e il server restituisce la risposta e chiude il flusso. Questo caso richiede i seguenti passaggi:

  1. Creare un socket per il server web in ascolto sulla porta 80.
  2. Ottenere un PrintStreamal server e inviare la richiestaGET PATH HTTP/1.0, dovePATH è la risorsa richiesta sul server., Ad esempio, se volessimo aprire la radice di un sito web, il percorso sarebbe /.
  3. Ottenere un InputStreamsul server, avvolgerlo con unBufferedReader e leggere la risposta riga per riga.

Il listato 1 mostra il codice sorgente di questo esempio.

Elenco 1. Un esempio semplice.java

Listato 1 accetta due argomenti della riga di comando: il server a cui connettersi (supponendo che ci stiamo connettendo al server sulla porta 80) e la risorsa da recuperare., Crea unSocket che punta al server e specifica esplicitamente la porta80. Quindi esegue il comando:

GET PATH HTTP/1.0

Ad esempio:

GET / HTTP/1.0

Cosa è appena successo?

Quando si recupera una pagina web da un server web, ad esempio , HTTP, il client utilizza i server DNS per trovare l’indirizzo del server: si inizia chiedendo il top a livello di server di dominio per il com dominio in cui l’autorevole domain-name server per il ., Quindi chiede al server dei nomi di dominio l’indirizzo IP (o gli indirizzi) per . Successivamente, apre un socket a quel server sulla porta 80. (Oppure, se si desidera definire una porta diversa, è possibile farlo aggiungendo due punti seguiti dal numero di porta, ad esempio: :8080. E ) infine, il client HTTP esegue specificato metodo HTTP, ad esempio GET POST PUT DELETE HEAD o OPTI/ONS. Ogni metodo ha la sua sintassi., Come mostrato negli snips di codice sopra, il metodoGET richiede un percorso seguito daHTTP/version number e una riga vuota. Se volessimo aggiungere intestazioni HTTP, avremmo potuto farlo prima di entrare nella nuova riga.

Nel listato 1, abbiamo recuperato unOutputStreame lo abbiamo avvolto in unPrintStream in modo da poter eseguire più facilmente i nostri comandi basati su testo., Il nostro codice ha ottenuto un InputStream, lo ha avvolto in un InputStreamReader, che lo ha convertito in un Reader, e poi lo ha avvolto in un BufferedReader. Abbiamo usato ilPrintStream per eseguire il nostro metodoGET e poi abbiamo usato ilBufferedReader per leggere la risposta riga per riga fino a quando non abbiamo ricevuto una rispostanull, indicando che il socket era stato chiuso.,

Ora esegui questa classe e passale i seguenti argomenti:

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

Dovresti vedere un output simile a quello qui sotto:

Questo output mostra una pagina di test sul sito web di JavaWorld. Ha risposto che parla HTTP versione 1.1 e la risposta è 200 OK.

Java socket server example

Abbiamo coperto il lato client e fortunatamente l’aspetto della comunicazione del lato server è altrettanto semplice., Da una prospettiva semplicistica, il processo è il seguente:

  1. Crea unServerSocket, specificando una porta su cui ascoltare.
  2. Richiamare il metodoServerSocketaccept() per ascoltare sulla porta configurata per una connessione client.
  3. Quando un client si connette al server, il metodoaccept()restituisce unSocket attraverso il quale il server può comunicare con il client., Questa è la stessa classe Socket che abbiamo usato per il nostro client, quindi il processo è lo stesso: ottenere un InputStream da leggere dal client e un OutputStream scrivere al client.
  4. Se il tuo server deve essere scalabile, dovrai passare Socket a un altro thread da elaborare in modo che il tuo server possa continuare ad ascoltare per ulteriori connessioni.
  5. Chiama di nuovo il metodoServerSocketaccept() per ascoltare un’altra connessione.,

Come vedrai presto, la gestione di NIO di questo scenario sarebbe un po ‘ diversa., Per ora, però, siamo in grado di creare direttamente un ServerSocket passando una porta di ascolto (per maggiori informazioni sui ServerSocketFactorynella prossima sezione):

ServerSocket serverSocket = new ServerSocket( port );

E ora siamo in grado di accettare connessioni in entrata tramite il accept() metodo:

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

la programmazione Multithreading in Java con sockets

Listato 2, di seguito, mette tutti i server di codice finora, in modo un po ‘ più robusto di esempio che utilizza thread per gestire più richieste., Il server mostrato è un server echo, il che significa che riecheggia qualsiasi messaggio che riceve.

Mentre l’esempio nel listato 2 non è complicato, anticipa alcuni di ciò che verrà nella prossima sezione su NIO. Prestare particolare attenzione alla quantità di codice di threading che dobbiamo scrivere per costruire un server in grado di gestire più richieste simultanee.

Elenco 2. SimpleSocketServer.java

Nel listato 2 creiamo una nuova istanzaSimpleSocketServer e avviiamo il server., Questo è necessario perché il SimpleSocketServer extends Thread per creare un nuovo thread per gestire il blocco accept() chiamata che si vede nel read() metodo. Il metodorun() si trova in un ciclo che accetta le richieste del client e creaRequestHandler thread per elaborare la richiesta. Ancora una volta, questo è un codice relativamente semplice, ma comporta anche una buona quantità di programmazione filettata.,

si noti anche che il RequestHandler gestisce il client di comunicazione molto simile al codice nel Listato 1 fatto: si avvolge il OutputStream con un PrintStream per facilitare la scrittura e, allo stesso modo, avvolge il InputStream con un BufferedReader per una facile lettura. Per quanto riguarda un server, legge le righe dal client e le riecheggia al client. Se il client invia una riga vuota, la conversazione è terminata e RequestHandler chiude il socket.

NIO e NIO.,2

Per molte applicazioni, il modello di programmazione socket Java di base che abbiamo appena esplorato è sufficiente. Per le applicazioni che coinvolgono I / O più intensi o input/output asincroni, è necessario avere familiarità con le API non bloccanti introdotte in Java NIO e NIO.2.

Il pacchetto JDK 1.4 NIO offre le seguenti caratteristiche principali:

  • I canali sono progettati per supportare i trasferimenti di massa da un buffer NIO a un altro.
  • I buffer rappresentano un blocco contiguo di memoria interfacciato da un semplice insieme di operazioni.,
  • L’input/Output non bloccante è un insieme di classi che espongono i canali a sorgenti I / O comuni come file e socket.

Durante la programmazione con NIO, si apre un canale verso la destinazione e quindi si leggono i dati in un buffer dalla destinazione, si scrivono i dati in un buffer e si inviano alla destinazione., Ci immergeremo in impostazione di una presa e l’ottenimento di un canale da poco, ma prima andiamo a rivedere il processo di utilizzo di un buffer di:

  1. Scrivere i dati in un buffer
  2. Chiamare il buffer flip() metodo per prepararlo per la lettura
  3. Leggere i dati dal buffer
  4. Chiamare il buffer clear() o compact() metodo per prepararla a ricevere più dati

Quando i dati vengono scritti nel buffer, buffer conosce la quantità di dati scritti in esso., Mantiene tre proprietà, i cui significati diversi se il buffer è in modalità di lettura o di scrittura:

  • Posizione: In modalità di scrittura, la posizione iniziale è 0 e contiene la posizione corrente viene scritto nel buffer; dopo si ribalta un buffer per metterlo in modalità di lettura, si ripristina la posizione a 0 e contiene la posizione corrente nel buffer di lettura,
  • Capacità: Fissa la dimensione del buffer
  • Limite: In modalità di scrittura, il limite che definisce la quantità di dati possono essere scritti nel buffer; in modalità di lettura, il limite che definisce la quantità di dati che possono essere letti dal buffer.,

Demo I / O Java: server Eco con NIO.2

NIO.2, che è stato introdotto in JDK 7, estende le librerie I / O non bloccanti di Java per aggiungere il supporto per le attività del filesystem, come il pacchetto java.nio.file e java.nio.file.Path ed espone una nuova API del file System. Con questo background in mente, scriviamo un nuovo server Echo usando NIO.2 di AsynchronousServerSocketChannel.

AsynchronousServerSocketChannel fornisce un canale asincrono non bloccante per socket di ascolto orientati al flusso., Per poterlo utilizzare, eseguiamo prima il suo metodo statico open() e poi bind() su una porta specifica. Successivamente, eseguiremo il suo metodoaccept(), passando ad esso una classe che implementa l’interfacciaCompletionHandler. Molto spesso, troverai quel gestore creato come una classe interna anonima.

Il listato 3 mostra il codice sorgente del nostro nuovo server Echo asincrono.

Elenco 3. SimpleSocketServer.,java

Nel Listato 3 prima cosa è necessario creare un nuovo AsynchronousServerSocketChannel e poi associarlo alla porta 5000:

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

questo AsynchronousServerSocketChannel, invochiamo accept() dirgli di mettersi in ascolto per le connessioni, con il passaggio di un custom CompletionHandler istanza. Quando invochiamo accept(), ritorna immediatamente., Si noti che questo esempio è diverso dalla classeServerSocket nel listato 1; mentre ilaccept() metodo bloccato fino a quando un client collegato ad esso, ilAsynchronousServerSocketChannelaccept() metodo gestisce per noi.

Il gestore di completamento

La nostra prossima responsabilità è creare una classeCompletionHandlere fornire un’implementazione dei metodicompleted()efailed()., Il metodocompleted() viene chiamato quandoAsynchronousServerSocketChannel riceve una connessione da un client e include unAsynchronousSocketChannel al client. Il metodo completed() accetta prima la connessione dal AsynchronousServerSocketChannel e quindi inizia a comunicare con il client. La prima cosa che fa è scrivere un messaggio “Hello”: costruisce una stringa, la converte in un array di byte e poi la passa a ByteBuffer.wrap() per costruire un ByteBuffer., IlByteBuffer può quindi essere passatoAsynchronousSocketChannel‘swrite() metodo.

Per leggere dal client, creiamo un nuovo ByteBuffer invocando il suo allocate(4096) (che crea un buffer 4K), quindi invochiamo il AsynchronousSocketChannelread() metodo. Ilread() restituisce unFuture<Integer> su cui possiamo richiamareget() per recuperare il numero di byte letti dal client., In questo esempio, passiamo get()un valore di timeout di 20 secondi: se non otteniamo una risposta in 20 secondi, il metodoget()genererà unTimeoutException. La nostra regola per questo server echo è che se osserviamo 20 secondi di silenzio, interrompiamo la conversazione.

Successivamente controlliamo la posizione del buffer, che sarà la posizione dell’ultimo byte ricevuto dal client. Se il client invia una riga vuota, riceviamo due byte: un ritorno a capo e un feed di riga., Il controllo assicura che se il client invia una riga vuota che prendiamo come un indicatore che il client è finito con la conversazione. Se abbiamo dati significativi, chiamiamo il metodoByteBufferflip() per prepararlo alla lettura. Creiamo un array di byte temporaneo per contenere il numero di byte letti dal client e quindi invochiamo ByteBufferget() per caricare i dati in quell’array di byte. Infine, convertiamo l’array di byte in una stringa creando una nuova istanzaString., Riecheggiamo la riga al client convertendo la stringa in un array di byte, passandola al metodoByteBuffer.wrap() e invocando il metodoAsynchronousSocketChannelwrite(). Ora abbiamoclear() ilByteBuffer, che richiama significa che riposiziona ilposition a zero e mette ilByteBuffer in modalità di scrittura, e poi leggiamo la riga successiva dal client.,

L’unica cosa di cui essere a conoscenza è che il metodomain(), che crea il server, imposta anche un timer di 60 secondi per mantenere l’applicazione in esecuzione. Poiché ilAsynchronousSocketChannel‘saccept() metodo restituisce immediatamente, se non abbiamo ilThread.sleep() allora la nostra applicazione si fermerà immediatamente.,

Per verificarlo, avviare il server e connettersi ad esso utilizzando un client telnet:

telnet localhost 5000

Inviare alcune stringhe al server, osservare che vengono riecheggiate e quindi inviare una riga vuota per terminare la conversazione.

In conclusione

In questo articolo ho presentato due approcci alla programmazione socket con Java: l’approccio tradizionale introdotto con Java 1.0 e i più recenti, non bloccanti NIO e NIO.2 approcci introdotti in Java 1.4 e Java 7, rispettivamente., Hai visto diverse iterazioni di un client Java socket e un esempio di server Java socket, dimostrando sia l’utilità di I/O Java di base che alcuni scenari in cui l’I/O non bloccante migliora il modello di programmazione Java socket. Utilizzando I / O non bloccanti, è possibile programmare applicazioni in rete Java per gestire più connessioni simultanee senza dover gestire più raccolte di thread. È inoltre possibile sfruttare la nuova scalabilità del server integrata in NIO e NIO.2.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *