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 }
 

  上述代码会试图访问一个本地的网址,而后将其内容打印出来。

相关文章
相关标签/搜索