Java'da soket programlama: Bir eğitim

Bu öğretici, Java G / Ç'nin temel özelliklerini gösteren basit bir istemci-sunucu örneğiyle başlayarak, Java'da soket programlamaya giriş niteliğindedir. Hem orijinal java.io paket hem de  java.nioJava 1.4'te sunulan engellemesiz G / Ç ( ) API'leri olan NIO ile tanışacaksınız . Son olarak, NIO.2'de Java 7'den itibaren uygulanan Java ağını gösteren bir örnek göreceksiniz.

Soket programlama, birbiriyle iletişim kuran iki sisteme indirgenir. Genel olarak, ağ iletişimi iki şekilde gelir: Aktarım Kontrol Protokolü (TCP) ve Kullanıcı Datagram Protokolü (UDP). TCP ve UDP farklı amaçlar için kullanılır ve her ikisinin de benzersiz kısıtlamaları vardır:

  • TCP, bir istemcinin bir sunucuya bağlantı kurmasını ve iki sistemin iletişim kurmasını sağlayan nispeten basit ve güvenilir bir protokoldür. TCP'de her varlık, iletişim yüklerinin alındığını bilir.
  • UDP, bağlantısız bir protokoldür ve medya akışı gibi her paketin hedefine ulaşmasına gerek duymadığınız senaryolar için iyidir.

TCP ve UDP arasındaki farkı anlamak için, en sevdiğiniz web sitesinden video akışı yapıyorsanız ve kareleri atlasaydınız ne olacağını düşünün. Müşterinin eksik kareleri almak için filminizi yavaşlatmasını mı yoksa videonun oynatılmaya devam etmesini mi tercih edersiniz? Video akış protokolleri tipik olarak UDP'den yararlanır. TCP teslimatı garanti ettiğinden, HTTP, FTP, SMTP, POP3 vb. İçin tercih edilen protokoldür.

Bu eğitimde, size Java'da soket programlamayı tanıtacağım. Orijinal Java I / O çerçevesindeki özellikleri gösteren bir dizi istemci-sunucu örneği sunuyorum, ardından kademeli olarak NIO.2'de sunulan özellikleri kullanmaya geçiyorum.

Eski usul Java soketleri

NIO'dan önceki uygulamalarda, Java TCP istemci soket kodu java.net.Socketsınıf tarafından işlenir . Aşağıdaki kod bir sunucuya bağlantı açar:

 Soket soket = yeni Soket (sunucu, bağlantı noktası); 

Örneğimiz socketsunucuya bağlandıktan sonra, sunucuya giriş ve çıkış akışları almaya başlayabiliriz. Çıkış akışları sunucuya veri yazmak için kullanılırken, giriş akışları sunucudan veri okumak için kullanılır. Giriş ve çıkış akışlarını elde etmek için aşağıdaki yöntemleri uygulayabiliriz:

InputStream in = socket.getInputStream (); OutputStream çıkışı = socket.getOutputStream ();

Bunlar sıradan akışlar olduğundan, bir dosyadan okumak ve bir dosyaya yazmak için kullandığımız akışların aynısı, bunları kullanım durumumuza en iyi hizmet eden forma dönüştürebiliriz. Örneğin, gibi yöntemlerle kolayca metin yazabilmemiz için OutputStreama ile kaydırabiliriz . Başka bir örnek olarak, metni . Gibi yöntemlerle kolayca okumak için a ile , bir yoluyla sarabiliriz .PrintStreamprintln()InputStreamBufferedReaderInputStreamReaderreadLine()

indir "Java'da soket programlama: bir eğitim" için kaynak kodu Kaynak kodunu indirin. Steven Haines tarafından JavaWorld için oluşturuldu.

Java soket istemcisi örneği

Bir HTTP sunucusuna karşı bir HTTP GET çalıştıran kısa bir örnek üzerinde çalışalım. HTTP, örneğimizin izin verdiğinden daha karmaşıktır, ancak en basit durumu işlemek için istemci kodu yazabiliriz: sunucudan bir kaynak isteyin ve sunucu yanıtı döndürerek akışı kapatır. Bu durum aşağıdaki adımları gerektirir:

  1. 80 numaralı bağlantı noktasını dinleyen web sunucusuna bir soket oluşturun.
  2. Bir elde PrintStreamsunucuya ve isteği göndermek GET PATH HTTP/1.0, PATHsunucu üzerinde istenen bir kaynaktır. Örneğin, bir web sitesinin kökünü açmak istersek yol olur /.
  3. Bir elde InputStream, sunucuya bir ile sarın BufferedReaderve tepki hattını-by-line okuyun.

Liste 1, bu örnek için kaynak kodunu gösterir.

Listeleme 1. SimpleSocketClientExample.java

paket com.geekcap.javaworld.simplesocketclient; java.io.BufferedReader'ı içe aktarın; java.io.InputStreamReader'ı içe aktarın; java.io.PrintStream'i içe aktarın; java.net.Socket içe aktarma; public class SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Kullanım: SimpleSocketClientExample"); System.exit (0); } Dize sunucusu = değiştirgeler [0]; Dize yolu = bağımsız değişkenler [1]; System.out.println ("URL içeriği yükleniyor:" + sunucu); deneyin {// Sunucuya bağlanın Socket socket = new Socket (server, 80); // Sunucudan okumak ve sunucudan yazmak için girdi ve çıktı akışları oluşturun PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = new BufferedReader (yeni InputStreamReader (socket.getInputStream ())); // GET HTTP / 1'in HTTP protokolünü izleyin.0 ve ardından boş bir satır out.println ("GET" + yol + "HTTP / 1.0"); out.println (); // Belgeyi okumayı bitirene kadar sunucudaki verileri okuyun String line = in.readLine (); while (satır! = boş) {System.out.println (satır); satır = in.readLine (); } // Akışlarımızı in.close () kapatın; out.close (); socket.close (); } catch (İstisna e) {e.printStackTrace (); }}}

Liste 1, iki komut satırı bağımsız değişkenini kabul eder: bağlanılacak sunucu (sunucuya bağlantı noktası 80'den bağlandığımızı varsayarsak) ve alınacak kaynak. SocketSunucuya işaret eden ve açıkça bağlantı noktasını belirten bir oluşturur 80. Daha sonra şu komutu yürütür:

YOLU AL HTTP / 1.0 

Örneğin:

GET / HTTP / 1.0 

Az önce ne oldu?

www.google.comHTTP istemcisi gibi bir web sunucusundan bir web sayfası aldığınızda , HTTP istemcisi sunucunun adresini bulmak için DNS sunucularını kullanır: comyetkili etki alanı-adı sunucusunun www.google.com. Daha sonra alan adı sunucusundan IP adresini (veya adreslerini) ister www.google.com. Daha sonra, bu sunucuya 80 numaralı bağlantı noktasında bir soket açar. (Veya, farklı bir bağlantı noktası tanımlamak istiyorsanız, bunu iki nokta üst üste ve ardından bağlantı noktası numarası ekleyerek yapabilirsiniz, örneğin :8080:.) Son olarak, HTTP istemcisi çalıştırır. belirtilen HTTP yöntemi, örneğin GET, POST, PUT, DELETE, HEAD, ya da OPTI/ONS. Her yöntemin kendi sözdizimi vardır. Yukarıdaki kod kesimlerinde gösterildiği gibi, GETyöntem bir yol ve ardındanHTTP/version numberve boş bir satır. HTTP başlıkları eklemek isteseydik, bunu yeni satıra girmeden önce yapabilirdik.

Liste 1'de, metin tabanlı komutlarımızı daha kolay çalıştırabilmemiz için bir tane aldık OutputStreamve onu bir içine PrintStreamsardık. Kodumuz bir elde InputStreametti InputStreamReader, onu bir Readeriçine sararak, onu a'ya dönüştürdü ve sonra onu bir BufferedReader. Biz kullanılan PrintStreambizim yürütmek için GETbir yöntem ve daha sonra kullanılan BufferedReaderbir alana kadar tepki hattını-by-line okumak için nullsoket kapalı olduğunu belirten yanıtı.

Şimdi bu sınıfı çalıştırın ve ona aşağıdaki argümanları iletin:

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

Aşağıdakine benzer çıktı görmelisiniz:

URL içeriği yükleniyor: www.javaworld.com HTTP / 1.1 200 Tamam Tarih: Paz, 21 Eylül 2014 22:20:13 GMT Sunucu: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Benzinli-Yerel X-Benzinli-Yaş: 8 İçerik-Uzunluk: 168 Son Değiştirilme Tarihi: Sal, 24 Ocak 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" İçerik Türü : text / html Vary: Accept-Encoding Bağlantısı: Benzin Test Sayfasını kapat

Başarı

Bu çıktı, JavaWorld web sitesinde bir test sayfasını gösterir. HTTP 1.1 sürümünü konuştuğunu ve yanıtın olduğunu söyledi 200 OK.

Java soket sunucusu örneği

İstemci tarafını ele aldık ve neyse ki sunucu tarafının iletişim yönü de aynı derecede kolay. Basit bir bakış açısıyla, süreç şu şekildedir:

  1. ServerSocketDinlenecek bir bağlantı noktası belirterek bir oluşturun .
  2. Bir istemci bağlantısı için yapılandırılmış bağlantı noktasını dinlemek için ServerSocket'ın accept()yöntemini çağırın .
  3. Bir istemci sunucuya bağlandığında, accept()yöntem Socket, sunucunun istemciyle iletişim kurabileceği bir yöntem döndürür . Bu, Socketmüşterimiz için kullandığımız sınıfın aynısıdır , dolayısıyla süreç aynıdır: InputStreamistemciden okumak için bir alın ve müşteriye bir OutputStreamyazı yazın.
  4. Sunucunuzun ölçeklenebilir olması gerekiyorsa Socket, sunucunuzun ek bağlantılar için dinlemeye devam edebilmesi için işlemek için başka bir iş parçacığına geçmek isteyeceksiniz .
  5. Başka bir bağlantıyı dinlemek için ServerSocket'ın accept()yöntemini yeniden arayın .

Yakında göreceğiniz gibi, NIO'nun bu senaryoyu ele alışı biraz farklı olacaktır. Şimdilik, ServerSocketdinlemek için bir bağlantı noktası geçirerek doğrudan bir a oluşturabiliriz ( ServerSocketFactorysonraki bölümde s hakkında daha fazla bilgi ):

 ServerSocket serverSocket = new ServerSocket (port); 

Ve şimdi gelen bağlantıları şu accept()yöntemle kabul edebiliriz :

Soket soketi = serverSocket.accept (); // Bağlantıyı halledin ...

Java soketleriyle çok iş parçacıklı programlama

Aşağıdaki Liste 2, şimdiye kadarki tüm sunucu kodunu, birden çok isteği işlemek için iş parçacıkları kullanan biraz daha sağlam bir örnek halinde bir araya getiriyor. Gösterilen sunucu bir yankı sunucusudur , yani aldığı herhangi bir mesajı geri yansıtır.

Liste 2'deki örnek karmaşık olmasa da, NIO ile ilgili bir sonraki bölümde gelecek olanların bir kısmını tahmin ediyor. Aynı anda birden çok isteği karşılayabilecek bir sunucu oluşturmak için yazmamız gereken iş parçacığı kodu miktarına özellikle dikkat edin.

Liste 2. SimpleSocketServer.java

paket com.geekcap.javaworld.simplesocketclient; java.io.BufferedReader'ı içe aktarın; import java.io.I / OException; java.io.InputStreamReader'ı içe aktarın; java.io.PrintWriter içe aktarımı; java.net.ServerSocket'i içe aktar; java.net.Socket içe aktarma; public class SimpleSocketServer, Thread {private ServerSocket serverSocket'i genişletir; özel int bağlantı noktası; private boolean running = false; public SimpleSocketServer (int bağlantı noktası) {this.port = bağlantı noktası; } public void startServer () {deneyin {serverSocket = new ServerSocket (port); this.start (); } catch (I / OException e) {e.printStackTrace (); }} genel void stopServer () {çalışıyor = false; this.interrupt (); } @Override public void run () {running = true; while (çalışırken) {{System.out.println'i deneyin ("Bağlantı için dinleme"); // Sonraki bağlantıyı almak için accept () 'yi çağırın Socket socket = serverSocket.accept ();// Soketi, RequestHandler'ı işlemek için RequestHandler iş parçacığına iletin requestHandler = new RequestHandler (soket); requestHandler.start (); } catch (I / OException e) {e.printStackTrace (); }}} public static void main (String [] değiştirgeler) {if (args.length == 0) {System.out.println ("Kullanım: SimpleSocketServer"); System.exit (0); } int bağlantı noktası = Tamsayı.parseInt (değiştirgeler [0]); System.out.println ("Sunucuyu bağlantı noktasından başlat:" + bağlantı noktası); SimpleSocketServer sunucusu = yeni SimpleSocketServer (bağlantı noktası); server.startServer (); // 1 dakika içinde otomatik olarak kapatmayı deneyin {Thread.sleep (60000); } catch (İstisna e) {e.printStackTrace (); } server.stopServer (); }} sınıfı RequestHandler, Thread {private Socket soketini genişletir; RequestHandler (Soket soketi) {this.socket = soket; } @Override public void run () {try {System.out.println ("Bir bağlantı alındı"); // Girdi ve çıktı akışlarını alın BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter çıkışı = new PrintWriter (socket.getOutputStream ()); // Başlığımızı istemciye yazın out.println ("Echo Server 1.0"); out.flush (); // İstemci bağlantıyı kapatana kadar veya boş bir satır alana kadar satırları istemciye geri yankılayın String line = in.readLine (); while (satır! = boş && line.length ()> 0) {out.println ("Yankı:" + satır); out.flush (); satır = in.readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}// Girdi ve çıktı akışlarını alın BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter çıkışı = new PrintWriter (socket.getOutputStream ()); // Başlığımızı istemciye yazın out.println ("Echo Server 1.0"); out.flush (); // İstemci bağlantıyı kapatana kadar veya boş bir satır alana kadar satırları istemciye geri yankılayın String line = in.readLine (); while (satır! = boş && line.length ()> 0) {out.println ("Yankı:" + satır); out.flush (); satır = in.readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}// Girdi ve çıktı akışlarını alın BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); PrintWriter çıkışı = new PrintWriter (socket.getOutputStream ()); // Başlığımızı istemciye yazın out.println ("Echo Server 1.0"); out.flush (); // İstemci bağlantıyı kapatana kadar veya boş bir satır alana kadar satırları istemciye geri yankılayın String line = in.readLine (); while (satır! = boş && line.length ()> 0) {out.println ("Yankı:" + satır); out.flush (); satır = in.readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}// Başlığımızı istemciye yazın out.println ("Echo Server 1.0"); out.flush (); // İstemci bağlantıyı kapatana kadar veya boş bir satır alana kadar satırları istemciye geri yankılayın String line = in.readLine (); while (satır! = boş && line.length ()> 0) {out.println ("Yankı:" + satır); out.flush (); satır = in.readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}// Başlığımızı istemciye yazın out.println ("Echo Server 1.0"); out.flush (); // İstemci bağlantıyı kapatana kadar veya boş bir satır alana kadar satırları istemciye geri yankılayın String line = in.readLine (); while (satır! = boş && line.length ()> 0) {out.println ("Yankı:" + satır); out.flush (); satır = in.readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}readLine (); } // in.close () bağlantımızı kapatın; out.close (); socket.close (); System.out.println ("Bağlantı kapatıldı"); } catch (İstisna e) {e.printStackTrace (); }}}