1. 程式人生 > >Thinking in Java--使用NIO實現非阻塞Socket通訊

Thinking in Java--使用NIO實現非阻塞Socket通訊

Java1.4提供了一種新的IO讀取方式,稱為NIO。NIO中使用了通道和緩衝器的概念,並且以塊的形式操作資料,這樣更接近作業系統IO操作的形式,提高了JavaIO的效率。NIO的核心類有兩個Channel和Buffer。但是其實除了提升了基本IO操作的效能外,NIO還提供了非阻塞IO的功能。這裡先介紹下阻塞IO和非阻塞IO的概念。考慮到應用程式傳送出IO請求,如果這個IO請求會阻塞執行緒(就是執行緒停在這裡直到讀取到了資料再繼續執行下去),那麼就是阻塞IO;如果這個IO請求沒有阻塞執行緒(執行緒發出了IO請求,但是並停在這裡等資料的到來而是先去做別的事情)就稱為非阻塞IO。可以很顯然的看到,非阻塞的IO可以提高程式的效能。這篇部落格下面會先介紹用於Socket通訊的非阻塞IO的具體類,然後再利用這些類實現一個非阻塞的Socket通訊伺服器。

一.用於非阻塞Socket通訊的幾個類
(1).Selector類
它是SelectableChannel物件的多路複用器,所有希望採用非阻塞方式進行通訊的Channel都應該註冊到Selector物件。但是這個類的物件是不能通過呼叫構造器得到的,而是通過這個類靜態的open()方法得到,該方法將使用系統預設的Selector來返回新的Selector。
Selector物件可以同時監聽多個SelectableChannel的IO狀況,是非阻塞IO的核心。一個Selector例項有3個SelectionKey集合。
1)所有的SelectionKey集合:代表了註冊在該Selector上的Channel,這個集合可以通過keys()方法返回
2)被選擇的SelectionKey集合:代表了所有可以通過select()方法獲取的,需要進行IO處理的Channel,這個集合可以通過selectedKeys()返回。
3)被取消的SelectionKey集合:代表了所有被取消註冊關係的Channel,下一次執行select()方法時,這些Channel對應的SelectionKey就會被徹底刪除,程式通常無須直接訪問這個集合。
Selector類還提供了一系列和select()相關的方法,這些方法比較重要,需要了解一下:
int select():監控所有註冊的Channel,當他們中有需要處理的IO操作時,該方法返回,並將對應的SelectionKey加入到被選擇的SelectionKey集合中,並返回這些Channel的數量。
int select(long timeout):可以設定超時時長的select()操作
int selectNow():執行一個立即返回的select()操作,相對與無引數的select()方法而言,該方法不會阻塞執行緒
Selector wakeup():使一個還未返回的select()方法立刻返回。

(2)SelectableChannel類
Selectabel類是一種支援阻塞I/O和非阻塞I/O的通道。應用程式可以呼叫SelectabelChanel的register()方法將其註冊到指定的Selector上。SelectableChannel物件支援阻塞和非阻塞兩種模式,但是預設情況下是阻塞的(所有的Channel預設都是阻塞模式),必須使用非阻塞模式才能支援非阻塞IO。但是不同的SelectableChannel支援的操作是不一樣的,向ServerSocketChannel代表一個ServerSocket,它只支援OP_ACCEPT操作。而SocketChannle代表一個socket,支援OP_READ操作。下面是幾個SelectableChannel常用的方法:
boolean isBlocking():返回該Channel是否為阻塞模式
SelectabelChannel configureBlocking(boolean block):設定是否採用阻塞模式。
int valiOps():返回一個整數值,表示這個Channel所支援的操作。
boolean isRegistered():返回該Channel是否已經註冊在一個或多個Selector上。

(3)SelectionKey類
該類物件代表SelectableChannel和Selector之間的註冊關係。

(4)ServerSocketChannel類
支援非阻塞操作,對應與ServerSocket這個類,支援OP_ACCEPT操作;該類也提供了accept()方法,功能相當於ServerSocket提供的accept()方法。

(5).SocketChannel類
支援非阻塞操作,對應Socket這個類,支援OP_CONNECT,OP_READ和OP_WRITE操作。這個類還實現了ByteChannel,可以通過SocketChannel來讀寫ByteBuffer物件。

二.利用非阻塞IO實現一個聊天室的伺服器
前面我自己寫了一個仿QQ的C/S區域網聊天工具,在這個工具中,伺服器使用SeverSocket進行監聽,每新加入一個人就新建一個socekt與其通訊並且還要單獨為其開啟一個服務執行緒。這樣如果加入的使用者比較多,那麼就要開啟很多的服務執行緒了,伺服器的壓力就會比較大。現在我們用非阻塞IO,伺服器只需要一個執行緒就可以同時與多個客戶端進行通訊。
具體的思路是:原先伺服器中使用ServerSocket進行監聽,現在改用ServerSocketChannel物件進行監聽。原先每接入一個客戶端,就新建一個Socket進行通訊,現在新建一個SocketChannel進行通訊。最重要的是這些SelectableChannel物件,都必須註冊到一個Selector物件上;然後我們只需要檢測這個Selector物件就行了,我們可以呼叫這個Selector物件的select()方法監聽,這樣就可以實時監聽所有客戶端的行為,並可以通過selectedKeys()方法返回需要處理的SelectionKey物件,SelectionKey物件可以判定返回訊息的內容(是連線請求還是具體的訊息),並且這個物件的Channel方法可以返回被選中的客戶端的Channel。更具體的思路見下面的程式碼及註釋:

package IO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.*;


public class NServer {

    //用於檢測所有Channel狀態的Selector
    private Selector selector = null;
    ///預設埠
    static final int PORT = 30000;
    //定義實現編碼和解碼的字符集物件
    private Charset charset = Charset.forName("UTF-8");
    public void init() throws IOException
    {
        //Selector物件是不能通過構造器得到的,必須通過靜態的open()方法得到
        selector=Selector.open();

        //SeverSocktChannel物件也不能通過構造器得到,所以需要通過open()方法開啟
        ServerSocketChannel server = ServerSocketChannel.open();
        InetSocketAddress isa  = new InetSocketAddress("127.0.0.1",PORT);

        //將ServerSocketChannel繫結到指定的IP地址
        server.bind(isa);

        //設定ServerSocketChannel以非阻塞方式工作(預設是阻塞的)
        server.configureBlocking(false);

        //將server註冊到指定的Selector物件
        server.register(selector, SelectionKey.OP_ACCEPT);

        //返回值大於0,表示有Channel中含有需要處理的資料
        while(selector.select()>0){

            //依次處理selector上的每個已選擇的SelectionKey
            for(SelectionKey sk : selector.selectedKeys()){

                //從selector已選擇的Key集中刪除正在處理的SelectionKey
                selector.selectedKeys().remove(sk);
                //如果sk對應的Channel包含客戶端的連線請求
                if(sk.isAcceptable()){
                    //呼叫accept方法接受連線,產生服務端的SocketChannel
                    SocketChannel sc = server.accept();
                    //設定採用非阻塞模式
                    sc.configureBlocking(false);
                    //將該SocketChannel也註冊到selector上去
                    sc.register(selector, SelectionKey.OP_READ);
                    //將sk的Channel設定成準備接收其它的請求
                    sk.interestOps(SelectionKey.OP_ACCEPT);
                }
                //如果sk對應的Channel有資料需要讀取
                if(sk.isReadable()){
                    //獲取SelectionKey對應的Channel,該Channel中有需要讀取的資料
                    SocketChannel sc =(SocketChannel)sk.channel();
                    //定義準備執行讀取資料的ByteBufferer
                    ByteBuffer buff = ByteBuffer.allocate(1024);
                    String content="";
                    try{
                        while(sc.read(buff)>0){
                            buff.flip();
                            content+=charset.decode(buff);
                        }
                        //將sk對應的Channel設定成準備洗一次讀取
                        sk.interestOps(SelectionKey.OP_READ);
                    }

                    //如果捕獲到該sk對應的Channel出現了異常,即表明該Channel
                    //對應的Client出現了異常,所以從Selector中取消掉sk的註冊
                    catch(IOException e){

                        //從Selector中刪除掉指定的SelectionKey
                        sk.cancel();
                        if(sk.channel()!=null){
                            sk.channel().close();
                        }
                    }
                    //如果content的長度不為空,即該聊天資訊不為空
                    if(content.length()>0){

                        //遍歷該selector中註冊的所有SelectionKey
                        for(SelectionKey key: selector.keys()){

                            //獲取key對應的Chanel
                            Channel targetChannel = key.channel();
                            //如果該Channel是SocketChannel物件
                            if(targetChannel instanceof SocketChannel){
                                //將讀到的內容寫入到Channel中
                                SocketChannel dest =(SocketChannel)targetChannel;
                                dest.write(charset.encode(content));
                            }
                        }
                    }
                }
            }
        }
    }
}

相關推薦

java NIO 實現阻塞socket通訊

java的nio為非阻塞式socket通訊提供瞭如下幾個類:           Selector : 它是SelectableChannel物件的多路複用器,所有希望採用非阻塞方式進行通訊的channel都應該註冊到Selector物件。可以通過呼叫此類的open()

Thinking in Java--使用NIO實現阻塞Socket通訊

Java1.4提供了一種新的IO讀取方式,稱為NIO。NIO中使用了通道和緩衝器的概念,並且以塊的形式操作資料,這樣更接近作業系統IO操作的形式,提高了JavaIO的效率。NIO的核心類有兩個Channel和Buffer。但是其實除了提升了基本IO操作的效能外,

Java網路程式設計——使用NIO實現阻塞Socket通訊

       除了普通的Socket與ServerSocket實現的阻塞式通訊外,java提供了非阻塞式通訊的NIO API。先看一下NIO的實現原理。        從圖中可以看出,伺服器上所有Channel(包括ServerSocketChannel和Socket

使用NIO實現阻塞Socket通訊原理

剛學了NIO,寫一下自己的理解 網路通訊中,NIO提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道來實現,可以設定阻塞與非阻塞兩種模式,為了實現高負載高併發都採取非阻塞的模式。通道是雙向的,可以同時在通道上傳送和讀取

Java入門系列-25-NIO(實現阻塞網路通訊)

還記得之前介紹NIO時對比傳統IO的一大特點嗎?就是NIO是非阻塞式的,這篇文章帶大家來看一下非阻塞的網路操作。 補充:以陣列的形式使用緩衝區 package testnio; import java.io.IOException; import java.io.RandomAccessFile; impo

NIO實現阻塞Socket程式設計

前言: 基於阿里面試時,面試官問我,我做的聊天專案裡,考慮過效能沒有,是怎麼解決程式卡頓現象的,針對客戶端,當在傳送檔案時,如果卡頓,怎麼辦,同時想聊天,當時程式我是基於多執行緒實現的,在客戶端裡,聊天時啟動一個執行緒,傳送檔案時,啟動另一個執行緒,所以

Java入門系列-25-NIO(實現阻塞網絡通信)

寫入 eve asn accept public int 次數 客戶端 服務器 還記得之前介紹NIO時對比傳統IO的一大特點嗎?就是NIO是非阻塞式的,這篇文章帶大家來看一下非阻塞的網絡操作。 補充:以數組的形式使用緩沖區 package testnio; import

NIO實現阻塞Socket通訊

Selector 非阻塞通訊核心 Selector負責監控所有已經註冊的Channel Selector通過SelectionKey來關聯Channel 群聊伺服器nio非阻塞式 public class NServer { // 用於檢測所有Channel狀

Java NIO 阻塞socket通訊案例

NIO的特性:它以塊為基本單位處理資料,所有的資料都要通過緩衝區(Buffer)來進行傳輸。它有一個用來作為原始I/O操作的抽象通道(Channel)並提供了Selector的非同步網路介面。且支援將檔案對映到記憶體,以大幅提高I/O效率。 緩衝區中有3個重要

java NIO同步阻塞

NIO伺服器端如何實現非阻塞? 伺服器上所有Channel需要向Selector註冊,而Selector則負責監視這些Socket的IO狀態(觀察者),當其中任意一個或者多個Channel具有可用的IO操作時,該Selector的select()方法將會返回大於0的整數,該整數值就表示該Sele

Java NIO阻塞

一、阻塞與非阻塞 1、傳統的IO流是阻塞式的。當一個執行緒呼叫read或write時,執行緒會被阻塞,直到有一些資料被讀取或寫入,該執行緒在此期間不能執行其他任務。因此,在網路通訊進行IO操作時,伺服器不得不為每個客戶端提供一個獨立的執行緒來進行處理。當伺服器需要處理大量的客戶端時,效能會急

[linux C]使用select進行阻塞socket通訊

程式碼片: fd_set save_fds; int sockfd; struct sockaddr_in serv_addr; //my address struct sockaddr_in client_addr; if((s

另一種實現阻塞網路通訊的方法———使用libev

背景:最近終於開始了我的實習生之路,本來在進公司之前還比較緊張,儘管拿到了offer,因為畢竟這是一個新的起點,一開始從學生到員工這個身份的轉變讓我有些不太適應,但是還好在公司裡遇到了人超級好的軟體經理Alex以及其他精明能幹的小夥伴們,所以這個過渡時間也很快。 一開始Al

通過多執行緒實現阻塞TCP通訊

在tcp通訊中,一般都是阻塞的,如果要實現非阻塞,我們可以使用多執行緒也可以使用nio中相關的類。這裡我使用的是多執行緒的方式實現非阻塞。 伺服器端: 1.建立ServerSocket物件,繫結監聽埠; 2.呼叫accept()方法對客戶端進行監聽; 3

利用java nio 實現簡單的訊息通訊

         在分散式java應用中,經常需要在各個子系統間進行通訊與資料交換。在java領域要實現這樣的功能有很多途徑,下面我將使用nio+tcp/ip這種基於訊息機制的方式來實現這樣的功能。利用nio的非阻塞模式以及選擇器機制能夠很大程度上的提高程式的效能及吞吐量

java socket通訊I/O阻塞>多執行緒實現阻塞通訊

簡單的java socket通訊,多個客戶端同時連線,功能可在此基礎上進行擴充套件。效果如圖: server: package com.lb.LB_Socket; import java.io.BufferedReader; import ja

採用Java阻塞IO對已經到達的socket實現阻塞完整讀取(一個簡單的java http server實現)

最近寫伺服器時想到一個問題:用Java Bio(即Socket)寫伺服器,怎麼一次性完整讀取已經到達的Socket流。 對這個需求有很多角度的設定,也有很多解法。我們來一一具化這個需求: (1) 解法:依賴http協議的content-length。 分析:很直觀的想

java NIO實現同步阻塞伺服器

server package net.smgui.util; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.Byt

異步阻塞socket實現

print except 事件循環 port int 性能 run utf8 try 在學習使用scrapy爬蟲框架之前,需要了解一些基礎原理   我們知道HTTP請求是基於socket模塊進行發送和接受的,但是socket套接字的在使用的中存在著阻塞,不利用爬蟲的高性能運

Java異步阻塞IO NIO使用與代碼分析

package mes 127.0.0.1 back 之一 write throwable private 建立 [TOC] Java異步非阻塞IO NIO使用與代碼分析 TimeServer程序的NIO實現完整代碼 TimeServer程序來自書本《Netty權威指南》