日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
JavaNIO如何處理慢速的連接

對企業(yè)級的服務器軟件,高性能和可擴展性是基本的要求。除此之外,還應該有應對各種不同環(huán)境的能力。例如,一個好的服務器軟件不應該假設(shè)所有的客戶端都有很快的處理能力和很好的網(wǎng)絡環(huán)境。如果一個客戶端的運行速度很慢,或者網(wǎng)絡速度很慢,這就意味著整個請求的時間變長。而對于服務器來說,這就意味著這個客戶端的請求將占用更長的時間。這個時間的延遲不是由服務器造成的,因此CPU的占用不會增加什么,但是網(wǎng)絡連接的時間會增加,處理線程的占用時間也會增加。這就造成了當前處理線程和其他資源得不到很快的釋放,無法被其他客戶端的請求來重用。例如Tomcat,當存在大量慢速連接的客戶端時,線程資源被這些慢速的連接消耗掉,使得服務器不能響應其他的請求了。

前面介紹過,NIO的異步非阻塞的形式,使得很少的線程就能服務于大量的請求。通過Selector的注冊功能,可以有選擇性地返回已經(jīng)準備好的頻道,這樣就不需要為每一個請求分配單獨的線程來服務。

在一些流行的NIO的框架中,都能看到對OP_ACCEPT和OP_READ的處理。很少有對OP_WRITE的處理。我們經(jīng)常看到的代碼就是在請求處理完成后,直接通過下面的代碼將結(jié)果返回給客戶端:

不對OP_WRITE進行處理的樣例:

 
 
 
  1. while (bb.hasRemaining()) {  
  2.     int len = socketChannel.write(bb);  
  3.     if (len < 0) {  
  4.         throw new EOFException();  
  5.     }  

這樣寫在大多數(shù)的情況下都沒有什么問題。但是在客戶端的網(wǎng)絡環(huán)境很糟糕的情況下,服務器會遭到很沉重的打擊。

因為如果客戶端的網(wǎng)絡或者是中間交換機的問題,使得網(wǎng)絡傳輸?shù)男屎艿?,這時候會出現(xiàn)服務器已經(jīng)準備好的返回結(jié)果無法通過TCP/IP層傳輸?shù)娇蛻舳恕_@時候在執(zhí)行上面這段程序的時候就會出現(xiàn)以下情況。

(1) bb.hasRemaining()一直為“true”,因為服務器的返回結(jié)果已經(jīng)準備好了。

(2) socketChannel.write(bb)的結(jié)果一直為0,因為由于網(wǎng)絡原因數(shù)據(jù)一直傳不過去。

(3) 因為是異步非阻塞的方式,socketChannel.write(bb)不會被阻塞,立刻被返回。

(4) 在一段時間內(nèi),這段代碼會被無休止地快速執(zhí)行著,消耗著大量的CPU的資源。事實上什么具體的任務也沒有做,一直到網(wǎng)絡允許當前的數(shù)據(jù)傳送出去為止。

這樣的結(jié)果顯然不是我們想要的。因此,我們對OP_WRITE也應該加以處理。在NIO中最常用的方法如下。

一般NIO框架中對OP_WRITE的處理:

 
 
 
  1. while (bb.hasRemaining()) {  
  2.     int len = socketChannel.write(bb);  
  3.     if (len < 0){  
  4.         throw new EOFException();  
  5.     }  
  6.     if (len == 0) {  
  7.         selectionKey.interestOps(  
  8.                         selectionKey.interestOps() | SelectionKey.OP_WRITE);  
  9.         mainSelector.wakeup();  
  10.         break;  
  11.     }  

上面的程序在網(wǎng)絡不好的時候,將此頻道的OP_WRITE操作注冊到Selector上,這樣,當網(wǎng)絡恢復,頻道可以繼續(xù)將結(jié)果數(shù)據(jù)返回客戶端的時候,Selector會通過SelectionKey來通知應用程序,再去執(zhí)行寫的操作。這樣就能節(jié)約大量的CPU資源,使得服務器能適應各種惡劣的網(wǎng)絡環(huán)境。

可是,Grizzly中對OP_WRITE的處理并不是這樣的。我們先看看Grizzly的源碼吧。在Grizzly中,對請求結(jié)果的返回是在ProcessTask中處理的,經(jīng)過SocketChannelOutputBuffer的類,最終通過OutputWriter類來完成返回結(jié)果的動作。在OutputWriter中處理OP_WRITE的代碼如下:

Grizzly中對OP_WRITE的處理:

 
 
 
  1. public static long flushChannel(SocketChannel socketChannel,  
  2.         ByteBuffer bb, long writeTimeout) throws IOException  
  3. {  
  4.     SelectionKey key = null;  
  5.     Selector writeSelector = null;  
  6.     int attempts = 0;  
  7.     int bytesProduced = 0;  
  8.     try {  
  9.         while (bb.hasRemaining()) {  
  10.             int len = socketChannel.write(bb);  
  11.             attempts++;  
  12.             if (len < 0){  
  13.                 throw new EOFException();  
  14.             }  
  15.             bytesProduced += len;  
  16.             if (len == 0) {  
  17.                 if (writeSelector == null){  
  18.                     writeSelector = SelectorFactory.getSelector();  
  19.                     if (writeSelector == null){  
  20.                         // Continue using the main one  
  21.                         continue;  
  22.                     }  
  23.                 }  
  24.                 key = socketChannel.register(writeSelector, key.OP_WRITE);  
  25.                 if (writeSelector.select(writeTimeout) == 0) {  
  26.                     if (attempts > 2)  
  27.                         throw new IOException("Client disconnected");  
  28.                 } else {  
  29.                     attempts--;  
  30.                 }  
  31.             } else {  
  32.                 attempts = 0;  
  33.             }  
  34.         }  
  35.     } finally {  
  36.         if (key != null) {  
  37.             key.cancel();  
  38.             key = null;  
  39.         }  
  40.         if (writeSelector != null) {  
  41.             // Cancel the key.  
  42.             writeSelector.selectNow();  
  43.             SelectorFactory.returnSelector(writeSelector);  
  44.         }  
  45.     }  
  46.     return bytesProduced;  

上面的程序例17.9與例17.8的區(qū)別之處在于:當發(fā)現(xiàn)由于網(wǎng)絡情況而導致的發(fā)送數(shù)據(jù)受阻(len==0)時,例17.8的處理是將當前的頻道注冊到當前的Selector中;而在例17.9中,程序從SelectorFactory中獲得了一個臨時的Selector。在獲得這個臨時的Selector之后,程序做了一個阻塞的操作:writeSelector.select(writeTimeout)。這個阻塞操作會在一定時間內(nèi)(writeTimeout)等待這個頻道的發(fā)送狀態(tài)。如果等待時間過長,便認為當前的客戶端的連接異常中斷了。

這種實現(xiàn)方式頗受爭議。有很多開發(fā)者置疑Grizzly的作者為什么不使用例17.8的模式。另外在實際處理中,Grizzly的處理方式事實上放棄了NIO中的非阻塞的優(yōu)勢,使用writeSelector.select(writeTimeout)做了個阻塞操作。雖然CPU的資源沒有浪費,可是線程資源在阻塞的時間內(nèi),被這個請求所占有,不能釋放給其他請求來使用。

Grizzly的作者對此的回應如下。

(1) 使用臨時的Selector的目的是減少線程間的切換。當前的Selector一般用來處理OP_ACCEPT,和OP_READ的操作。使用臨時的Selector可減輕主Selector的負擔;而在注冊的時候則需要進行線程切換,會引起不必要的系統(tǒng)調(diào)用。這種方式避免了線程之間的頻繁切換,有利于系統(tǒng)的性能提高。

(2) 雖然writeSelector.select(writeTimeout)做了阻塞操作,但是這種情況只是少數(shù)極端的環(huán)境下才會發(fā)生。大多數(shù)的客戶端是不會頻繁出現(xiàn)這種現(xiàn)象的,因此在同一時刻被阻塞的線程不會很多。

(3) 利用這個阻塞操作來判斷異常中斷的客戶連接。

(4) 經(jīng)過壓力實驗證明這種實現(xiàn)的性能是非常好的。


分享標題:JavaNIO如何處理慢速的連接
文章轉(zhuǎn)載:http://m.5511xx.com/article/cdehhhc.html