一、nio類庫簡介
1、緩沖區buffer
buffer是一個對象,包含一些要寫入和讀出的數據。
在nio中,所有的數據都是用緩沖區處理的,讀取數據時,它是從通道(channel)直接讀到緩沖區中,在寫入數據時,也是從緩沖區寫入到通道。
緩沖區實質上是一個數組,通常是一個字節數組(bytebuffer),也可以是其它類型的數組,此外緩沖區還提供了對數據的結構化訪問以及維護讀寫位置等信息。
buffer類的繼承關系如下圖所示:
2、通道channel
channel是一個通道,網絡數據通過channel讀取和寫入。通道和流的不同之處在于通道是雙向的(通道可以用于讀、寫后者二者同時進行),流只是在一個方向上移動。
channel大體上可以分為兩類:用于網絡讀寫的selectablechannel(serversocketchannel和socketchannel就是其子類)、用于文件操作的filechannel。
下面的例子給出通過filechannel來向文件中寫入數據、從文件中讀取數據,將文件數據拷貝到另一個文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
public class niotest { public static void main(string[] args) throws ioexception { copyfile(); } //拷貝文件 private static void copyfile() { fileinputstream in= null ; fileoutputstream out= null ; try { in= new fileinputstream( "src/main/java/data/in-data.txt" ); out= new fileoutputstream( "src/main/java/data/out-data.txt" ); filechannel inchannel=in.getchannel(); filechannel outchannel=out.getchannel(); bytebuffer buffer=bytebuffer.allocate( 1024 ); int bytesread = inchannel.read(buffer); while (bytesread!=- 1 ) { buffer.flip(); outchannel.write(buffer); buffer.clear(); bytesread = inchannel.read(buffer); } } catch (filenotfoundexception e) { // todo auto-generated catch block e.printstacktrace(); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } //寫文件 private static void writefilenio() { try { randomaccessfile fout = new randomaccessfile( "src/main/java/data/nio-data.txt" , "rw" ); filechannel fc=fout.getchannel(); bytebuffer buffer=bytebuffer.allocate( 1024 ); buffer.put( "hi123" .getbytes()); buffer.flip(); try { fc.write(buffer); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } catch (filenotfoundexception e) { // todo auto-generated catch block e.printstacktrace(); } } //讀文件 private static void readfilenio() { fileinputstream fileinputstream; try { fileinputstream = new fileinputstream( "src/main/java/data/nio-data.txt" ); filechannel filechannel=fileinputstream.getchannel(); //從 fileinputstream 獲取通道 bytebuffer bytebuffer=bytebuffer.allocate( 1024 ); //創建緩沖區 int bytesread=filechannel.read(bytebuffer); //將數據讀到緩沖區 while (bytesread!=- 1 ) { /*limit=position * position=0; */ bytebuffer.flip(); //hasremaining():告知在當前位置和限制之間是否有元素 while (bytebuffer.hasremaining()) { system.out.print((char) bytebuffer.get()); } /* * 清空緩沖區 * position=0; * limit=capacity; */ bytebuffer.clear(); bytesread = filechannel.read(bytebuffer); } } catch (filenotfoundexception e) { // todo auto-generated catch block e.printstacktrace(); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } } |
3、多路復用器selector
多路復用器提供選擇已經就緒的任務的能力。selector會不斷的輪詢注冊在其上的channel,如果某個channel上面發送讀或者寫事件,這個channel就處于就緒狀態,會被selector輪詢出來,然后通過selectionkey可以獲取就緒channel的集合,進行后續的i/o操作。
一個多路復用器selector可以同時輪詢多個channel,由于jdk使用了epoll代替了傳統的select實現,所以它沒有最大連接句柄1024/2048的限制,意味著只需要一個線程負責selector的輪詢,就可以接入成千上萬的客戶端。其模型如下圖所示:
用單線程處理一個selector。要使用selector,得向selector注冊channel,然后調用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。
注:
1、什么select模型?
select是事件觸發機制,當等待的事件發生就觸發進行處理,多用于linux實現的服務器對客戶端的處理。
可以阻塞地同時探測一組支持非阻塞的io設備,是否有事件發生(如可讀、可寫,有高優先級錯誤輸出等),直至某一個設備觸發了事件或者超過了指定的等待時間。也就是它們的職責不是做io,而是幫助調用者尋找當前就緒的設備。
2、什么是epoll模型?
epoll的設計思路,是把select/poll單個的操作拆分為1個epoll_create+多個epoll_ctrl+一個wait。此外,內核針對epoll操作添加了一個文件系統”eventpollfs”,每一個或者多個要監視的文件描述符都有一個對應的eventpollfs文件系統的inode節點,主要信息保存在eventpoll結構體中。而被監視的文件的重要信息則保存在epitem結構體中。所以他們是一對多的關系。
二、nio服務器端開發
功能說明:開啟服務器端,對每一個接入的客戶端都向其發送hello字符串。
使用nio進行服務器端開發主要有以下幾個步驟:
1、創建serversocketchannel,配置它為非阻塞模式
1
2
|
serversocketchannel = serversocketchannel.open(); serversocketchannel.configureblocking( false ); |
2、綁定監聽,配置tcp參數,如backlog大小
1
|
serversocketchannel.socket().bind( new inetsocketaddress( 8080 )); |
3、創建一個獨立的i/o線程,用于輪詢多路復用器selector
4、創建selector,將之前創建的serversocketchannel注冊到selector上,監聽selectionkey.accept
1
2
|
selector=selector.open(); serversocketchannel.register(selector, selectionkey.op_accept); |
5、啟動i/o線程,在循環體內執行selector.select()方法,輪詢就緒的channel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
while ( true ) { try { //select()阻塞到至少有一個通道在你注冊的事件上就緒了 //如果沒有準備好的channel,就在這一直阻塞 //select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數)。 selector.select(); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); break ; } } |
6、當輪詢到了處于就緒狀態的channel時,需對其進行判斷,如果是op_accept狀態,說明是新的客戶端接入,則調用serversocketchannel.accept()方法接受新的客戶端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
//返回已經就緒的selectionkey,然后迭代執行 set<selectionkey> readkeys=selector.selectedkeys(); for (iterator<selectionkey> it=readkeys.iterator();it.hasnext();) { selectionkey key=it.next(); it.remove(); try { if (key.isacceptable()) { serversocketchannel server=(serversocketchannel) key.channel(); socketchannel client=server.accept(); client.configureblocking( false ); client.register(selector,selectionkey.op_write); } else if (key.iswritable()) { socketchannel client=(socketchannel) key.channel(); bytebuffer buffer=bytebuffer.allocate( 20 ); string str= "hello" ; buffer=bytebuffer.wrap(str.getbytes()); client.write(buffer); key.cancel(); } } catch (ioexception e) { e.printstacktrace(); key.cancel(); try { key.channel().close(); } catch (ioexception e1) { // todo auto-generated catch block e1.printstacktrace(); } } } |
7、設置新接入的客戶端鏈路socketchannel為非阻塞模式,配置其他的一些tcp參數
1
2
3
4
5
6
7
|
if (key.isacceptable()) { serversocketchannel server=(serversocketchannel) key.channel(); socketchannel client=server.accept(); client.configureblocking( false ); ... } |
8、將socketchannel注冊到selector,監聽op_write
1
|
client.register(selector,selectionkey.op_write); |
9、如果輪詢的channel為op_write,則說明要向sockchannel中寫入數據,則構造bytebuffer對象,寫入數據包
1
2
3
4
5
6
7
8
9
|
else if (key.iswritable()) { socketchannel client=(socketchannel) key.channel(); bytebuffer buffer=bytebuffer.allocate( 20 ); string str= "hello" ; buffer=bytebuffer.wrap(str.getbytes()); client.write(buffer); key.cancel(); } |
完整代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
import java.io.ioexception; import java.net.inetsocketaddress; import java.nio.bytebuffer; import java.nio.channels.selectionkey; import java.nio.channels.selector; import java.nio.channels.serversocketchannel; import java.nio.channels.socketchannel; import java.util.iterator; import java.util.set; public class serversocketchanneldemo { public static void main(string[] args) { serversocketchannel serversocketchannel; selector selector= null ; try { serversocketchannel = serversocketchannel.open(); serversocketchannel.configureblocking( false ); serversocketchannel.socket().bind( new inetsocketaddress( 8080 )); selector=selector.open(); serversocketchannel.register(selector, selectionkey.op_accept); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } while ( true ) { try { //select()阻塞到至少有一個通道在你注冊的事件上就緒了 //如果沒有準備好的channel,就在這一直阻塞 //select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數)。 selector.select(); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); break ; } //返回已經就緒的selectionkey,然后迭代執行 set<selectionkey> readkeys=selector.selectedkeys(); for (iterator<selectionkey> it=readkeys.iterator();it.hasnext();) { selectionkey key=it.next(); it.remove(); try { if (key.isacceptable()) { serversocketchannel server=(serversocketchannel) key.channel(); socketchannel client=server.accept(); client.configureblocking( false ); client.register(selector,selectionkey.op_write); } else if (key.iswritable()) { socketchannel client=(socketchannel) key.channel(); bytebuffer buffer=bytebuffer.allocate( 20 ); string str= "hello" ; buffer=bytebuffer.wrap(str.getbytes()); client.write(buffer); key.cancel(); } } catch (ioexception e) { e.printstacktrace(); key.cancel(); try { key.channel().close(); } catch (ioexception e1) { // todo auto-generated catch block e1.printstacktrace(); } } } } } } |
我們用telnet localhost 8080模擬出多個客戶端:
程序運行結果如下:
總結
以上就是本文關于java nio服務器端開發詳解的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
原文鏈接:http://www.cnblogs.com/xujian2014/p/5657540.html