1. 程式人生 > >用NIO實現http協議

用NIO實現http協議

his 開發 非阻塞 etl 必須 操作 nts blocking this

先來看一下本篇博文的目錄:

一:簡介Nio

二:Nio的好處

三:關於http協議

四:代碼實現

五:總結

一:簡介Nio

我們都知道io流,那麽NIO是什麽呢?本篇博文將會帶你一探NIO,NIO的全稱叫做New IO,顧名思義也就是全新的IO流,從Java Api 1.4版本開始發行的,nio提供了完全不同的工作體制,java nio的IO模型是同步非阻塞,也就是當用戶發起一個IO操作後,邊可等待邊可返回,但是需要進程不斷的詢問是否傳輸完畢,這就引入了不必要的cpu浪費,但是總體它的效率是要高於IO的,它主要有三大核心組件:

  • Channels
  • Buffers
  • Selectors

channel你可以理解為一個通道,就相當於數據傳輸的管道,你可以如此抽象的理解。Buffers是一個緩存區,數據可以通過管道到緩沖區,也可以通讀緩沖區到channel,Nio的主要實現類為FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel,BUffer的主要實現類為,從中可以看出包含了java的很多基本類型數據,Byte、char、double、Folat、int、long等,ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。而Selector是Nio最關鍵的一個部分,Selector 允許單線程處理多個 Channel,如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用 Selector 就能極大的提升效率。要使用 Selector,得向 Selector 註冊 Channel,然後調用它的 select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等

二:nio的好處

傳統的socket IO中,需要為每個連接創建一個線程,當並發的連接數量非常巨大時,線程所占用的棧內存和CPU線程上下文切換的開銷將非常巨大。使用NIO,不再需要為每個線程創建單獨的線程,可以用一個含有限數量線程的線程池,甚至一個線程來為任意數量的連接服務。由於線程數量小於連接數量,所以每個線程進行IO操作時就不能阻塞,如果阻塞的話,有些連接就得不到處理,NIO提供了這種非阻塞的能力。當有任務進程來,可以實現通道註冊到Selector上,一個Slector可以管理多個線程,這樣就避免了線程的上下文切換,避免了資源的浪費,大大的提升了效率。

三:關於http協議

http從1990年開始,目前已經經歷3代,最近使用的http1.1,https其實就是實際上的http1.2,當然本篇主要關註的還是http1.1,http協議,我們每天用到的非常多,它是基於服務器和客戶端模式的,它在應用層解析內容的,它有點類似於我們的電報,比如在發送之前需要加密報文,發送到目的地需要解析報文,對應的我們網站,其實每次我們點一下鏈接進行請求就是進行了一次http的request操作,這是對於客戶端而言的,這一過程就將會攜帶有你請求的地址、host主機名、用戶的狀態,sessionid、cookie等信息(具體我們可以用程序來實現),然後發送到服務器,服務器進行解析,然後給我們返回。學習http協議,我們要記得以下兩點:

1.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。采用這種方式可以節省傳輸時間。
2.無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味著如果後續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快

四:代碼實現

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel; import java.util.Iterator; public class Server { public static void main(String[] args) throws IOException { ServerSocketChannel ssc = ServerSocketChannel.open();//監聽新進來的 TCP連接的通道,打開 ServerSocketChannel ssc.socket().bind(new InetSocketAddress(8080));//綁定8080端口 ssc.configureBlocking(false);//設置非阻塞模式 Selector selector = Selector.open();//創建選擇器 SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT);//給選擇器註冊通道 //selectionKey:代表了註冊到該 Selector 的通道 while (true) { //監聽新進來的連接 int select = selector.select(2000); if (select==0) { //如果選擇的通道為0,最長會阻塞 timeout毫秒 System.out.println("等待請求超時......"); continue; } System.out.println("開始處理請求....."); Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();//叠代器 while (keyIter.hasNext()) { SelectionKey key = keyIter.next(); new Thread(new HttpHandler(key)).run(); keyIter.remove(); } } } }


package com.wyq.NioToHttp;


import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;


/**
* Http處理器器
* @author Yiron
*
*/
public class HttpHandler implements Runnable{


private int buffersize= 1024;//設置緩沖區大小


private String localCharSet="UTF-8"; //設置編碼格式


private SelectionKey key;//註冊號的通道


public HttpHandler(SelectionKey key) {//把選擇鍵構造進去


this.key = key;
}


public void handleAccept() throws IOException{


SocketChannel socketChannel=((ServerSocketChannel)key.channel()).accept();


socketChannel.configureBlocking(false);//設置非阻塞模式


socketChannel.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(buffersize));
//buffer分配一個緩沖區 大小為1024


}


public void handleRead() {


SocketChannel sc=(SocketChannel)key.channel();// SocketChannel 是一個連接到 TCP 網絡套接字的通道


ByteBuffer buffer=(ByteBuffer)key.attachment();//從 SocketChannel讀取到的數據將會放到這個 buffer中


buffer.clear();


try {
if((sc.read(buffer))!=-1) {


buffer.flip();//flip方法將Buffer從寫模式切換到讀模式


String receive = Charset.forName(localCharSet).newDecoder().decode(buffer).toString();
//將此 charset 中的字節解碼成 Unicode 字符

String[] requestMessage = receive.split("\r\n");//接受請求的信息


for (String message : requestMessage) {


if (message.isEmpty()) {//如果是空行說明信息已經結束了

break;
}
}
//控制臺打印
String[] firsetLine = requestMessage[0].split(" ");

System.out.println("----控制臺輸出:-------");


System.out.println("Method:t"+firsetLine[0]);


System.out.println("url是:\t"+firsetLine[1]);


System.out.println("Httpversion是:\t"+firsetLine[2]);


System.out.println("-----輸出結束-------------");


//返回客戶端
StringBuilder sendStr = new StringBuilder();


sendStr.append("Http/1.1 200 Ok\r\n");


sendStr.append("Content-Type:text/html;charset="+localCharSet+"\r\n");


sendStr.append("\r\n");


sendStr.append("<html><head><title>顯示報文</title></head><body>");


sendStr.append("接受到請求的報文是:+<br>");


for (String s : requestMessage) {


sendStr.append(s+"<br/>");


}
sendStr.append("</body></html>");


buffer=ByteBuffer.wrap(sendStr.toString().getBytes(localCharSet));


sc.write(buffer);


sc.close();
}else {
sc.close();

}
} catch (IOException e) {

e.printStackTrace();
}

}

@Override
public void run() {


try {
if (key.isAcceptable()) {//接受


handleAccept();


}
if (key.isReadable()) {//開始讀


handleRead();


}


} catch (Exception e) {


e.printStackTrace();
}
}



}

 

打印控制臺,輸出如下:

開始處理請求.....
----控制臺輸出:-------
Method:tGET
url是: /
Httpversion是: HTTP/1.1

因為我們綁定了8080端口,這裏進行請求,看瀏覽器會打印出相關的http信息,具體如下:

技術分享

從中可以看出很多信息,比如我們的請求方式為:Get,主機為:localhost,端口為8080,連接狀態:存活,接收的語言:中文-英文,cookile信息等等,這代表了我們請求的

頭信息,將會把這些內容發送給瀏覽器。這就是http協議在request請求前組織的報文,從中我們可以一探http的究竟,這對我們理解客戶端-服務器模式更加深刻。有助於我們

對於web網站的開發。

五:總結

本篇博文的主要思想是學習nio的使用方式,並且能夠理解http協議,其具體的包含信息的細節,理解nio的好處,關於對同步阻塞模式的理解,如果去通過nio實現http協議,雖然nio在實際中使用的機會並不多,但是還是有助我們加深對管道流的理解,io的實現方式,記得我有次面試的時候,被問到Nio、BiO的區別,當時是很蒙圈的,因為不知道java中還有這麽多的流處理方式。我們需要學習關於單選擇器處理多線程的方式,還有去思考關於線程效率的問題,這都對我們大有裨益。

用NIO實現http協議