網絡編程Socket的阻塞和非阻塞IO

  網絡應用程序一個很重要的工做是傳輸數據。傳輸數據的過程不同取決於使用哪一種「交通工具「,可是傳輸的方式都是同樣的:都是以字節碼傳輸。JAVA開發網絡程序傳輸數據的過程和方式是被抽象了的,咱們不須要關注底層接口,只須要使用Java API 或其餘網絡框架就能達到數據傳輸的目的。發送數據和接收數據都是字節碼。編程

  Socket網絡編程我就很少囉嗦了,這裏我經過兩個簡單的示例比較下阻塞式IO(OIO)和非阻塞式IO(NIO)。服務器

  OIO中,每一個線程只能處理一個channel,該線程和該channel綁定。也就是同步的,客戶端在發送請求後,必須得在服務端有迴應後才發送下一個請求。因此這個時候的全部請求將會在服務端獲得同步。網絡

  NIO中,每一個線程能夠處理多個channel。也就是異步的,客戶端在發送請求後,沒必要等待服務端的迴應就能夠發送下一個請求,這樣對於全部的請求動做來講將會在服務端獲得異步,這條請求的鏈路就象是一個請求隊列,全部的動做在這裏不會獲得同步的。併發

  你可能使用過Java提供的網絡接口工做過,遇到過想從阻塞傳輸切換到非阻塞傳輸的狀況,這種狀況是比較困難的,由於阻塞IO和非阻塞IO使用的API有很大的差別。當咱們想切換傳輸方式時要花很大的精力和時間來重構代碼。app

  先看一個傳統的阻塞IO傳輸實現的Socket服務端:
框架

 1 /**
 2  *    傳統阻塞IO(OIO),原始socket
 3  *  
 4  *   <p>Title: PlainOioServer</p>
 5  *    @author wyx
 6  *    @date 2016-6-15 下午1:36:04
 7  */
 8 public class PlainOioServer {
 9     public void server(int port) throws Exception{
10         // bind server to port 
11         final ServerSocket socket = new ServerSocket(port);
12         while(true){
13             // accept connection
14             final Socket clientSocket = socket.accept();
15             System.out.println("Accepted connection form " + clientSocket);
16             // create new thread to handle connection
17             new Thread(new Runnable() {
18                 
19                 @Override
20                 public void run() {
21                     OutputStream out;
22                     try {
23                         out = clientSocket.getOutputStream();
24                         // write  message to connected client
25                         out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));
26                         out.flush();
27                         // close connection once message written and flushed
28                         clientSocket.close();
29                     } catch (IOException e) {
30                         e.printStackTrace();
31                     }
32                 }
33             }).start(); // start thread to begin handling
34         }
35     }
36 }

 

  上面的方式很簡潔,可是這種阻塞模式在大鏈接的狀況就會有嚴重的問題,如:客戶端鏈接超時,服務器響應嚴重延遲等。爲了解決這一問題,咱們可使用異步網絡處理全部的併發鏈接,但問題在於NIO和OIO的API是徹底不一樣的,因此一個用OIO開發的網絡應用程序想要使用NIO重構代碼幾乎是從新開發。異步

  下面代碼是使用Java NIO實現的例子:socket

 1 /**
 2  *    傳統非阻塞式IO(NIO),原始socket
 3  *  
 4  *    <p>Title: PlainNioServer</p>
 5  *    @author wyx
 6  *    @date 2016-6-15 下午1:46:09
 7  */
 8 public class PlainNioServer {
 9     public void server(int port) throws Exception{
10         System.out.println("Listening for connections on port " + port);
11         // open selector that handles channels
12         Selector selector = Selector.open();
13         // open ServerSocketChannel
14         ServerSocketChannel serverChannel = ServerSocketChannel.open();
15         // get ServerSocket
16         ServerSocket serverSocket = serverChannel.socket();
17         // bind server to port     
18         serverSocket.bind(new InetSocketAddress(port));
19         // set to non-blocking
20         serverChannel.configureBlocking(false);
21         // register ServerSocket to selector and specify than it is interested in new accepted clients
22         serverChannel.register(selector, SelectionKey.OP_ACCEPT);
23         final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
24         while(true){
25             // Wait for new events that are ready for process. this will block until something happens
26             int n = selector.select();
27             if(n > 0){
28                 // Obtain all SelectionKey instances that received enents
29                 Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
30                 while(iter.hasNext()){
31                     SelectionKey key = iter.next();
32                     iter.remove();
33                     //Check if event was because new client ready to get accepted
34                     if(key.isAcceptable()){
35                         ServerSocketChannel server = (ServerSocketChannel) key.channel();
36                         SocketChannel client = server.accept();
37                         System.out.println("Accepted connection from " + client);
38                         client.configureBlocking(false);
39                         // Accept client and register it to seletor 
40                         client.register(selector, SelectionKey.OP_WRITE, msg.duplicate());
41                     }
42                     
43                     // Check if event was because socket is ready to write data
44                     if(key.isWritable()){
45                         SocketChannel client = (SocketChannel) key.channel();
46                         ByteBuffer buff = (ByteBuffer) key.attachment();
47                         // Write date to connected client 
48                         while(buff.hasRemaining()){
49                             if(client.write(buff) == 0){
50                                 break;
51                             }
52                         }
53                         client.close();
54                     }
55                 }
56             }
57         }
58     }
59 }

  如你所見,即便它們實現的功能時候同樣的,可是代碼徹底不一樣。根據不一樣需求選用不一樣的實現方式,固然,也能夠直接選擇流行的網絡傳輸框架實現,如:Netty。以便於後期維護。ide

相關文章
相關標籤/搜索