Java回顧之網絡通訊

在這篇文章裏,咱們主要討論如何使用Java實現網絡通訊,包括TCP通訊、UDP通訊、多播以及NIO。java

  TCP鏈接

  TCP的基礎是Socket,在TCP鏈接中,咱們會使用ServerSocket和Socket,當客戶端和服務器創建鏈接之後,剩下的基本就是對I/O的控制了。數據庫

  咱們先來看一個簡單的TCP通訊,它分爲客戶端和服務器端。編程

  客戶端代碼以下:數組

 
 1 import java.net.*;  2 import java.io.*;  3 public class SimpleTcpClient {  4 
 5     public static void main(String[] args) throws IOException  6  {  7         Socket socket = null;  8         BufferedReader br = null;  9         PrintWriter pw = null; 10         BufferedReader brTemp = null; 11         try
12  { 13             socket = new Socket(InetAddress.getLocalHost(), 5678); 14             br = new BufferedReader(new InputStreamReader(socket.getInputStream())); 15             pw = new PrintWriter(socket.getOutputStream()); 16             brTemp = new BufferedReader(new InputStreamReader(System.in)); 17             while(true) 18  { 19                 String line = brTemp.readLine(); 20  pw.println(line); 21  pw.flush(); 22                 if (line.equals("end")) break; 23  System.out.println(br.readLine()); 24  } 25  } 26         catch(Exception ex) 27  { 28  System.err.println(ex.getMessage()); 29  } 30         finally
31  { 32             if (socket != null) socket.close(); 33             if (br != null) br.close(); 34             if (brTemp != null) brTemp.close(); 35             if (pw != null) pw.close(); 36  } 37  } 38 }
 

  服務器端代碼以下:服務器

 
 1 import java.net.*;  2 import java.io.*;  3 public class SimpleTcpServer {  4 
 5     public static void main(String[] args) throws IOException  6  {  7         ServerSocket server = null;  8         Socket client = null;  9         BufferedReader br = null; 10         PrintWriter pw = null; 11         try
12  { 13             server = new ServerSocket(5678); 14             client = server.accept(); 15             br = new BufferedReader(new InputStreamReader(client.getInputStream())); 16             pw = new PrintWriter(client.getOutputStream()); 17             while(true) 18  { 19                 String line = br.readLine(); 20                 pw.println("Response:" + line); 21  pw.flush(); 22                 if (line.equals("end")) break; 23  } 24  } 25         catch(Exception ex) 26  { 27  System.err.println(ex.getMessage()); 28  } 29         finally
30  { 31             if (server != null) server.close(); 32             if (client != null) client.close(); 33             if (br != null) br.close(); 34             if (pw != null) pw.close(); 35  } 36  } 37 }
 

  這裏的服務器的功能很是簡單,它接收客戶端發來的消息,而後將消息「原封不動」的返回給客戶端。當客戶端發送「end」時,通訊結束。網絡

  上面的代碼基本上勾勒了TCP通訊過程當中,客戶端和服務器端的主要框架,咱們能夠發現,上述的代碼中,服務器端在任什麼時候刻,都只能處理來自客戶 端的一個請求,它是串行處理的,不能並行,這和咱們印象裏的服務器處理方式不太相同,咱們能夠爲服務器添加多線程,當一個客戶端的請求進入後,咱們就建立 一個線程,來處理對應的請求。多線程

  改善後的服務器端代碼以下:框架

 
 1 import java.net.*;  2 import java.io.*;  3 public class SmartTcpServer {  4     public static void main(String[] args) throws IOException  5  {  6         ServerSocket server = new ServerSocket(5678);  7         while(true)  8  {  9             Socket client = server.accept(); 10             Thread thread = new ServerThread(client); 11  thread.start(); 12  } 13  } 14 } 15 
16 class ServerThread extends Thread 17 { 18     private Socket socket = null; 19 
20     public ServerThread(Socket socket) 21  { 22         this.socket = socket; 23  } 24     
25     public void run() { 26         BufferedReader br = null; 27         PrintWriter pw = null; 28         try
29  { 30             br = new BufferedReader(new InputStreamReader(socket.getInputStream())); 31             pw = new PrintWriter(socket.getOutputStream()); 32             while(true) 33  { 34                 String line = br.readLine(); 35                 pw.println("Response:" + line); 36  pw.flush(); 37                 if (line.equals("end")) break; 38  } 39  } 40         catch(Exception ex) 41  { 42  System.err.println(ex.getMessage()); 43  } 44         finally
45  { 46             if (socket != null) 47                 try { 48  socket.close(); 49                 } catch (IOException e1) { 50  e1.printStackTrace(); 51  } 52             if (br != null) 53                 try { 54  br.close(); 55                 } catch (IOException e) { 56  e.printStackTrace(); 57  } 58             if (pw != null) pw.close(); 59  } 60  } 61 }
 

  修改後的服務器端,就能夠同時處理來自客戶端的多個請求了。socket

  在編程的過程當中,咱們會有「資源」的概念,例如數據庫鏈接就是一個典型的資源,爲了提高性能,咱們一般不會直接銷燬數據庫鏈接,而是使用數據庫 鏈接池的方式來對多個數據庫鏈接進行管理,已實現重用的目的。對於Socket鏈接來講,它也是一種資源,當咱們的程序須要大量的Socket鏈接時,如 果每一個鏈接都須要從新創建,那麼將會是一件很是沒有效率的作法。ide

  和數據庫鏈接池相似,咱們也能夠設計TCP鏈接池,這裏的思路是咱們用一個數組來維持多個Socket鏈接,另一個狀態數組來描述每一個 Socket鏈接是否正在使用,當程序須要Socket鏈接時,咱們遍歷狀態數組,取出第一個沒被使用的Socket鏈接,若是全部鏈接都在使用,拋出異 常。這是一種很直觀簡單的「調度策略」,在不少開源或者商業的框架中(Apache/Tomcat),都會有相似的「資源池」。

  TCP鏈接池的代碼以下:

 
 1 import java.net.*;  2 import java.io.*;  3 public class TcpConnectionPool {  4 
 5     private InetAddress address = null;  6     private int port;  7     private Socket[] arrSockets = null;  8     private boolean[] arrStatus = null;  9     private int count; 10     
11     public TcpConnectionPool(InetAddress address, int port, int count) 12  { 13         this.address = address; 14         this.port = port; 15         this .count = count; 16         arrSockets = new Socket[count]; 17         arrStatus = new boolean[count]; 18         
19  init(); 20  } 21     
22     private void init() 23  { 24         try
25  { 26             for (int i = 0; i < count; i++) 27  { 28                 arrSockets[i] = new Socket(address.getHostAddress(), port); 29                 arrStatus[i] = false; 30  } 31  } 32         catch(Exception ex) 33  { 34  System.err.println(ex.getMessage()); 35  } 36  } 37     
38     public Socket getConnection() 39  { 40         if (arrSockets == null) init(); 41         int i = 0; 42         for(i = 0; i < count; i++) 43  { 44             if (arrStatus[i] == false) 45  { 46                 arrStatus[i] = true; 47                 break; 48  } 49  } 50         if (i == count) throw new RuntimeException("have no connection availiable for now."); 51         
52         return arrSockets[i]; 53  } 54     
55     public void releaseConnection(Socket socket) 56  { 57         if (arrSockets == null) init(); 58         for (int i = 0; i < count; i++) 59  { 60             if (arrSockets[i] == socket) 61  { 62                 arrStatus[i] = false; 63                 break; 64  } 65  } 66  } 67     
68     public void reBuild() 69  { 70  init(); 71  } 72     
73     public void destory() 74  { 75         if (arrSockets == null) return; 76         
77         for(int i = 0; i < count; i++) 78  { 79             try
80  { 81  arrSockets[i].close(); 82  } 83             catch(Exception ex) 84  { 85  System.err.println(ex.getMessage()); 86                 continue; 87  } 88  } 89  } 90 }
 

  UDP鏈接

  UDP是一種和TCP不一樣的鏈接方式,它一般應用在對實時性要求很高,對準肯定要求不高的場合,例如在線視頻。UDP會有「丟包」的狀況發生,在TCP中,若是Server沒有啓動,Client發消息時,會報出異常,但對UDP來講,不會產生任何異常。

  UDP通訊使用的兩個類時DatagramSocket和DatagramPacket,後者存放了通訊的內容。

  下面是一個簡單的UDP通訊例子,同TCP同樣,也分爲Client和Server兩部分,Client端代碼以下:

 
 1 import java.net.*;  2 import java.io.*;  3 public class UdpClient {  4 
 5     public static void main(String[] args)  6  {  7         try
 8  {  9             InetAddress host = InetAddress.getLocalHost(); 10             int port = 5678; 11             BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 12             while(true) 13  { 14                 String line = br.readLine(); 15                 byte[] message = line.getBytes(); 16                 DatagramPacket packet = new DatagramPacket(message, message.length, host, port); 17                 DatagramSocket socket = new DatagramSocket(); 18  socket.send(packet); 19  socket.close(); 20                 if (line.equals("end")) break; 21  } 22  br.close(); 23  } 24         catch(Exception ex) 25  { 26  System.err.println(ex.getMessage()); 27  } 28  } 29 }
 

  Server端代碼以下:

 
 1 import java.net.*;  2 import java.io.*;  3 public class UdpServer {  4 
 5     public static void main(String[] args)  6  {  7         try
 8  {  9             int port = 5678; 10             DatagramSocket dsSocket = new DatagramSocket(port); 11             byte[] buffer = new byte[1024]; 12             DatagramPacket packet = new DatagramPacket(buffer, buffer.length); 13             while(true) 14  { 15  dsSocket.receive(packet); 16                 String message = new String(buffer, 0, packet.getLength()); 17                 System.out.println(packet.getAddress().getHostName() + ":" + message); 18                 if (message.equals("end")) break; 19  packet.setLength(buffer.length); 20  } 21  dsSocket.close(); 22  } 23         catch(Exception ex) 24  { 25  System.err.println(ex.getMessage()); 26  } 27  } 28 }
 

  這裏,咱們也假設和TCP同樣,當Client發出「end」消息時,認爲通訊結束,但其實這樣的設計不是必要的,Client端能夠隨時斷開,並不須要關心Server端狀態。

  多播(Multicast)

  多播採用和UDP相似的方式,它會使用D類IP地址和標準的UDP端口號,D類IP地址是指224.0.0.0到239.255.255.255之間的地址,不包括224.0.0.0。

  多播會使用到的類是MulticastSocket,它有兩個方法須要關注:joinGroup和leaveGroup。

  下面是一個多播的例子,Client端代碼以下:

 
 1 import java.net.*;  2 import java.io.*;  3 public class MulticastClient {  4 
 5     public static void main(String[] args)  6  {  7         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  8         try
 9  { 10             InetAddress address = InetAddress.getByName("230.0.0.1"); 11             int port = 5678; 12             while(true) 13  { 14                 String line = br.readLine(); 15                 byte[] message = line.getBytes(); 16                 DatagramPacket packet = new DatagramPacket(message, message.length, address, port); 17                 MulticastSocket multicastSocket = new MulticastSocket(); 18  multicastSocket.send(packet); 19                 if (line.equals("end")) break; 20  } 21  br.close(); 22  } 23         catch(Exception ex) 24  { 25  System.err.println(ex.getMessage()); 26  } 27  } 28 }
 

  服務器端代碼以下:

 
 1 import java.net.*;  2 import java.io.*;  3 public class MulticastServer {  4 
 5     public static void main(String[] args)  6  {  7         int port = 5678;  8         try
 9  { 10             MulticastSocket multicastSocket = new MulticastSocket(port); 11             InetAddress address = InetAddress.getByName("230.0.0.1"); 12  multicastSocket.joinGroup(address); 13             byte[] buffer = new byte[1024]; 14             DatagramPacket packet = new DatagramPacket(buffer, buffer.length); 15             while(true) 16  { 17  multicastSocket.receive(packet); 18                 String message = new String(buffer, packet.getLength()); 19                 System.out.println(packet.getAddress().getHostName() + ":" + message); 20                 if (message.equals("end")) break; 21  packet.setLength(buffer.length); 22  } 23  multicastSocket.close(); 24  } 25         catch(Exception ex) 26  { 27  System.err.println(ex.getMessage()); 28  } 29  } 30 }
 

  NIO(New IO)

  NIO是JDK1.4引入的一套新的IO API,它在緩衝區管理、網絡通訊、文件存取以及字符集操做方面有了新的設計。對於網絡通訊來講,NIO使用了緩衝區和通道的概念。

  下面是一個NIO的例子,和咱們上面提到的代碼風格有很大的不一樣。

 
 1 import java.io.*;  2 import java.nio.*;  3 import java.nio.channels.*;  4 import java.nio.charset.*;  5 import java.net.*;  6 public class NewIOSample {  7 
 8     public static void main(String[] args)  9  { 10         String host="127.0.0.1"; 11         int port = 5678; 12         SocketChannel channel = null; 13         try
14  { 15             InetSocketAddress address = new InetSocketAddress(host,port); 16             Charset charset = Charset.forName("UTF-8"); 17             CharsetDecoder decoder = charset.newDecoder(); 18             CharsetEncoder encoder = charset.newEncoder(); 19             
20             ByteBuffer buffer = ByteBuffer.allocate(1024); 21             CharBuffer charBuffer = CharBuffer.allocate(1024); 22             
23             channel = SocketChannel.open(); 24  channel.connect(address); 25             
26             String request = "GET / \r\n\r\n"; 27  channel.write(encoder.encode(CharBuffer.wrap(request))); 28             
29             while((channel.read(buffer)) != -1) 30  { 31  buffer.flip(); 32                 decoder.decode(buffer, charBuffer, false); 33  charBuffer.flip(); 34  System.out.println(charBuffer); 35  buffer.clear(); 36  charBuffer.clear(); 37  } 38  } 39         catch(Exception ex) 40  { 41  System.err.println(ex.getMessage()); 42  } 43         finally
44  { 45             if (channel != null) 46                 try { 47  channel.close(); 48                 } catch (IOException e) { 49                     // TODO Auto-generated catch block
50  e.printStackTrace(); 51  } 52  } 53  } 54 }
 

  上述代碼會試圖訪問一個本地的網址,而後將其內容打印出來。

相關文章
相關標籤/搜索