1. 程式人生 > >採用Java阻塞IO對已經到達的socket流實現非阻塞完整讀取(一個簡單的java http server實現)

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

最近寫伺服器時想到一個問題:用Java Bio(即Socket)寫伺服器,怎麼一次性完整讀取已經到達的Socket流。

對這個需求有很多角度的設定,也有很多解法。我們來一一具化這個需求:

(1)

解法:依賴http協議的content-length。

分析:很直觀的想法,可以根據http請求頭給定一個固定長度的位元組或字元快取,從中獲取content-length,就知道往後要在從流中讀多少位元組了。

設定:如果不考慮http,設定也不考慮任何定製的協議(流的開頭給長度或者用特殊的字元標誌留的結尾),僅僅考慮一次socket流的到達,如何才能用完整讀取這次到達流的內容。

(2)

解法:上nio上mina上netty。

分析:又直觀的想法。不過開篇設定好了用阻塞讀來實現非阻塞完整讀取到達流。

設定:如何用java阻塞讀(bio)達到不阻塞的完整讀取一次socket到達流。

(3)

解法:依賴流的結尾返回-1。

分析:更直觀的想法,java.io(也就是bio啦)提供的各種XXStream和XXReader都提供read(XX)的函式,並提示如果讀到結尾就返回-1。那在伺服器端直接不斷read(),直到-1就好了。問題是對於檔案的讀取,到達結尾會返回-1(好像是檔案末尾的EOF?具體沒研究過)。但是socket流,是沒有結束符的(其實也有,一個socket關閉後再read就會返回-1,但是這裡對於一個已經到達的流的完整讀取,肯定不能依賴於網路對面對socket關閉)。雖然檔案流是連續的,但是網路流肯定不能保證,即便通過socket傳送一個完整的檔案,由於網路原因,這個檔案可能分幾個部分到達socket,而本文的需求就設定在對一次到達的流,怎麼保證完整的讀取。

(4)

解決:用大快取。

分析:更加直觀的想法。上個足夠大的快取,一次性把到達流的內容全部讀出來,不怕不夠,就怕你不夠大!但是,多大的快取算大呢?如果伺服器的資源有限呢?如果到達流裡的內容就是要勝天半子,比快取多一個位元組呢?

設定:我們可用的快取有限,肯定比一次性到達的流長度要小。

(5)

解決:用快取迴圈從socket流裡read(buf),如果快取讀滿了,說明流可能還有未讀內容。如果快取未滿,哼哼~這次到達流肯定讀完了。其實這個方案本質上和不用快取迴圈用read()挨個讀沒區別。虛擬碼:

while (inputStream.read(buf) == buf.size){}

or

while (inputStream.read() != -1) {}

分析:這個方法有個漏洞——我們在使用阻塞io。假設這樣一個場景,我能用的快取是8個位元組,一次流到達了16個位元組,那麼兩次迴圈下來,我能把流讀完。但是第二次迴圈時判斷buf還是讀滿了,所以可能流還沒讀完。於是進行第三次流的read(buf)。假設後續沒有其他流到達,那麼第三次的read(buf)是讀不到東西的,也就是阻塞在那裡了。用read()挨個讀也是一樣的,讀完流裡面最後一個位元組,再讀取的話會阻塞,而不是返回-1。這與條件設定的非阻塞讀完整流衝突。

(6)

問題:如果在讀取流的過程中,後續又到達了其他流資訊,怎麼區分兩次到達的流?

分析:超綱了超綱了... 本題設定是沒有流的協議資訊的...這種情況只能把多次到達的流全部讀取過來,或者其他高手在這種玩法下可以解決的話請不吝賜教。

分析到這,清楚這個問題的限定:(1)無傳輸協議,沒有長度資訊或者結束標誌;(2)用阻塞IO讀取;(3)讀取一次到達的流內容時不能阻塞;(4)讀取時後續有其他流到達不管。

我想到的一個解決的辦法就是java.io.XXReader類的ready()函式。這個函式會測試下次呼叫read()函式時會不會阻塞,如果不阻塞就返回true,阻塞的話返回false。

注意下,ready()函式返回true可以保證進行read()後100%不阻塞,但是返回false的話,進行read()不一定阻塞,也可能讀到資料返回。其實這很直觀,如果探測到還有未讀的資料,返回true,這些未讀的資料在沒讀取之前也不會跑掉,read()的話肯定能讀到。但是探測到如果沒有資料,和用read()進行讀取之間,可能會繼續有流到達,此時就不一定會阻塞。也就是為什麼用ready()函式解決不了多次到達流區分的問題。

我用java的bio寫了一個簡單的http server來實現上述需求。雖然用http,只是為了大家從瀏覽器端跟這個server互動方便而已。其實採用任何協議或者自己寫個client用socket隨便傳送什麼來測試ready()的非阻塞讀也可以。

這個server接受使用者的http請求,然後從請求頭把uri提取出來(不包括引數,比如請求行“GET /user?name=XXX HTTP/1.1”,那麼提取的uri就是“/user”),然後通過html發回給使用者,頁面顯示使用者輸入的uri。如果使用者輸入的請求路徑是“/stop”,那麼可以從瀏覽器端關閉伺服器,並在頁面顯示“server close”。

下面直接上程式碼:

package com.jxshen.example.web.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * A simple http server using bio to read a socket stream<br> 
 * and response the uri in http request line to client, <br>
 * that is uri after method blank and before the blank of protocol/version<br>
 * if the uri is "/stop", server close<br>
 * just test a method to read a complete arrival socket stream with a very small buffer<br>
 * so the buffer should be used repeatedly to join the hold arrival message<br>
 * the key point is BufferedRead.ready() function which tell the next read() is guaranteed not to block for input<br>
 * but the ready() return false do not guarantee the next read() is 100% block<br>
 * 
 * @author jxshen
 *
 */
public class SimpleHttpServer {
    
    public static final int SMALL_BUF_SIZE = 8;
    public static final int PORT = 8080;
    public static final int BACK_LOG = 50;
    // client can use http get uri to close server, eg: http://localhost:8080/stop
    private static final String STOP_URL = "/stop";
    // if client stop server, the string of response
    private static final String CLOSE_RESP_STR = "Server Close";
    private static volatile boolean stop = false; 
    
    // The html template of response
    private static final String HTML = "HTTP/1.1 200 OK\r\n"
            + "Content-Type: text/html\r\n" 
            + "Content-Length: %d\r\n" + "\r\n"
            + "%s";
    
    public static void main(String[] args) {
        new SimpleHttpServer().run();
    }

    public void run() {
        ServerSocket server = null;
        try {
            server = new ServerSocket();
            server.bind(new InetSocketAddress(PORT), BACK_LOG);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        Socket client = null;
        InputStream is = null;
        OutputStream os = null;
        while (!stop) {
            try {
                client = server.accept();
                is = client.getInputStream();
                os = client.getOutputStream();
                
                // handle inputStream
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                StringBuilder reqStr = new StringBuilder();
                char[] buf = new char[SMALL_BUF_SIZE];
                do {
                    if (br.read(buf) != -1) {
                        reqStr.append(buf);
                    }
                } // the key point to read a complete arrival socket stream with bio but without block
                while (br.ready()); 
                
                // get uri in http request line
                String respStr = parse(reqStr.toString());
                
                // handle outputStream
                if (stop = STOP_URL.equalsIgnoreCase(respStr)) {
                    respStr = CLOSE_RESP_STR;
                    System.out.println("client require server to stop");
                }
                
                // join the html content
                respStr = "<h1>" + respStr + "</h1>";
                os.write(String.format(HTML, respStr.length(), respStr).getBytes());
                os.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            server.close();
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * get uri in http request line to client, that is uri after the blank of method and before the blank of protocol/version<br>
     * eg: for a http get request, the request line maybe: GET /user?name=jxshen HTTP/1.1<br>
     * then the function return "user"<br>
     * 
     */
    public static String parse(String source) {
        if(source == null || source.length() == 0) {
            return new String();
        }
        
        int startIndex;
        startIndex = source.indexOf(' ');
        if (startIndex != -1) {
          int paramIndex = source.indexOf('?', startIndex + 1);
          int secondBlankIndex = source.indexOf(' ', startIndex + 1);
          int endIndex = -1;
          if (secondBlankIndex > paramIndex) {
              endIndex = secondBlankIndex;
          } else {
              endIndex = paramIndex;
          }
          if (endIndex > startIndex)
            return source.substring(startIndex + 1, endIndex);
        }
        return new String();
    }
}


相關推薦

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

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

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

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

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

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

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 Socket程式設計(阻塞多執行緒,NIO)

服務端:伺服器Server類public class Server implements Runnable { private int port; private volatile boolean stop; private Selector sele

Linux Socket - 內核阻塞功能

由於 有變 增加 連接建立 ioc eval type ddr ont select 函數 int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*time

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

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

Java(五)IO操作4.位元組輸出

import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; public class FileOutputStreamDemo { p

IO通訊模型(二)同步阻塞模式NIO(NonBlocking IO

同步非阻塞模式(NonBlocking IO) 在非阻塞模式中,發出Socket的accept()和read()操作時,如果核心中的資料還沒有準備好,那麼它並不會阻塞使用者程序,而是立刻返回一個資訊。也就是說程序發起一個read操作後,並不需要一直阻塞等待,而是馬上就得到了一個結果。 如果結果發現數據準備

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

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

NIO實現阻塞Socket通訊

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

android socket連結 NIO阻塞方式

最近在研究android的推送,一開始準備自己搭建推送伺服器,並在android機上建立一個socket長連線,由於之前一直是用c++,在網上搜索一些資料後,臨時寫了一個基於NIO模式的客戶端socket連結類,測試後能使用,當然還有很多問題,沒有去修改了,因為最後發現,現

Java實現阻塞通訊

用ServerSocket和Socket來編寫伺服器程式和客戶程式,是Java網路程式設計的最基本的方式。這些伺服器程式或客戶程式在執行過程中常常會阻塞。例如當一個執行緒執行ServerSocket的accept()方法時,假如沒有客戶連線,該執行緒就會一直等到有了客戶連

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

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

Java實現阻塞通訊 和阻塞通訊

用ServerSocket和Socket來編寫伺服器程式和客戶程式,是Java網路程式設計的最基本的方式。這些伺服器程式或客戶程式在執行過程中常常會阻塞。例如當一個執行緒執行ServerSocket的accept()方法時,假如沒有客戶連線,該執行緒就會一直等到有了客戶連線

89、javaIO操作-基本資料操作

/* * 可以讀寫基本資料型別的資料 * 資料輸入流:DataInputStream * DataInputStream(InputStream in) * 資料輸出流:DataOutputStream * DataOutputStream(Outpu

linux下socket程式設計 select實現阻塞模式多臺客戶端與伺服器通訊

select函式原型如下: int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select系統呼叫是用來讓我們的程式

伺服器程式設計心得(四)—— 如何將socket設定為阻塞模式

1. windows平臺上無論利用socket()函式還是WSASocket()函式建立的socket都是阻塞模式的: SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol )

socket設定為阻塞模式

1,套接字的預設狀態是阻塞的。即當發出一個不能立即完成的套接字呼叫時,該程序將被投入睡眠,等待相應操作完成。2,阻塞的套接字分為下面四類:輸入操作:包括read、recv、recvfrom和recvms