採用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、java的IO操作-基本資料操作流
/* * 可以讀寫基本資料型別的資料 * 資料輸入流: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