歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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

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

Socket網絡編程我就不多啰嗦了,這裡我通過兩個簡單的示例比較下阻塞式IO(OIO)和非阻塞式IO(NIO)。

OIO中,每個線程只能處理一個channel,該線程和該channel綁定。也就是同步的,客戶端在發送請求後,必須得在服務端有回應後才發送下一個請求。所以這個時候的所有請求將會在服務端得到同步。

NIO中,每個線程可以處理多個channel。也就是異步的,客戶端在發送請求後,不必等待服務端的回應就可以發送下一個請求,這樣對於所有的請求動作來說將會在服務端得到異步,這條請求的鏈路就象是一個請求隊列,所有的動作在這裡不會得到同步的。

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

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

/**
 *    傳統阻塞IO(OIO),原始socket
 * 
 *   <p>Title: PlainOioServer</p>
 *    @author wyx
 *    @date 2016-6-15 下午1:36:04
 */
public class PlainOioServer {
    public void server(int port) throws Exception{
        // bind server to port
        final ServerSocket socket = new ServerSocket(port);
        while(true){
            // accept connection
            final Socket clientSocket = socket.accept();
            System.out.println("Accepted connection form " + clientSocket);
            // create new thread to handle connection
            new Thread(new Runnable() {
               
                @Override
                public void run() {
                    OutputStream out;
                    try {
                        out = clientSocket.getOutputStream();
                        // write  message to connected client
                        out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));
                        out.flush();
                        // close connection once message written and flushed
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start(); // start thread to begin handling
        }
    }
}

上面的方式很簡潔,但是這種阻塞模式在大連接的情況就會有嚴重的問題,如:客戶端連接超時,服務器響應嚴重延遲等。為了解決這一問題,我們可以使用異步網絡處理所有的並發連接,但問題在於NIO和OIO的API是完全不同的,所以一個用OIO開發的網絡應用程序想要使用NIO重構代碼幾乎是重新開發。

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

/**
 *    傳統非阻塞式IO(NIO),原始socket
 * 
 *    <p>Title: PlainNioServer</p>
 *    @author wyx
 *    @date 2016-6-15 下午1:46:09
 */
public class PlainNioServer {
    public void server(int port) throws Exception{
        System.out.println("Listening for connections on port " + port);
        // open selector that handles channels
        Selector selector = Selector.open();
        // open ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // get ServerSocket
        ServerSocket serverSocket = serverChannel.socket();
        // bind server to port   
        serverSocket.bind(new InetSocketAddress(port));
        // set to non-blocking
        serverChannel.configureBlocking(false);
        // register ServerSocket to selector and specify than it is interested in new accepted clients
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        while(true){
            // Wait for new events that are ready for process. this will block until something happens
            int n = selector.select();
            if(n > 0){
                // Obtain all SelectionKey instances that received enents
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    iter.remove();
                    //Check if event was because new client ready to get accepted
                    if(key.isAcceptable()){
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        System.out.println("Accepted connection from " + client);
                        client.configureBlocking(false);
                        // Accept client and register it to seletor
                        client.register(selector, SelectionKey.OP_WRITE, msg.duplicate());
                    }
                   
                    // Check if event was because socket is ready to write data
                    if(key.isWritable()){
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buff = (ByteBuffer) key.attachment();
                        // Write date to connected client
                        while(buff.hasRemaining()){
                            if(client.write(buff) == 0){
                                break;
                            }
                        }
                        client.close();
                    }
                }
            }
        }
    }
}

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

Copyright © Linux教程網 All Rights Reserved