網(wǎng)上看了好多IO,NIO的文字越庇,參差不齊顺呕,每篇總是差一兩個(gè)點(diǎn)沒(méi)有講到,所以這里對(duì)于我自己理解的做一個(gè)總結(jié),也許有不對(duì)的地方庸毫。
1供璧,基本概念
1.1)同步/異步,阻塞/非阻塞
同步異步主要針對(duì)C端:
所謂同步,就是在c端發(fā)出一個(gè)功能調(diào)用時(shí)弯屈,在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回恋拷。也就是必須一件一件事做,等前一件做完了才能做下一件事资厉。
異步的概念和同步相對(duì)。當(dāng)c端一個(gè)異步過(guò)程調(diào)用發(fā)出后蔬顾,調(diào)用者不能立刻得到結(jié)果宴偿。實(shí)際處理這個(gè)調(diào)用的部件在完成后,通過(guò)狀態(tài)诀豁、通知和回調(diào)來(lái)通知調(diào)用者窄刘。
阻塞非阻塞主要針對(duì)S端:
阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起(線程進(jìn)入非可執(zhí)行狀態(tài)舷胜,在這個(gè)狀態(tài)下娩践,cpu不會(huì)給線程分配時(shí)間片,即線程暫停運(yùn)行)逞带。函數(shù)只有在得到結(jié)果之后才會(huì)返回欺矫。
非阻塞和阻塞的概念相對(duì)應(yīng),指在不能立刻得到結(jié)果之前展氓,該函數(shù)不會(huì)阻塞當(dāng)前線程穆趴,而會(huì)立刻返回。
1.2)面向流遇汞,面向緩沖區(qū)
面向流:每次從流中讀一個(gè)或多個(gè)字節(jié)未妹,直至讀取所有字節(jié),它們沒(méi)有被緩存在任何地方空入。
面向緩沖區(qū):數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū)络它,需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過(guò)程中的靈活性歪赢。
1.4)內(nèi)核態(tài)和用戶態(tài)
內(nèi)核空間可以訪問(wèn)受保護(hù)的內(nèi)存(32位下高位1G為內(nèi)核空間)化戳,剩下3G為用戶空間
操作系統(tǒng)限制用戶態(tài)不能直接訪問(wèn)硬件設(shè)備,所以數(shù)據(jù)從硬件移動(dòng)到用戶進(jìn)程的內(nèi)存時(shí)需要2步操作埋凯。
1.3)mmap(內(nèi)存映射)
將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間点楼,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系
2,linux下的5種IO模型
5種IO模式關(guān)于同步異步白对,阻塞非阻塞的關(guān)系
阻塞 | 非阻塞 | |
---|---|---|
同步 | BIO | nonblockingIO/多路復(fù)用IO(NIO1.0) |
異步 | X | AIO |
1掠廓,BIO:
阻塞當(dāng)前線程,等待數(shù)據(jù)準(zhǔn)備
2甩恼,nonblockingIO:
相當(dāng)于輪訓(xùn)蟀瞧,將大片的等待時(shí)間切割成小片沉颂,
3,多路復(fù)用IO(IO mulitplexing)NIO
監(jiān)聽多個(gè)socket悦污,當(dāng)任何一個(gè)數(shù)據(jù)準(zhǔn)備好后就返回铸屉,用戶進(jìn)程再調(diào)用read函數(shù)讀取數(shù)據(jù),極限情況塞关,如果只監(jiān)聽一個(gè)socket那么和BIO是一樣的抬探,只有監(jiān)聽的socket多時(shí),才會(huì)凸顯效率帆赢。
重點(diǎn),后面詳細(xì)說(shuō)明
4线梗,信號(hào)驅(qū)動(dòng) I/O
5椰于, asynchronous IO
需要操作系統(tǒng)內(nèi)核支持
aio目前在linux下存在BUG,使用場(chǎng)景少
3仪搔,為什么說(shuō)JAVA的標(biāo)準(zhǔn)IO是面向流瘾婿,NIO面向緩沖區(qū)
標(biāo)準(zhǔn)IO:
SocketInputStream.read
int read(byte b[], int off, int length, int timeout) throws IOException {
int n;
// EOF already encountered
if (eof) {
return -1;
}
// connection reset
if (impl.isConnectionReset()) {
throw new SocketException("Connection reset");
}
// bounds check
if (length <= 0 || off < 0 || off + length > b.length) {
if (length == 0) {
return 0;
}
throw new ArrayIndexOutOfBoundsException();
}
boolean gotReset = false;
// acquire file descriptor and do the read
FileDescriptor fd = impl.acquireFD();
try {
n = socketRead(fd, b, off, length, timeout);//native函數(shù)
if (n > 0) {
return n;
}
} catch (ConnectionResetException rstExc) {
gotReset = true;
} finally {
impl.releaseFD();
}
/*
* We receive a "connection reset" but there may be bytes still
* buffered on the socket
*/
if (gotReset) {
impl.setConnectionResetPending();
impl.acquireFD();
try {
n = socketRead(fd, b, off, length, timeout);//native函數(shù),這里從內(nèi)核態(tài)中讀取數(shù)據(jù)到數(shù)組b中
if (n > 0) {
return n;
}
} catch (ConnectionResetException rstExc) {
} finally {
impl.releaseFD();
}
}
/*
* If we get here we are at EOF, the socket has been closed,
* or the connection has been reset.
*/
if (impl.isClosedOrPending()) {
throw new SocketException("Socket closed");
}
if (impl.isConnectionResetPending()) {
impl.setConnectionReset();
}
if (impl.isConnectionReset()) {
throw new SocketException("Connection reset");
}
eof = true;
return -1;
}
面向緩沖區(qū)(NIO):
DatagramChannelImpl.receive
private int receive(FileDescriptor fd, ByteBuffer dst)
throws IOException
{
int pos = dst.position();
int lim = dst.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (dst instanceof DirectBuffer && rem > 0)
return receiveIntoNativeBuffer(fd, dst, rem, pos);
// Substitute a native buffer. If the supplied buffer is empty
// we must instead use a nonempty buffer, otherwise the call
// will not block waiting for a datagram on some platforms.
int newSize = Math.max(rem, 1);
ByteBuffer bb = Util.getTemporaryDirectBuffer(newSize);//申請(qǐng)一塊newSize大小的緩沖區(qū)塊
try {
int n = receiveIntoNativeBuffer(fd, bb, newSize, 0);//數(shù)據(jù)讀取到緩沖區(qū)中烤咧,buffer可以做標(biāo)記偏陪,操作指針等
bb.flip();
if (n > 0 && rem > 0)
dst.put(bb);
return n;
} finally {
Util.releaseTemporaryDirectBuffer(bb);
}
}
channel buffer 說(shuō)明:
http://www.reibang.com/p/052035037297
4,select,poll,epoll詳解
select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
監(jiān)視所有的readFD,writeFD,exceptFD
select的一 個(gè)缺點(diǎn)在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制
poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
pollfd并沒(méi)有最大數(shù)量限制
select和poll沒(méi)有太大區(qū)別煮嫌,都是輪訓(xùn)所有的fd/pollfd來(lái)獲取準(zhǔn)備好的fd笛谦,當(dāng)有大量連接的客戶端時(shí),效率會(huì)線性下降
epoll
1)int epfd = epoll_create(intsize); 創(chuàng)建一個(gè)ep句柄(/proc/進(jìn)程id/fd/),用于監(jiān)聽所有注冊(cè)的套接字昌阿,存在一個(gè)紅黑樹的數(shù)據(jù)結(jié)構(gòu)中饥脑,這棵紅黑樹的存儲(chǔ)通過(guò)mmap將內(nèi)核態(tài)和用戶態(tài)共享,減少用戶態(tài)和內(nèi)核態(tài)之間的數(shù)據(jù)交換懦冰,而select/poll每次輪訓(xùn)時(shí)都要將相關(guān)的句柄從內(nèi)核態(tài)拷貝至用戶態(tài)灶轰。
2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 添加/修改/刪除注冊(cè)的套接字,由于是在紅黑樹中刷钢,效率較高笋颤,當(dāng)事件添加時(shí),該事件會(huì)與相應(yīng)的設(shè)備(網(wǎng)卡)驅(qū)動(dòng)程序建立回調(diào)連接内地,一旦事件發(fā)生(文件fd改變)相應(yīng)fd會(huì)回調(diào)這個(gè)函數(shù)伴澄,將事件添加到一個(gè)rdllist(雙向鏈表)中
事件類型:
EPOLLIN :表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉); EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫瓤鼻; EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來(lái))秉版; EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤; EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷茬祷;
3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
3.1)調(diào)用ep_poll清焕,當(dāng)rdllist為空時(shí)掛起,一直到rdllist不為空時(shí)喚醒
3.2)ep_events_transfer函數(shù)將rdlist中的epitem拷貝到txlist中,并將rdlist清空秸妥。ep_send_events函數(shù)(很關(guān)鍵)滚停,它掃描txlist中的每個(gè)epitem,調(diào)用其關(guān)聯(lián)fd對(duì)用的poll方法粥惧。此時(shí)對(duì)poll的調(diào)用僅僅是取得fd上較新的events(防止之前events被更新)键畴,之后將取得的events和相應(yīng)的fd發(fā)送到用戶空間(封裝在struct epoll_event,從epoll_wait返回)突雪。
3起惕,asynchronous IO
IO詳解:https://segmentfault.com/a/1190000003063859#articleHeader14
select、poll咏删、epoll之間的區(qū)別總結(jié)[整理]:http://www.cnblogs.com/Anker/p/3265058.html
http://www.cnblogs.com/lojunren/p/3856290.html
http://www.smithfox.com/?e=191
這篇對(duì)IO流操作寫的比較好 http://www.cnblogs.com/hapjin/p/5736188.html
補(bǔ)充:
創(chuàng)建Selectorprovider
當(dāng)linux內(nèi)核>2.6時(shí)使用epoll
public static SelectorProvider create() {
String osname = AccessController.doPrivileged(
new GetPropertyAction("os.name"));
if ("SunOS".equals(osname)) {
return new sun.nio.ch.DevPollSelectorProvider();
}
// use EPollSelectorProvider for Linux kernels >= 2.6
if ("Linux".equals(osname)) {
String osversion = AccessController.doPrivileged(
new GetPropertyAction("os.version"));
String[] vers = osversion.split("\\.", 0);
if (vers.length >= 2) {
try {
int major = Integer.parseInt(vers[0]);
int minor = Integer.parseInt(vers[1]);
if (major > 2 || (major == 2 && minor >= 6)) {
return new sun.nio.ch.EPollSelectorProvider();
}
} catch (NumberFormatException x) {
// format not recognized
}
}
}
return new sun.nio.ch.PollSelectorProvider();
}
select poll模式
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
通過(guò)程序去控制傳入的監(jiān)控fd
代碼層面監(jiān)視多個(gè)描述符惹想,描述文件越多越慢
nio selector: