欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)邀請(qǐng)碼778899分享:網(wǎng)絡(luò) io,nio,aio總結(jié)

柚子快報(bào)邀請(qǐng)碼778899分享:網(wǎng)絡(luò) io,nio,aio總結(jié)

http://yzkb.51969.com/

I/O簡(jiǎn)介

I/O(Input/Outpu) 即輸入/輸出 。

我們先從計(jì)算機(jī)結(jié)構(gòu)的角度來(lái)解讀一下 I/O。

根據(jù)馮.諾依曼結(jié)構(gòu),計(jì)算機(jī)結(jié)構(gòu)分為 5 大部分:運(yùn)算器、控制器、存儲(chǔ)器、輸入設(shè)備、輸出設(shè)備。

輸入設(shè)備(比如鍵盤(pán))和輸出設(shè)備(比如顯示器)都屬于外部設(shè)備。網(wǎng)卡、硬盤(pán)這種既可以屬于輸入設(shè)備,也可以屬于輸出設(shè)備。

輸入設(shè)備向計(jì)算機(jī)輸入數(shù)據(jù),輸出設(shè)備接收計(jì)算機(jī)輸出的數(shù)據(jù)。

從計(jì)算機(jī)結(jié)構(gòu)的視角來(lái)看的話(huà), I/O 描述了計(jì)算機(jī)系統(tǒng)與外部設(shè)備之間通信的過(guò)程。

我們?cè)傧葟膽?yīng)用程序的角度來(lái)解讀一下 I/O。

根據(jù)大學(xué)里學(xué)到的操作系統(tǒng)相關(guān)的知識(shí):為了保證操作系統(tǒng)的穩(wěn)定性和安全性,一個(gè)進(jìn)程的地址空間劃分為 用戶(hù)空間(User space) 和 內(nèi)核空間(Kernel space ) 。

像我們平常運(yùn)行的應(yīng)用程序都是運(yùn)行在用戶(hù)空間,只有內(nèi)核空間才能進(jìn)行系統(tǒng)態(tài)級(jí)別的資源有關(guān)的操作,比如文件管理、進(jìn)程通信、內(nèi)存管理等等。也就是說(shuō),我們想要進(jìn)行 IO 操作,一定是要依賴(lài)內(nèi)核空間的能力。

并且,用戶(hù)空間的程序不能直接訪問(wèn)內(nèi)核空間。

當(dāng)想要執(zhí)行 IO 操作時(shí),由于沒(méi)有執(zhí)行這些操作的權(quán)限,只能發(fā)起系統(tǒng)調(diào)用請(qǐng)求操作系統(tǒng)幫忙完成。

因此,用戶(hù)進(jìn)程想要執(zhí)行 IO 操作的話(huà),必須通過(guò) 系統(tǒng)調(diào)用 來(lái)間接訪問(wèn)內(nèi)核空間

我們?cè)谄匠i_(kāi)發(fā)過(guò)程中接觸最多的就是 磁盤(pán) IO(讀寫(xiě)文件) 和 網(wǎng)絡(luò) IO(網(wǎng)絡(luò)請(qǐng)求和響應(yīng))。

從應(yīng)用程序的視角來(lái)看的話(huà),我們的應(yīng)用程序?qū)Σ僮飨到y(tǒng)的內(nèi)核發(fā)起 IO 調(diào)用(系統(tǒng)調(diào)用),操作系統(tǒng)負(fù)責(zé)的內(nèi)核執(zhí)行具體的 IO 操作。也就是說(shuō),我們的應(yīng)用程序?qū)嶋H上只是發(fā)起了 IO 操作的調(diào)用而已,具體 IO 的執(zhí)行是由操作系統(tǒng)的內(nèi)核來(lái)完成的。

當(dāng)應(yīng)用程序發(fā)起 I/O 調(diào)用后,會(huì)經(jīng)歷兩個(gè)步驟:

內(nèi)核等待 I/O 設(shè)備準(zhǔn)備好數(shù)據(jù)內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶(hù)空間。

同步/異步,阻塞/非阻塞

同步和異步關(guān)注的是消息通信機(jī)制.

同步是指: 發(fā)送方發(fā)出數(shù)據(jù)后, 等待接收方發(fā)回響應(yīng)后才發(fā)下一個(gè)數(shù)據(jù)包的通訊方式. 就是在發(fā)出一個(gè)調(diào)用時(shí), 在沒(méi)有得到結(jié)果之前, 該調(diào)用就不返回, 但是一旦調(diào)用返回, 就得到返回值了. 也就是由"調(diào)用者"主動(dòng)等待這個(gè)"調(diào)用"的結(jié)果.

異步是指: 發(fā)送方發(fā)出數(shù)據(jù)后, 不等待接收方發(fā)回響應(yīng), 接著發(fā)送下個(gè)數(shù)據(jù)包的通訊方式. 當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后, 調(diào)用者不會(huì)立刻得到結(jié)果. 而是在調(diào)用發(fā)出后, "被調(diào)用者"通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者, 或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用.

阻塞和非阻塞屬于進(jìn)程API執(zhí)行動(dòng)作的方式, 關(guān)注的是程序在等待調(diào)用結(jié)果時(shí)的狀態(tài).

阻塞是指: 調(diào)用結(jié)果返回之前, 當(dāng)前線(xiàn)程會(huì)被掛起. 函數(shù)只有在得到結(jié)果之后才會(huì)返回, 線(xiàn)程需要等待結(jié)果.

非阻塞是指: 與阻塞的概念相對(duì)應(yīng), 指在不能立刻得到結(jié)果之前, 該函數(shù)不會(huì)阻塞當(dāng)前線(xiàn)程, 而會(huì)立刻返回. 線(xiàn)程不需要等待結(jié)果.

常見(jiàn)的 IO 模型

UNIX 系統(tǒng)下, IO 模型一共有 5 種: 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路復(fù)用、信號(hào)驅(qū)動(dòng) I/O 和異步 I/O。

這也是我們經(jīng)常提到的 5 種 IO 模型。

java中的bio,nio,aio的簡(jiǎn)介

BIO (Blocking I/O)

BIO 屬于同步阻塞 IO 模型 。

同步阻塞 IO 模型中,應(yīng)用程序發(fā)起 read 調(diào)用后,會(huì)一直阻塞,直到內(nèi)核把數(shù)據(jù)拷貝到用戶(hù)空間。

在客戶(hù)端連接數(shù)量不高的情況下,是沒(méi)問(wèn)題的。但是,當(dāng)面對(duì)十萬(wàn)甚至百萬(wàn)級(jí)連接的時(shí)候,傳統(tǒng)的 BIO 模型是無(wú)能為力的。因此,我們需要一種更高效的 I/O 處理模型來(lái)應(yīng)對(duì)更高的并發(fā)量。

BIO的優(yōu)點(diǎn):

程序簡(jiǎn)單,在阻塞等待數(shù)據(jù)期間,用戶(hù)線(xiàn)程掛起。用戶(hù)線(xiàn)程基本不會(huì)占用 CPU 資源。

BIO的缺點(diǎn):

一般情況下,會(huì)為每個(gè)連接配套一條獨(dú)立的線(xiàn)程,或者說(shuō)一條線(xiàn)程維護(hù)一個(gè)連接成功的IO流的讀寫(xiě)。在并發(fā)量小的情況下,這個(gè)沒(méi)有什么問(wèn)題。但是,當(dāng)在高并發(fā)的場(chǎng)景下,需要大量的線(xiàn)程來(lái)維護(hù)大量的網(wǎng)絡(luò)連接,內(nèi)存、線(xiàn)程切換開(kāi)銷(xiāo)會(huì)非常巨大。因此,基本上,BIO模型在高并發(fā)場(chǎng)景下是不可用的。

NIO (Non-blocking/New I/O)

Java 中的 NIO 于 Java 1.4 中引入,對(duì)應(yīng) java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它是支持面向緩沖的,基于通道的 I/O 操作方法。 對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用 NIO 。流行基于Java NIO通信框架有Mina、Netty、Grizzly等

Java 中的 NIO 可以看作是 I/O 多路復(fù)用模型。也有很多人認(rèn)為,Java 中的 NIO 屬于同步非阻塞 IO 模型。

我們先來(lái)看看 同步非阻塞 IO 模型。

同步非阻塞 IO 模型中,應(yīng)用程序會(huì)一直發(fā)起 read 調(diào)用,等待數(shù)據(jù)從內(nèi)核空間拷貝到用戶(hù)空間的這段時(shí)間里,線(xiàn)程依然是阻塞的,直到在內(nèi)核把數(shù)據(jù)拷貝到用戶(hù)空間。

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型確實(shí)有了很大改進(jìn)。通過(guò)輪詢(xún)操作,避免了一直阻塞。

但是,這種 IO 模型同樣存在問(wèn)題:應(yīng)用程序不斷進(jìn)行 I/O 系統(tǒng)調(diào)用輪詢(xún)數(shù)據(jù)是否已經(jīng)準(zhǔn)備好的過(guò)程是十分消耗 CPU 資源的。

NIO的優(yōu)點(diǎn):每次發(fā)起的 IO 系統(tǒng)調(diào)用,在內(nèi)核的等待數(shù)據(jù)過(guò)程中可以立即返回。用戶(hù)線(xiàn)程不會(huì)阻塞,實(shí)時(shí)性較好。

NIO的缺點(diǎn):需要不斷的重復(fù)發(fā)起IO系統(tǒng)調(diào)用,這種不斷的輪詢(xún),將會(huì)不斷地詢(xún)問(wèn)內(nèi)核,這將占用大量的 CPU 時(shí)間,系統(tǒng)資源利用率較低。

總之,NIO模型在高并發(fā)場(chǎng)景下,也是不可用的。一般 Web 服務(wù)器不使用這種 IO 模型。一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性。java的實(shí)際開(kāi)發(fā)中,也不會(huì)涉及這種IO模型。

再次說(shuō)明,Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一種模型,叫做IO多路復(fù)用模型( IO multiplexing )。

I/O 多路復(fù)用模型

IO 多路復(fù)用模型中,線(xiàn)程首先發(fā)起 select 調(diào)用,詢(xún)問(wèn)內(nèi)核數(shù)據(jù)是否準(zhǔn)備就緒,等內(nèi)核把數(shù)據(jù)準(zhǔn)備好了,用戶(hù)線(xiàn)程再發(fā)起 read 調(diào)用。read 調(diào)用的過(guò)程(數(shù)據(jù)從內(nèi)核空間 -> 用戶(hù)空間)還是阻塞的。

目前支持 IO 多路復(fù)用的系統(tǒng)調(diào)用,有 select,epoll 等等。select 系統(tǒng)調(diào)用,目前幾乎在所有的操作系統(tǒng)上都有支持。

select 調(diào)用 :內(nèi)核提供的系統(tǒng)調(diào)用,它支持一次查詢(xún)多個(gè)系統(tǒng)調(diào)用的可用狀態(tài)。幾乎所有的操作系統(tǒng)都支持。epoll 調(diào)用 :linux 2.6 內(nèi)核,屬于 select 調(diào)用的增強(qiáng)版本,優(yōu)化了 IO 的執(zhí)行效率。

IO 多路復(fù)用模型,通過(guò)減少無(wú)效的系統(tǒng)調(diào)用,減少了對(duì) CPU 資源的消耗。

Java 中的 NIO ,有一個(gè)非常重要的選擇器 ( Selector ) 的概念,也可以被稱(chēng)為 多路復(fù)用器。通過(guò)它,只需要一個(gè)線(xiàn)程便可以管理多個(gè)客戶(hù)端連接。當(dāng)客戶(hù)端數(shù)據(jù)到了之后,才會(huì)為其服務(wù)。

多路復(fù)用IO的特點(diǎn):

IO多路復(fù)用模型,建立在操作系統(tǒng)kernel內(nèi)核能夠提供的多路分離系統(tǒng)調(diào)用select/epoll基礎(chǔ)之上的。多路復(fù)用IO需要用到兩個(gè)系統(tǒng)調(diào)用(system call), 一個(gè)select/epoll查詢(xún)調(diào)用,一個(gè)是IO的讀取調(diào)用。

和NIO模型相似,多路復(fù)用IO需要輪詢(xún)。負(fù)責(zé)select/epoll查詢(xún)調(diào)用的線(xiàn)程,需要不斷的進(jìn)行select/epoll輪詢(xún),查找出可以進(jìn)行IO操作的連接。

另外,多路復(fù)用IO模型與前面的NIO模型,是有關(guān)系的。對(duì)于每一個(gè)可以查詢(xún)的socket,一般都設(shè)置成為non-blocking模型。只是這一點(diǎn),對(duì)于用戶(hù)程序是透明的(不感知)。

多路復(fù)用IO的優(yōu)點(diǎn):

用select/epoll的優(yōu)勢(shì)在于,它可以同時(shí)處理成千上萬(wàn)個(gè)連接(connection)。與一條線(xiàn)程維護(hù)一個(gè)連接相比,I/O多路復(fù)用技術(shù)的最大優(yōu)勢(shì)是:系統(tǒng)不必創(chuàng)建線(xiàn)程,也不必維護(hù)這些線(xiàn)程,從而大大減小了系統(tǒng)的開(kāi)銷(xiāo)。

Java的NIO(new IO)技術(shù),使用的就是IO多路復(fù)用模型。在linux系統(tǒng)上,使用的是epoll系統(tǒng)調(diào)用。

多路復(fù)用IO的缺點(diǎn):

本質(zhì)上,select/epoll系統(tǒng)調(diào)用,屬于同步IO,也是阻塞IO。都需要在讀寫(xiě)事件就緒后,自己負(fù)責(zé)進(jìn)行讀寫(xiě),也就是說(shuō)這個(gè)讀寫(xiě)過(guò)程是阻塞的。

AIO (Asynchronous I/O)

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進(jìn)版 NIO 2,它是異步 IO 模型。

異步 IO 是基于事件和回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會(huì)直接返回,不會(huì)堵塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)會(huì)通知相應(yīng)的線(xiàn)程進(jìn)行后續(xù)的操作。

異步IO模型的特點(diǎn):

在內(nèi)核kernel的等待數(shù)據(jù)和復(fù)制數(shù)據(jù)的兩個(gè)階段,用戶(hù)線(xiàn)程都不是block(阻塞)的。用戶(hù)線(xiàn)程需要接受kernel的IO操作完成的事件,或者說(shuō)注冊(cè)IO操作完成的回調(diào)函數(shù),到操作系統(tǒng)的內(nèi)核。所以說(shuō),異步IO有的時(shí)候,也叫做信號(hào)驅(qū)動(dòng) IO 。

異步IO模型缺點(diǎn):

需要完成事件的注冊(cè)與傳遞,這里邊需要底層操作系統(tǒng)提供大量的支持,去做大量的工作。

目前來(lái)說(shuō), Windows 系統(tǒng)下通過(guò) IOCP 實(shí)現(xiàn)了真正的異步 I/O。但是,就目前的業(yè)界形式來(lái)說(shuō),Windows 系統(tǒng),很少作為百萬(wàn)級(jí)以上或者說(shuō)高并發(fā)應(yīng)用的服務(wù)器操作系統(tǒng)來(lái)使用。

而在 Linux 系統(tǒng)下,異步IO模型在2.6版本才引入,目前并不完善。所以,這也是在 Linux 下,實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)編程時(shí)都是以 IO 復(fù)用模型模式為主

目前來(lái)說(shuō) AIO 的應(yīng)用還不是很廣泛。Netty 之前也嘗試使用過(guò) AIO,不過(guò)又放棄了。這是因?yàn)?,Netty 使用了 AIO 之后,在 Linux 系統(tǒng)上的性能并沒(méi)有多少提升。

Java 中的 BIO、NIO、AIO對(duì)比。

select,poll,epoll 對(duì)比

select,poll,epoll都是IO多路復(fù)用的機(jī)制。I/O多路復(fù)用就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作。但select,poll,epoll本質(zhì)上都是同步I/O,因?yàn)樗麄兌夹枰谧x寫(xiě)事件就緒后自己負(fù)責(zé)進(jìn)行讀寫(xiě),也就是說(shuō)這個(gè)讀寫(xiě)過(guò)程是阻塞的,而異步I/O則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫(xiě),異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶(hù)空間。

select

#include

#include

int select(int maxfd_add_1, fd_set *readset, fd_set *write_set, fd_set * exceptset,

const struct timeval *timeout);

struct timeval{

long tv_sec;

long tv_usec;

};

/*設(shè)置fd_set,下面的四個(gè)函數(shù)都是宏,不能取地址*/

//清空

void FD_ZERO(fd_set *fdset);

//設(shè)置一個(gè)位

void FD_SET(int fd, fd_set *fdset);

//清除一個(gè)位

void FD_CLR(int fd, fd_set *fdset);

/*檢查某個(gè)位是否被設(shè)置,可用于函數(shù)返回時(shí)判斷那個(gè)文件描述符就緒*/

int FD_ISSET(int fd,fd_set *fdset);

maxfd_add_1后面三個(gè)集中設(shè)置了的文件描述符的最大值+1,因?yàn)檫@個(gè)值表示的是個(gè)數(shù)。這個(gè)參數(shù)存在的意義就是內(nèi)核在每次喚醒的時(shí)候需要遍歷的文件描述符個(gè)數(shù),因而不用全部遍歷所有1024個(gè)文件描述符的狀態(tài)

存在這個(gè)參數(shù)的原因純粹是為了效率。因?yàn)閒d_set的最大值典型的是1024.為了避免所有的都被檢查,因此使用這個(gè)。

最大值為宏FD_SETSIZE

readset、write_set、exceptset這三個(gè)fd_set類(lèi)型是一個(gè)位圖。返回的時(shí)候,如果對(duì)應(yīng)的位被設(shè)置,那么表示文件描述符就緒,因此需要用戶(hù)再次手動(dòng)遍歷依次。

select的缺點(diǎn): select支持的文件描述符數(shù)量太小了,默認(rèn)是1024

每次調(diào)用select都需要把fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)會(huì)很大

每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,查看有沒(méi)有就緒的fd,這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)也很大,效率隨FD數(shù)目增加而線(xiàn)性下降

poll

#include

int poll(struct pollfd *fdarray, unsigned long n, int timeout);

//成功個(gè)數(shù),錯(cuò)誤-1,超時(shí)0

struct pollfd{

int fd;

short events;

short revents;

};

fdarray是一個(gè)pollfd類(lèi)型的數(shù)組(首指針),n表示這個(gè)數(shù)組的個(gè)數(shù)。pollfd的events成員是要測(cè)試的條件,而revents是內(nèi)核要填充的,表示文件描述符當(dāng)前的讀寫(xiě)狀態(tài)。

poll方法不再使用位圖的方式傳入文件描述符符,而是采用一個(gè)結(jié)構(gòu)體的方式,因而在遍歷每個(gè)文件描述符的poll方法的時(shí)候,就從結(jié)構(gòu)體的數(shù)組中依次遍歷,因此突破了select文件描述符的限制。

poll的缺點(diǎn): 每次調(diào)用select都需要把fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)會(huì)很大

每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,查看有沒(méi)有就緒的fd,這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)也很大,效率隨FD數(shù)目增加而線(xiàn)性下降

epoll

源碼

include

// 創(chuàng)建 epollfd 的函數(shù)

int epoll_create(int size);

int epoll_create1(int flags);

// 對(duì)要監(jiān)聽(tīng)的文件描述符的的增加修改刪除

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 開(kāi)始阻塞

int epoll_wait(int epfd, struct epoll_event *events, int maxevents,

int timeout);

int epoll_pwait(int epfd, struct epoll_event *events, int maxevents,

int timeout, const sigset_t *sigmask);

// epoll_wait 返回的時(shí)候,events表示發(fā)生的、

//時(shí)間 data則表示與文件描述符相關(guān)的信息,可以指向一個(gè)結(jié)構(gòu)體,

//也可以是直接的文件描述符

typedef union epoll_data {

void *ptr; // 常用這個(gè)

int fd;

uint32_t u32;

uint64_t u64;

} epoll_data_t;

struct epoll_event

{

//感興趣的事件如EPOLLIN,EPOLLOUT,EPOLLPRI等

uint32_t events;

//一般使用data的fd成員表示感興趣的socket

epoll_data_t data;

};

epoll_ctl() 這個(gè)函數(shù)是修改epoll關(guān)注描述符的。

epfd是epoll_create創(chuàng)建的文件描述符op表示要修改的方式:

POLL_CTL_ADD:添加EPOLL_CTL_MOD:修改EPOLL_CTL_DEL:刪除 fd參數(shù)是要修改關(guān)注的文件描述符event則是要關(guān)注的事件了。

epoll_wait() 就是開(kāi)始阻塞的函數(shù)了。

events是已經(jīng)分配好的epoll_event結(jié)構(gòu)體。這個(gè)是一個(gè)空的,內(nèi)核負(fù)責(zé)幫我們填充.events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè)events數(shù)組中,不會(huì)去幫助我們?cè)谟脩?hù)態(tài)中分配內(nèi)存 maxevents是傳入要填充的數(shù)組的大小。

為什么高效

每次調(diào)用不需要傳入所有的文件描述符

select/poll每次調(diào)用都要傳遞所要監(jiān)控的所有fd給select/poll系統(tǒng)調(diào)用(這意味著每次調(diào)用都要將fd列表從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),當(dāng)fd數(shù)目很多時(shí),這會(huì)造成低效)。而每次調(diào)用epoll_wait時(shí)(作用相當(dāng)于調(diào)用select/poll),不需要再傳遞fd列表給內(nèi)核,因?yàn)橐呀?jīng)在epoll_ctl中將需要監(jiān)控的fd告訴了內(nèi)核(epoll_ctl不需要每次都拷貝所有的fd,只需要進(jìn)行增量式操作)。所以,在調(diào)用epoll_create之后,內(nèi)核已經(jīng)在內(nèi)核態(tài)開(kāi)始準(zhǔn)備數(shù)據(jù)結(jié)構(gòu)存放要監(jiān)控的fd了。每次epoll_ctl只是對(duì)這個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行簡(jiǎn)單的維護(hù)。

每次epoll_wait()被喚醒,不需要去遍歷所有文件描述符 epoll沒(méi)有文件描述符數(shù)量上的限制。

epoll兩種工作方式LT和ET

水平觸發(fā)(LT)是epoll**缺省的工作方式,**支持阻塞和非阻塞文件描述符。當(dāng)數(shù)據(jù)可讀寫(xiě)的時(shí)候就喚醒epoll_wait,如果不對(duì)這個(gè)文件描述符作任何操作,內(nèi)核還是會(huì)繼續(xù)通知,所以,這種模式編程出錯(cuò)誤可能性要小一點(diǎn)。傳統(tǒng)的select/poll都使用這種模型。

邊緣觸發(fā)(ET)是高速工作方式,只支持非阻塞模式。當(dāng)一個(gè)新的事件到來(lái)時(shí),ET模式下當(dāng)然可以從epoll_wait調(diào)用中獲取到這個(gè)事件,可是如果這次沒(méi)有把這個(gè)事件對(duì)應(yīng)的套接字緩沖區(qū)處理完,在這個(gè)套接字中沒(méi)有新的事件再次到來(lái)時(shí),在ET模式下是無(wú)法再次從epoll_wait調(diào)用中獲取這個(gè)事件的。

LT模式下開(kāi)發(fā)基于epoll的應(yīng)用要簡(jiǎn)單些,不太容易出錯(cuò)。而在ET模式下事件發(fā)生時(shí),如果沒(méi)有徹底地將緩沖區(qū)數(shù)據(jù)處理完,則會(huì)導(dǎo)致緩沖區(qū)中的用戶(hù)請(qǐng)求得不到響應(yīng)。

epoll的跟select和poll對(duì)比的優(yōu)點(diǎn):

支持一個(gè)進(jìn)程打開(kāi)大數(shù)目的socket描述符。它所支持的FD上限是最大可以打開(kāi)文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)遠(yuǎn)于2048,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來(lái)說(shuō)這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。

select和poll每次調(diào)用都需要把所有要監(jiān)聽(tīng)的fd重新拷貝到內(nèi)核空間;epoll只在調(diào)用epoll_ctl時(shí)拷貝一次要監(jiān)聽(tīng)的fd,調(diào)用epoll_wait時(shí)不需要每次把所有要監(jiān)聽(tīng)的fd重復(fù)拷貝到內(nèi)核空間。

IO效率不隨FD數(shù)目增加而線(xiàn)性下降。傳統(tǒng)的select/poll另一個(gè)致命弱點(diǎn)就是當(dāng)你擁有一個(gè)很大的socket集合,任一時(shí)間只有部分的socket是”活躍”的,但是select/poll每次調(diào)用都會(huì)線(xiàn)性?huà)呙枞康募?,?dǎo)致效率呈現(xiàn)線(xiàn)性下降。但是epoll不存在這個(gè)問(wèn)題,它只會(huì)對(duì)”活躍”的socket進(jìn)行操作。這是因?yàn)樵趦?nèi)核實(shí)現(xiàn)中epoll是根據(jù)每個(gè)fd上面的callback函數(shù)實(shí)現(xiàn)的。那么,只有”活躍”的socket才會(huì)主動(dòng)的去調(diào)用callback函數(shù),其他idle狀態(tài)socket則不會(huì)。

select,poll,epoll區(qū)別

零拷貝

零拷貝基本概念:

零拷貝是指計(jì)算機(jī)執(zhí)行IO操作時(shí),CPU不需要將數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域復(fù)制到另一個(gè)存儲(chǔ)區(qū)域,進(jìn)而減少上下文切換以及CPU的拷貝時(shí)間。它是一種IO操作優(yōu)化技術(shù)。

nio中epoll零拷貝原理解析:

mmap 通過(guò)內(nèi)存映射,將文件映射到 內(nèi)核緩沖區(qū),同時(shí),用戶(hù)空間可以共享內(nèi)核空間的數(shù)據(jù)。這樣,在進(jìn)行網(wǎng)絡(luò)傳輸時(shí),就可以減少內(nèi)核空間到用戶(hù)控件的拷貝次數(shù)。

零拷貝代碼演示:

/**

* 零拷貝代碼演示

*/

private static void zeroCopyDemo() throws IOException {

// 構(gòu)建零拷貝的輸入輸出

FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\yojofly\\Desktop\\sps-plugins\\learn\\test.csv"), StandardOpenOption.READ);

FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\yojofly\\Desktop\\sps-plugins\\learn\\test_cp.csv"),StandardOpenOption.READ,StandardOpenOption.CREATE,StandardOpenOption.WRITE);

// 緩沖區(qū)初始化,可對(duì)比為文件Io讀取時(shí)候的 new byte[1024]

MappedByteBuffer inMapperBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());

MappedByteBuffer outMapperBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

// 獲取輸入文件的字節(jié)長(zhǎng)度limit

byte[] bytes = new byte[inMapperBuffer.limit()];

// 文件讀入緩存中

inMapperBuffer.get(bytes);

// 從緩存中取出文件內(nèi)容寫(xiě)入到新的文件

outMapperBuffer.put(bytes);

inChannel.close();

outChannel.close();

}

nio中channel,buffer,selector

Channel

Channel是數(shù)據(jù)傳輸?shù)碾p向通道,可以從 channel 將數(shù)據(jù)讀入 buffer,也可以將 buffer 的數(shù)據(jù)寫(xiě)入 channel。 常見(jiàn)的Channel有:

FileChannel 文件傳輸 DatagramChannel(通常用于UDP) SocketChannel(通常用于TCP) ServerSocketChannel(通常用于TCP)

Buffer

內(nèi)存緩存區(qū),用于暫存從Channel中讀取的數(shù)據(jù)、以及即將寫(xiě)入Channel中的數(shù)據(jù)。

常見(jiàn)的實(shí)現(xiàn)類(lèi)Buffer:

ByteBuffer 字節(jié)

MappedByteBuffer DirectByteBuffer HeapByteBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer CharBuffer 從名字上可以看出,這些Buffer支持的是不同數(shù)據(jù)類(lèi)型的緩沖區(qū),如short、int等。

常用方法:

put: 寫(xiě)入數(shù)據(jù)

get:讀取數(shù)據(jù)

flip:將Buffer從寫(xiě)模式切換到讀模式。調(diào)用flip()方法會(huì)將position設(shè)回0,并將limit設(shè)置成之前position的值

clear:方法,position將被設(shè)回0,limit被設(shè)置成 capacity的值

compact:方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面

mark :添加標(biāo)記 reset:恢復(fù)到標(biāo)記位置

通過(guò)調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過(guò)調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position。

內(nèi)部結(jié)構(gòu)

ByteBuffer的內(nèi)部結(jié)構(gòu)類(lèi)似于一個(gè)數(shù)組,主要有3個(gè)重要屬性:

capacity:代表buffer的容量。

position:一個(gè)讀寫(xiě)指針,可以理解為讀寫(xiě)到buffer中的哪個(gè)下標(biāo)。

limit:讀寫(xiě)限制,position不能超過(guò)limit。

當(dāng)我們新建一個(gè)buffer時(shí),它的內(nèi)部結(jié)構(gòu)如下:

寫(xiě)模式下,position 是寫(xiě)入位置,limit 等于容量,下圖表示寫(xiě)入了 4 個(gè)字節(jié)后的狀態(tài):

flip 動(dòng)作發(fā)生后,position 切換為讀取位置,limit 切換為讀取限制:

讀取 4 個(gè)字節(jié)后,狀態(tài)變成:

clear 動(dòng)作發(fā)生后,狀態(tài)變成:

compact 方法,是把未讀完的部分向前壓縮,然后切換至寫(xiě)模式

Selector

在NIO出現(xiàn)之前,在開(kāi)發(fā)服務(wù)器端程序時(shí),會(huì)采用一種叫做“多線(xiàn)程版設(shè)計(jì)”的思路,即服務(wù)器端每當(dāng)需要和一個(gè)新的客戶(hù)端進(jìn)行通信時(shí),就會(huì)開(kāi)啟一個(gè)新的Socket連接(通過(guò)Socket就能讀寫(xiě)數(shù)據(jù)了),并啟動(dòng)一個(gè)新的線(xiàn)程來(lái)為這個(gè)Socket服務(wù)(執(zhí)行讀寫(xiě)操作等)。

這種思路的弊端在于,如果客戶(hù)端太多的話(huà),那么服務(wù)器端就需要開(kāi)啟許多線(xiàn)程才能進(jìn)行服務(wù),這會(huì)帶來(lái)以下問(wèn)題:

內(nèi)存占用高。 線(xiàn)程上下文切換成本高。 只適合連接數(shù)少的場(chǎng)景。 注意:線(xiàn)程并不是越多越好,因?yàn)镃PU能并行處理的線(xiàn)程是有限的,一旦線(xiàn)程數(shù)量超出CPU能并行處理的數(shù)量,多出來(lái)的線(xiàn)程就需要等待CPU,而這就會(huì)導(dǎo)致線(xiàn)程上下文切換,從而帶來(lái)額外開(kāi)銷(xiāo)

SelectionKey的四個(gè)常量

SelectionKey.OP_CONNECTSelectionKey.OP_ACCEPTSelectionKey.OP_READSelectionKey.OP_WRITE

分別表示監(jiān)聽(tīng)四種不同類(lèi)型的事件:

ConnectAcceptReadWrite

selector常用方法:

open: 創(chuàng)建一個(gè)selector連接

select():阻塞到至少有一個(gè)通道在你注冊(cè)的事件上就緒了。

select(long timeout):select()一樣,除了最長(zhǎng)會(huì)阻塞timeout毫秒(參數(shù))。

selectNow():不會(huì)阻塞,不管什么通道就緒都立刻返回(此方法執(zhí)行非阻塞的選擇操作。如果自從前一次選擇操作后,沒(méi)有通道變成可選擇的,則此方法直接返回零)。

selectedKeys():返回所有selector里注冊(cè)的SelectionKey

代碼示例:模擬Nio中網(wǎng)絡(luò)中的客戶(hù)端和服務(wù)端通信

SelectorClientDemo.java

package com.yojofly.server.learn.nio.selector;

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.SocketChannel;

import java.util.Iterator;

import java.util.Set;

public class SelectorClientDemo {

static Selector selector;

public static void main(String[] args) throws IOException {

selector = Selector.open();

SocketChannel socketChannel = SocketChannel.open();

// 設(shè)置為非阻塞io

socketChannel.configureBlocking(false);

socketChannel.connect(new InetSocketAddress("localhost",8000));

socketChannel.register(selector, SelectionKey.OP_CONNECT);

while (true){

selector.select(); //阻塞

Set selectionKeys = selector.selectedKeys();

Iterator iterator = selectionKeys.iterator();

while (iterator.hasNext()){

SelectionKey selectionKey = iterator.next();

iterator.remove();

if (selectionKey.isConnectable()){ // 連接事件

// 處理連接事件

handleConnect(selectionKey);

}else if (selectionKey.isReadable()){ // 讀事件

// 處理讀事件

handleRead(selectionKey);

}

}

}

}

private static void handleConnect(SelectionKey selectionKey) throws IOException{

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

// 連接中斷

if (socketChannel.isConnectionPending()){

socketChannel.finishConnect();

}

// 設(shè)置io非阻塞狀態(tài)

socketChannel.configureBlocking(false);

socketChannel.write(ByteBuffer.wrap("hello server,i am nio client with accept".getBytes()));

socketChannel.register(selector,SelectionKey.OP_READ);

}

private static void handleRead(SelectionKey selectionKey) throws IOException{

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

socketChannel.read(byteBuffer);

System.out.println("client receive Msg:" +new String(byteBuffer.array()));

}

}

SelectorServerDemo.java

package com.yojofly.server.learn.nio.selector;

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 SelectorServerDemo {

static Selector selector;

public static void main(String[] args) throws IOException {

selector = Selector.open();

ServerSocketChannel socketChannel = ServerSocketChannel.open();

// 設(shè)置為非阻塞io

socketChannel.configureBlocking(false);

socketChannel.socket().bind(new InetSocketAddress(8000));

socketChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true){

selector.select(); //阻塞

Set selectionKeys = selector.selectedKeys();

Iterator iterator = selectionKeys.iterator();

while (iterator.hasNext()){

SelectionKey selectionKey = iterator.next();

iterator.remove();

if (selectionKey.isAcceptable()){ // 連接事件

// 處理連接事件

handleConnect(selectionKey);

}else if (selectionKey.isReadable()){ // 讀事件

// 處理讀事件

handleRead(selectionKey);

}

}

}

}

private static void handleConnect(SelectionKey selectionKey) throws IOException{

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

socketChannel.write(ByteBuffer.wrap("hello client,i am nio server with connect".getBytes()));

socketChannel.register(selector,SelectionKey.OP_READ);

}

private static void handleRead(SelectionKey selectionKey) throws IOException{

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

socketChannel.read(byteBuffer);

System.out.println("server receive Msg:" +new String(byteBuffer.array()));

}

}

文章參考:

同步異步,阻塞非阻塞 https://blog.csdn.net/qq_39515350/article/details/120854214 io多路復(fù)用中詳解: https://blog.csdn.net/adminpd/article/details/124553590 io基礎(chǔ)知識(shí)原理分析: https://www.cnblogs.com/crazymakercircle/p/10225159.html select,poll.epoll對(duì)比 https://blog.csdn.net/xx_yTm/article/details/54801977 select,poll,epoll源碼分析 http://t.zoukankan.com/perfy576-p-8554734.html nio中channel,buffer,selector的作用: https://blog.csdn.net/lucas161543228/article/details/125172566

柚子快報(bào)邀請(qǐng)碼778899分享:網(wǎng)絡(luò) io,nio,aio總結(jié)

http://yzkb.51969.com/

精彩內(nèi)容

評(píng)論可見(jiàn),查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19199475.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問(wèn)

文章目錄