通過(guò)Java帶你了解網(wǎng)絡(luò)IO模型
1.BIO
1.1 簡(jiǎn)述
BIO是同步阻塞IO,所有連接都是同步執(zhí)行的,在上一個(gè)連接未處理完的時(shí)候是無(wú)法接收下一個(gè)連接
1.2 代碼示例
在上述代碼中,如果啟動(dòng)一個(gè)客戶(hù)端起連接服務(wù)端時(shí)如果沒(méi)有發(fā)送數(shù)據(jù),那么下一個(gè)連接將永遠(yuǎn)無(wú)法進(jìn)來(lái)
public static void main(String[] args) { try { // 監(jiān)聽(tīng)端口 ServerSocket serverSocket = new ServerSocket(8080); // 等待客戶(hù)端的連接過(guò)來(lái),如果沒(méi)有連接過(guò)來(lái),就會(huì)阻塞 while (true) { // 阻塞IO中一個(gè)線程只能處理一個(gè)連接 Socket socket = serverSocket.accept(); System.out.println("客戶(hù)端建立連接:"+socket.getPort()); String line = null; try { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); line = bufferedReader.readLine(); System.out.println("客戶(hù)端的數(shù)據(jù):" + line); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bufferedWriter.write("ok\n"); bufferedWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }
1.3優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
簡(jiǎn)單易用,代碼實(shí)現(xiàn)比較簡(jiǎn)單。
對(duì)于低并發(fā)量的場(chǎng)景,因?yàn)槊總€(gè)連接都有獨(dú)占的線程處理IO操作,因此可以保證每個(gè)連接的IO操作都能夠及時(shí)得到處理。
對(duì)于數(shù)據(jù)量較小的IO操作,同步阻塞IO模型的性能表現(xiàn)較好。
缺點(diǎn):
由于每一個(gè)客戶(hù)端連接都需要開(kāi)啟一個(gè)線程,因此無(wú)法承載高并發(fā)的場(chǎng)景。
線程切換的開(kāi)銷(xiāo)比較大,會(huì)導(dǎo)致系統(tǒng)性能下降。
對(duì)于IO操作較慢的情況下,會(huì)占用大量的線程資源,導(dǎo)致系統(tǒng)負(fù)載過(guò)高。
對(duì)于處理大量連接的服務(wù)器,BIO模型的性能較低,無(wú)法滿(mǎn)足需求。
1.4 思考
問(wèn):既然每個(gè)連接進(jìn)來(lái)都會(huì)阻塞,那么是否可以使用多線程的方式接收處理?
答:當(dāng)然可以,但是這樣如果有1w個(gè)連接那么就要啟動(dòng)1w個(gè)線程去處理嗎,線程是非常寶貴的資源,頻繁使用線程對(duì)系統(tǒng)的開(kāi)銷(xiāo)是非常大的
2. NoBlockingIO
2.1 簡(jiǎn)述
NoBlockingIO是同步非阻塞IO,相對(duì)比阻塞IO,他在接收數(shù)據(jù)的時(shí)候是非阻塞的,會(huì)一直輪詢(xún)去問(wèn)內(nèi)核是否準(zhǔn)備好數(shù)據(jù),直到有數(shù)據(jù)返回
ps: NoBlockingIO并不是真正意義上的NIO
2.2 代碼示例
在下述代碼中,將BIO中的ServerSocket修改為ServerSocketChannel,然后configureBlocking為false則為非阻塞,從而數(shù)據(jù)都是在channel的buffer(緩沖區(qū))中獲取,不理解沒(méi)關(guān)系,就當(dāng)作是設(shè)置非阻塞IO的方式就好
此時(shí)在accept中是非阻塞的,不斷的等待客戶(hù)端進(jìn)來(lái)
注意
- accept是非阻塞,不斷輪詢(xún),如果為空則跳過(guò),不為空則添加連接
- 讀數(shù)據(jù)是非阻塞,不斷的輪詢(xún)連接,等待客戶(hù)端寫(xiě)入數(shù)據(jù)
public static List<SocketChannel> channelList = new ArrayList<>(); public static void main(String[] args) { try { // 相當(dāng)于serverSocket // 1.支持非阻塞 2.數(shù)據(jù)總是寫(xiě)入buffer,讀取也是從buffer中去讀 3.可以同時(shí)讀寫(xiě) ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 設(shè)置非阻塞 serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); while (true){ // 這里將不再阻塞 SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ socketChannel.configureBlocking(false); channelList.add(socketChannel); }else { System.out.println("沒(méi)有請(qǐng)求過(guò)來(lái)?。?!"); } for (SocketChannel client : channelList){ ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 也不阻塞 int num = client.read(byteBuffer); if(num>0){ System.out.println("客戶(hù)端端口:"+ client.socket().getPort()+",客戶(hù)端收據(jù):"+new String(byteBuffer.array())); }else { System.out.println("等待客戶(hù)端寫(xiě)數(shù)據(jù)"); } } } } catch (IOException e) { e.printStackTrace(); } }
2.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
非阻塞I/O可以同時(shí)處理多個(gè)客戶(hù)端連接,提高服務(wù)器的并發(fā)處理能力。
由于非阻塞I/O的模式下,一個(gè)線程可以處理多個(gè)I/O操作,因此可以減少線程切換次數(shù),提高系統(tǒng)性能
缺點(diǎn):
- 有很多無(wú)效訪問(wèn),因?yàn)闆](méi)有連接的時(shí)候accept也不會(huì)阻塞,很多為空的accpet
- 如果客戶(hù)端沒(méi)有寫(xiě)數(shù)據(jù),會(huì)一直向內(nèi)核訪問(wèn),每次都是一個(gè)系統(tǒng)調(diào)用,非常浪費(fèi)系統(tǒng)資源
2.4 思考
問(wèn) :既然一直輪詢(xún)會(huì)產(chǎn)生很多的無(wú)效輪詢(xún),并浪費(fèi)系統(tǒng)資源,那么有沒(méi)有更好的辦法呢
答: 通過(guò)事件注冊(cè)的方式(多路復(fù)用器)
3. NIO(NewIO)
3.1 簡(jiǎn)述
NewIO才是真正意義上的NIO,NoBlockingIO只能算是NIO的前身,因?yàn)镹ewIO在NoBlockingIO上加上了多路復(fù)用器,使得NIO更加完美
在下圖中,channel不再是直接循環(huán)調(diào)用內(nèi)核,而是將連接,接收,讀取,寫(xiě)入等事件注冊(cè)到多路復(fù)用器中,如果沒(méi)有事件到來(lái)將會(huì)阻塞等待
NIO三件套(記):
- channel: 介于字節(jié)緩沖區(qū)(buffer)和套接字(socket)之間,可以同時(shí)讀寫(xiě),支持異步IO
- buffer: 字節(jié)緩沖區(qū),是應(yīng)用程序和通道之間進(jìn)行IO數(shù)據(jù)傳輸?shù)闹修D(zhuǎn)
- selector:多路復(fù)用器,監(jiān)聽(tīng)服務(wù)端和客戶(hù)端的管道上注冊(cè)的事件
3.2 代碼示例
從代碼示例可以看到,在沒(méi)有連接的時(shí)候會(huì)在selector.select()中阻塞,然后等待客戶(hù)端連接或者寫(xiě)入數(shù)據(jù),不同的監(jiān)聽(tīng)事件會(huì)有不同的處理方法
具體流程:
服務(wù)端創(chuàng)建Selector,并注冊(cè)O(shè)P_ACCEPT接受連接事件,然后調(diào)用select阻塞等待連接進(jìn)來(lái)
客戶(hù)端注冊(cè)O(shè)P_CONNECT事件,表示連接客戶(hù)端,連接成功后會(huì)調(diào)用handlerConnect方法
2.1 handlerConnect方法會(huì)注冊(cè)O(shè)P_READ事件并向服務(wù)端寫(xiě)數(shù)據(jù)
這時(shí)候服務(wù)端會(huì)收到OP_ACCEPT后就會(huì)走到handlerAccept方法,表示接受連接
3.1handlerAccept方法也會(huì)注冊(cè)一個(gè)OP_READ事件并向客戶(hù)端寫(xiě)數(shù)據(jù)
客戶(hù)端接收到服務(wù)端的數(shù)據(jù)后會(huì)再次喚醒select方法,然后判斷為isReadable(讀事件,服務(wù)端寫(xiě)入給客戶(hù)端,那么客戶(hù)端就是讀),handlerRead方法將會(huì)把服務(wù)端寫(xiě)入的數(shù)據(jù)讀取
反之亦然,服務(wù)端也會(huì)收到客戶(hù)端寫(xiě)入的數(shù)據(jù),然后通過(guò)讀事件將數(shù)據(jù)讀取
服務(wù)端代碼
public class NewIOServer { static Selector selector; public static void main(String[] args) { try { selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 需要把serverSocketChannel注冊(cè)到多路復(fù)用器上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 阻塞 selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isAcceptable()) { handlerAccept(key); } else if (key.isReadable()) { handlerRead(key); }else if(key.isWritable()){ } } } } catch (IOException e) { e.printStackTrace(); } } private static void handlerRead(SelectionKey key) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer allocate = ByteBuffer.allocate(1024); try { socketChannel.read(allocate); System.out.println("server msg:" + new String(allocate.array())); } catch (IOException e) { e.printStackTrace(); } } private static void handlerAccept(SelectionKey key) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); // 不阻塞 try { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.write(ByteBuffer.wrap("It‘s server msg".getBytes())); // 讀取客戶(hù)端的數(shù)據(jù) socketChannel.register(selector, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } }
客戶(hù)端代碼
public class NewIOClient { static Selector selector; public static void main(String[] args) { try { selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("localhost", 8080)); // 需要把socketChannel注冊(cè)到多路復(fù)用器上 socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { // 阻塞 selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isConnectable()) { handlerConnect(key); } else if (key.isReadable()) { handlerRead(key); } else if (key.isWritable()) { } } } } catch (IOException e) { e.printStackTrace(); } } private static void handlerRead(SelectionKey key) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer allocate = ByteBuffer.allocate(1024); try { socketChannel.read(allocate); System.out.println("client msg:" + new String(allocate.array())); } catch (IOException e) { e.printStackTrace(); } } private static void handlerConnect(SelectionKey key) throws IOException { SocketChannel socketChannel = (SocketChannel) key.channel(); if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); } socketChannel.configureBlocking(false); socketChannel.write(ByteBuffer.wrap("it‘s client msg".getBytes())); socketChannel.register(selector,SelectionKey.OP_READ); } }
3.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
NIO使用了非阻塞IO,可以大大提高系統(tǒng)的吞吐量和并發(fā)性能。
NIO提供了可擴(kuò)展的選擇器,可以監(jiān)控多個(gè)通道的狀態(tài),從而實(shí)現(xiàn)高效的事件驅(qū)動(dòng)模型。
NIO采用直接內(nèi)存緩沖區(qū),可以避免Java堆內(nèi)存的GC問(wèn)題,提高內(nèi)存管理的效率。
缺點(diǎn):
- NIO的編程模型相比傳統(tǒng)的IO模型更加復(fù)雜,需要掌握較多的API和概念。
- NIO的實(shí)現(xiàn)難度較高,需要處理很多細(xì)節(jié)問(wèn)題,如緩沖區(qū)的管理、選擇器的使用等。
- NIO的可靠性不如傳統(tǒng)的IO模型,容易出現(xiàn)空輪詢(xún)、系統(tǒng)負(fù)載過(guò)高等問(wèn)題。
3.4 思考
問(wèn):select方法不是也阻塞嗎,那跟BIO有什么區(qū)別?
答:雖然他是在select阻塞,但是他通過(guò)事件注冊(cè)的方式,可以將多個(gè)selectKey同時(shí)加載到selectionKeys集合中,通過(guò)for循環(huán)處理不同的事件,而B(niǎo)IO只能由一個(gè)連接處理完才能處理下一個(gè)連接
問(wèn):什么是多路復(fù)用?
答:
多路:是指多個(gè)連接的管道,通道
復(fù)用:復(fù)用一個(gè)系統(tǒng)調(diào)用,原本多次系統(tǒng)調(diào)用變成一次
4. 擴(kuò)展select/poll、epoll
4.1 簡(jiǎn)述
由第三部分的NIO可知,多路復(fù)用把for循環(huán)的系統(tǒng)調(diào)用變成了一次調(diào)用,那么他具體是怎么實(shí)現(xiàn)的?
其實(shí)我們仔細(xì)思考一下就能知道,他主要實(shí)現(xiàn)就是在selector.select()
,由他去阻塞和觸發(fā)動(dòng)作。然而在實(shí)現(xiàn)這些功能的時(shí)候,就用到了三種模型,select、poll、epoll。因?yàn)閟elect和poll很相似,所以大家都會(huì)把他們歸為一類(lèi)。
4.2 select/poll
我們先來(lái)說(shuō)說(shuō)什么是select?
實(shí)現(xiàn)過(guò)程
- 每一個(gè)socket調(diào)用select()方法后,socket的等待隊(duì)列就會(huì)放線程的引用,該線程就是你調(diào)用select的那個(gè)線程
- 當(dāng)其中一個(gè)socket發(fā)送數(shù)據(jù)的時(shí)候,他會(huì)將每一個(gè)socket在等待隊(duì)列中移除放入就緒隊(duì)列,這就表明一定有一個(gè)客戶(hù)端寫(xiě)了數(shù)據(jù)過(guò)來(lái),但是注意,這并不表示所有都有客戶(hù)端寫(xiě)了數(shù)據(jù)過(guò)來(lái)
- 這時(shí)候喚醒主線程,然后去就緒隊(duì)列中遍歷找到客戶(hù)端寫(xiě)的數(shù)據(jù)并返回
具體如下圖所示:
產(chǎn)生問(wèn)題
- 因?yàn)閒d(file)是個(gè)數(shù)組,所以socket容量會(huì)有上限
- 只要有一個(gè)socket寫(xiě)入就會(huì)遍歷所有socket,雖然減少了空輪詢(xún)問(wèn)題,但是每次都要在所有socket中去找到已準(zhǔn)備好的那個(gè)socket需要消耗性能
什么是poll?
因?yàn)閒d是個(gè)數(shù)組,所以容量會(huì)達(dá)到上限,而poll則將這個(gè)數(shù)據(jù)結(jié)構(gòu)改成了鏈表,所以解決了select模型中上限的問(wèn)題,但是遍歷socket的問(wèn)題還是存在
select和poll的本質(zhì)區(qū)別就是一個(gè)是用數(shù)組存放socket,一個(gè)是用鏈表存放,其他地方?jīng)]有任何區(qū)別
4.3 epoll
epoll和select/poll相比,采用了事件回調(diào)的機(jī)制,并且使用紅黑樹(shù)去維護(hù)注冊(cè)的socket,如下圖所示
實(shí)現(xiàn)過(guò)程:
- 調(diào)用Selector.open的時(shí)候會(huì)創(chuàng)建一個(gè)eventpoll的文件,里面主要含有等待隊(duì)列,rbr(紅黑樹(shù)),就緒列表
- 然后在建立連接的時(shí)候調(diào)用epoll_ctl函數(shù)將socket放入epitem中
- 調(diào)用epoll_wait函數(shù)將線程放入等待隊(duì)列中,等待數(shù)據(jù)過(guò)來(lái)時(shí)喚醒
- 有數(shù)據(jù)寫(xiě)入的時(shí)候會(huì)觸發(fā)epitem的回調(diào)方法,將該epitem移除并加入rdlist就緒列表中
- 當(dāng)有數(shù)據(jù)在就緒列表的時(shí)候,就會(huì)喚醒等待對(duì)列中的線程并處理數(shù)據(jù)
這樣通過(guò)紅黑樹(shù)來(lái)維護(hù)連接和通過(guò)就緒列表來(lái)處理數(shù)據(jù)就可以保證可以存放最大限度的socket數(shù)量,并且在喚醒線程處理去處理就緒列表的時(shí)候肯定都是需要處理并且已就緒的socket。完美的解決了select/poll中的問(wèn)題
總結(jié)
epoll相較于select/poll的優(yōu)勢(shì):
采用了事件驅(qū)動(dòng)的方式,可以處理大量的連接,效率更高。
支持邊緣觸發(fā)(ET)和水平觸發(fā)(LT)兩種模式,可以更靈活地處理IO事件。
記錄了上次處理的位置,可以避免重復(fù)的遍歷,更加高效。
高效利用了內(nèi)核空間和用戶(hù)空間的交互,避免了復(fù)制文件描述符。
4.4 擴(kuò)展話題
對(duì)于epoll的一些擴(kuò)展,有興趣的可以了解下,不感興趣可以略過(guò)
4.4.1 什么是ET和LT?
ET和LT是epoll工作模式中的兩種觸發(fā)方式,分別表示邊緣觸發(fā)(Edge Triggered)和水平觸發(fā)(Level Triggered)。
邊緣觸發(fā)(ET)
在ET模式下,當(dāng)一個(gè)文件描述符上出現(xiàn)事件時(shí),epoll_wait函數(shù)只會(huì)通知一次,即只有在文件描述符狀態(tài)發(fā)生變化時(shí)才會(huì)返回。如果應(yīng)用程序沒(méi)有處理完這個(gè)事件,那么下一次調(diào)用epoll_wait函數(shù)時(shí),它不會(huì)再返回這個(gè)事件,直到下一次狀態(tài)變化。
ET模式下的事件處理更為高效,因?yàn)樗粫?huì)在必要的時(shí)候通知應(yīng)用程序,避免了重復(fù)通知的問(wèn)題。但是,由于ET模式只在狀態(tài)變化時(shí)通知一次,因此應(yīng)用程序需要及時(shí)處理事件,否則可能會(huì)錯(cuò)過(guò)某些事件。
水平觸發(fā)(LT)
在LT模式下,當(dāng)一個(gè)文件描述符上出現(xiàn)事件時(shí),epoll_wait函數(shù)會(huì)重復(fù)通知應(yīng)用程序,直到該文件描述符上的事件被處理完畢為止。如果應(yīng)用程序沒(méi)有處理完這個(gè)事件,那么下一次調(diào)用epoll_wait函數(shù)時(shí),它會(huì)再次返回這個(gè)事件,直到應(yīng)用程序處理完為止。
LT模式下的事件處理比較簡(jiǎn)單,因?yàn)樗鼤?huì)重復(fù)通知應(yīng)用程序,直到應(yīng)用程序處理完為止。但是,由于重復(fù)通知的問(wèn)題,LT模式下可能會(huì)導(dǎo)致一些性能問(wèn)題。同時(shí),在LT模式下,應(yīng)用程序需要及時(shí)處理事件,否則可能會(huì)導(dǎo)致文件描述符上的事件積壓,影響系統(tǒng)的性能。
4.4.2 什么是驚群?
epoll的驚群(Thundering Herd)指的是多個(gè)線程或進(jìn)程同時(shí)等待同一個(gè)epoll文件描述符上的事件,
當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,但只有一個(gè)線程或進(jìn)程能夠真正處理該事件,其他線程或進(jìn)程會(huì)被喚醒但不能處理該事件,從而造成資源浪費(fèi)和性能降低的問(wèn)題。
驚群?jiǎn)栴}是由于內(nèi)核通知等待線程或進(jìn)程的方式引起的。在epoll中,當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,而不是通知一個(gè)線程或進(jìn)程。因此,如果有多個(gè)線程或進(jìn)程等待同一個(gè)文件描述符,那么當(dāng)該文件描述符上出現(xiàn)事件時(shí),內(nèi)核會(huì)通知所有等待的線程或進(jìn)程,導(dǎo)致驚群?jiǎn)栴}。
為了解決驚群?jiǎn)栴},可以采用以下兩種方式:
- 使用邊緣觸發(fā)(ET)模式:在ET模式下,當(dāng)文件描述符上出現(xiàn)事件時(shí),內(nèi)核只會(huì)通知一個(gè)等待的線程或進(jìn)程,從而避免了驚群?jiǎn)栴}。
- 采用互斥量或條件變量等機(jī)制:在多個(gè)線程或進(jìn)程等待同一個(gè)文件描述符時(shí),可以使用互斥量或條件變量等機(jī)制來(lái)控制線程或進(jìn)程的喚醒,從而避免驚群?jiǎn)栴}。
5. AIO
5.1簡(jiǎn)述
在上面將的BIO,NIO中都是同步IO,BIO叫做同步阻塞,NIO叫做同步非阻塞,那么AIO則是異步IO,全名(Asynchronous I/O)
5.2 代碼示例
從代碼示例可以看到,以下代碼都是基于回調(diào)機(jī)制實(shí)現(xiàn)的,并不會(huì)像BIO和NIO一樣使用輪詢(xún)的方式,他不需要像同步IO一樣需要查找就緒socket,只要客戶(hù)端有數(shù)據(jù)寫(xiě)入就會(huì)回調(diào)給服務(wù)端,既然是異步的所以就不會(huì)存在阻塞
服務(wù)端代碼
public class AIOServer { public static void main(String[] args) throws Exception { // 創(chuàng)建一個(gè)SocketChannel并綁定了8080端口 final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { try { // 打印線程的名字 System.out.println("2--"+Thread.currentThread().getName()); System.out.println(socketChannel.getRemoteAddress()); ByteBuffer buffer = ByteBuffer.allocate(1024); // socketChannel異步的讀取數(shù)據(jù)到buffer中 socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { // 打印線程的名字 System.out.println("3--"+Thread.currentThread().getName()); buffer.flip(); System.out.println(new String(buffer.array(), 0, result)); socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes())); } @Override public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); } }); } catch (IOException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); System.out.println("1--"+Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); } }
客戶(hù)端代碼
public class AIOClient { private final AsynchronousSocketChannel client; public AIOClient() throws IOException { client = AsynchronousSocketChannel.open(); } public static void main(String[] args) throws Exception { new AIOClient().connect("localhost",8080); } public void connect(String host, int port) throws Exception { // 客戶(hù)端向服務(wù)端發(fā)起連接 client.connect(new InetSocketAddress(host, port), null, new CompletionHandler<Void, Object>() { @Override public void completed(Void result, Object attachment) { try { client.write(ByteBuffer.wrap("這是一條測(cè)試數(shù)據(jù)".getBytes())).get(); System.out.println("已發(fā)送到服務(wù)端"); } catch (Exception e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); final ByteBuffer bb = ByteBuffer.allocate(1024); // 客戶(hù)端接收服務(wù)端的數(shù)據(jù),獲取的數(shù)據(jù)寫(xiě)入到bb中 client.read(bb, null, new CompletionHandler<Integer, Object>() { @Override public void completed(Integer result, Object attachment) { // 服務(wù)端返回?cái)?shù)據(jù)的長(zhǎng)度result System.out.println("I/O操作完成:" + result); System.out.println("獲取反饋結(jié)果:" + new String(bb.array())); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } }
5.3 優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)勢(shì):
更加高效:AIO采用回調(diào)方式,可以避免輪詢(xún)等操作對(duì)CPU的占用,減少CPU的負(fù)擔(dān),從而提高了系統(tǒng)的性能。
可以更好地利用系統(tǒng)資源:AIO能夠在I/O操作完成之前把線程釋放出來(lái),可以更好地利用系統(tǒng)資源,提高系統(tǒng)的并發(fā)處理能力。
適用于高并發(fā)場(chǎng)景:AIO適用于高并發(fā)場(chǎng)景,能夠支持大量的并發(fā)連接,提高系統(tǒng)的處理能力。
缺點(diǎn):
學(xué)習(xí)成本高:相比于NIO,AIO的編程模型更加復(fù)雜,需要學(xué)習(xí)更多的知識(shí),學(xué)習(xí)成本更高。
實(shí)現(xiàn)難度大:AIO的實(shí)現(xiàn)難度比較大,需要對(duì)操作系統(tǒng)的底層機(jī)制有深入的了解,因此開(kāi)發(fā)成本較高。
并非所有操作系統(tǒng)都支持:AIO并非所有操作系統(tǒng)都支持,只有Linux 2.6以上的內(nèi)核才支持AIO,因此跨平臺(tái)的支持較差。
ps: 說(shuō)白了AIO很好用,但是太復(fù)雜
以上就是通過(guò)Java帶你了解網(wǎng)絡(luò)IO模型的詳細(xì)內(nèi)容,更多關(guān)于Java 網(wǎng)絡(luò)IO模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于TreeMap自定義排序規(guī)則的兩種方式
這篇文章主要介紹了關(guān)于TreeMap自定義排序規(guī)則的兩種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之無(wú)權(quán)無(wú)向圖
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之無(wú)權(quán)無(wú)向圖?,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01Java中關(guān)于String StringBuffer StringBuilder特性深度解析
這篇文章主要介紹了Java中關(guān)于String StringBuffer StringBuilder特性深度解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09java合成模式之神奇的樹(shù)結(jié)構(gòu)
這篇文章主要介紹了java合成模式,文中運(yùn)用大量的代碼進(jìn)行詳細(xì)講解,希望大家看完本文后能學(xué)習(xí)到相關(guān)的知識(shí),需要的朋友可以參考一下2021-08-08mybatis-puls中的resultMap數(shù)據(jù)映射
這篇文章主要介紹了mybatis-puls中的resultMap數(shù)據(jù)映射,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Serializable接口的作用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了java中Serializable接口的作用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05SpringBoot項(xiàng)目?jī)?yōu)雅的全局異常處理方式(全網(wǎng)最新)
這篇文章主要介紹了SpringBoot項(xiàng)目?jī)?yōu)雅的全局異常處理方式(全網(wǎng)最新),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04