摘要:這里只讀數(shù)據(jù),未作任何處理讀完成這里我根據(jù)返回值來(lái)拋出異常,使得下面的語(yǔ)句塊捕捉并關(guān)閉連接,也可以不拋出異常,直接在里處理。
我這篇文章想講的是編程時(shí)如何正確關(guān)閉tcp連接。
首先給出一個(gè)網(wǎng)絡(luò)上絕大部分的java nio代碼示例:
服務(wù)端:
1首先實(shí)例化一個(gè)多路I/O復(fù)用器Selector
2然后實(shí)例化一個(gè)ServerSocketChannel
3ServerSocketChannel注冊(cè)為非阻塞(channel.configureBlocking(false);)
4ServerSocketChannel注冊(cè)到Selector,并監(jiān)聽連接事件(serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);)
5Selector開始輪詢,如果監(jiān)聽到了isAcceptable()事件,就建立一個(gè)連接,如果監(jiān)聽到了isReadable()事件,就讀數(shù)據(jù)。
6處理完或者在處理每個(gè)事件之前將SelectionKey移除出Selector.selectedKeys()
代碼:
package qiuqi.main; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; public class NioServer { public static void main(String[] args) throws IOException { startServer(); } static void startServer() throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(999)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() > 0) { Iteratoriterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey sk = iterator.next(); iterator.remove(); if (sk.isAcceptable()) { SocketChannel channel = serverSocketChannel.accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { System.out.println("讀事件!!!"); SocketChannel channel = (SocketChannel) sk.channel(); try { ByteBuffer byteBuffer = ByteBuffer.allocate(200); //這里只讀數(shù)據(jù),未作任何處理 channel.read(byteBuffer); } catch (IOException e) { //手動(dòng)關(guān)閉channel System.out.println(e.getMessage()); sk.cancel(); if (channel != null) channel.close(); } } } } } }
還有說(shuō)明一下,為什么在if (sk.isReadable()){}這個(gè)里面加上異常捕捉,因?yàn)榭赡茏x數(shù)據(jù)的時(shí)候客戶端突然斷掉,如果不捕捉這個(gè)異常,將會(huì)導(dǎo)致整個(gè)程序結(jié)束。
而客戶端如果使用NIO編程,那么和服務(wù)端很像,然鵝,我們并不需要使用NIO編程,因?yàn)檫@里我想講的問(wèn)題和NIO或是普通IO無(wú)關(guān),在我想講的問(wèn)題上,他倆是一樣的,那么我就用普通socket編程來(lái)講解,因?yàn)檫@個(gè)好寫:)。
直接給代碼如下:
package qiuqi.main; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; public class TraditionalSocketClient { public static void main(String[] args) throws IOException { startClient(); } static void startClient() throws IOException { Socket socket = new Socket(); socket.connect(new InetSocketAddress(999)); socket.getOutputStream().write(new byte[100]); //要注意這個(gè)close方法,這是正常關(guān)閉socket的方法 //也是導(dǎo)致這個(gè)錯(cuò)誤的根源 socket.close(); } }
我們運(yùn)行客戶端和服務(wù)端的代碼,輸出的結(jié)果是:
讀事件!!!
讀事件!!!
讀事件!!!
讀事件!!!
讀事件!!!
讀事件!!!
....
讀事件!!!
讀事件!!!
無(wú)限個(gè)讀事件!!!
why???
客戶端正常關(guān)閉,然后顯然客戶端不可能再給服務(wù)端發(fā)送任何數(shù)據(jù)了,服務(wù)端怎么可能還有讀響應(yīng)呢?
我們現(xiàn)在把客戶端代碼的最后一行socket.close();這個(gè)去掉,再運(yùn)行一次!輸出結(jié)果是:
讀事件!!!
讀事件!!!
遠(yuǎn)程主機(jī)強(qiáng)迫關(guān)閉了一個(gè)現(xiàn)有的連接。
然后。。。就正常了(當(dāng)然代碼里會(huì)有異常提示的),這里的正常指的是不會(huì)輸出多余的讀事件!!!了。
這又是怎么回事?
我們知道如果去掉socket.close();那么客戶端是非正常關(guān)閉,服務(wù)端這邊會(huì)引發(fā)IOException。
引發(fā)完IOExpection之后,我們的程序在catch{}語(yǔ)句塊中手動(dòng)關(guān)閉了channel。
既然非正常關(guān)閉會(huì)引發(fā)異常,那么正常關(guān)閉呢?什么都不引發(fā)?但是這樣服務(wù)端怎么知道客戶端已經(jīng)關(guān)閉了呢?
顯然服務(wù)端會(huì)收到客戶端的關(guān)閉信號(hào)(可讀數(shù)據(jù)),而網(wǎng)絡(luò)上絕大多數(shù)代碼并沒(méi)有根據(jù)這個(gè)關(guān)閉信號(hào)來(lái)結(jié)束channel。
那么關(guān)閉信號(hào)是什么?
channel.read(byteBuffer);
這個(gè)語(yǔ)句是有返回值的,大多數(shù)情況是返回一個(gè)大于等于0的值,表示將多少數(shù)據(jù)讀入byteBuffer緩沖區(qū)。
然鵝,當(dāng)客戶端正常斷開連接的時(shí)候,它就會(huì)返回-1。雖然這個(gè)斷開連接信號(hào)也是可讀數(shù)據(jù)(會(huì)使得isReadable()為true),但是
這個(gè)信號(hào)無(wú)法被讀入byteBuffer,也就是說(shuō)一旦返回-1,那么無(wú)論再繼續(xù)讀多少次都是-1,并且會(huì)引發(fā)可讀事件isReadable()。
因此,這樣寫問(wèn)題就能得到解決,下面的代碼在try語(yǔ)句塊里。
SocketChannel channel = (SocketChannel) sk.channel(); try { ByteBuffer byteBuffer = ByteBuffer.allocate(200); int num; //這里只讀數(shù)據(jù),未作任何處理 num = channel.read(byteBuffer); if(num == -1) throw new IOException("讀完成"); } catch (IOException e) { System.out.println(e.getMessage()); sk.cancel(); if (channel != null) channel.close(); }
這里我根據(jù)返回值-1來(lái)拋出異常,使得下面的catch語(yǔ)句塊捕捉并關(guān)閉連接,也可以不拋出異常,直接在try{}里處理。
還要注意一點(diǎn)的是,假如說(shuō)bytebuffer已經(jīng)滿了,也就是channel.read(byteBuffer)返回0,那么即使客戶端正常關(guān)閉,也無(wú)法收到-1。因此當(dāng)bytebuffer滿的時(shí)候需要及時(shí)清空,或者一開始就開一個(gè)大一點(diǎn)的bytebuffer。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/73544.html
摘要:采用通信模型的服務(wù)端通常由一個(gè)獨(dú)立的線程負(fù)責(zé)監(jiān)聽客戶端的連接它接收到客戶端連接請(qǐng)求之后為每個(gè)客戶端創(chuàng)建一個(gè)新的線程進(jìn)行鏈路處理處理完成之后通過(guò)輸出流返回應(yīng)答給客戶端線程銷毀這就是典型的一請(qǐng)求一應(yīng)答通信模型該模型最大的問(wèn)題就是缺乏彈性伸縮能力 BIO 采用 BIO 通信模型的服務(wù)端, 通常由一個(gè)獨(dú)立的 Acceptor 線程負(fù)責(zé)監(jiān)聽客戶端的連接, 它接收到客戶端連接請(qǐng)求之后為每個(gè)客戶端創(chuàng)...
摘要:的異步即是異步的,也是非阻塞的。但是,也可以進(jìn)行一層稍微薄點(diǎn)的封裝,保留這種多路復(fù)用的模型,比如的,是一種同步非阻塞的模型。系統(tǒng)調(diào)用操作系統(tǒng)的系統(tǒng)調(diào)用提供了多路復(fù)用的非阻塞的系統(tǒng)調(diào)用,這也是機(jī)制實(shí)現(xiàn)需要用到的。 異步IO編程在javascript中得到了廣泛的應(yīng)用,之前也寫過(guò)一篇博文進(jìn)行梳理。js的異步IO即是異步的,也是非阻塞的。非阻塞的IO需要底層操作系統(tǒng)的支持,比如在linux上...
摘要:前言本篇主要講解中的機(jī)制和網(wǎng)絡(luò)通訊中處理高并發(fā)的分為兩塊第一塊講解多線程下的機(jī)制第二塊講解如何在機(jī)制下優(yōu)化資源的浪費(fèi)服務(wù)器單線程下的機(jī)制就不用我介紹了,不懂得可以去查閱下資料那么多線程下,如果進(jìn)行套接字的使用呢我們使用最簡(jiǎn)單的服務(wù)器來(lái)幫助大 前言 本篇主要講解Java中的IO機(jī)制和網(wǎng)絡(luò)通訊中處理高并發(fā)的NIO 分為兩塊:第一塊講解多線程下的IO機(jī)制第二塊講解如何在IO機(jī)制下優(yōu)化CPU資...
摘要:抽象類有一個(gè)方法用于使通道處于阻塞模式或非阻塞模式。注意抽象類的方法是由抽象類實(shí)現(xiàn)的,都是直接繼承了抽象類。大家有興趣可以看看的源碼,各種抽象類和抽象類上層的抽象類。 歷史回顧: Java NIO 概覽 Java NIO 之 Buffer(緩沖區(qū)) Java NIO 之 Channel(通道) 其他高贊文章: 面試中關(guān)于Redis的問(wèn)題看這篇就夠了 一文輕松搞懂redis集群原理及搭建...
摘要:的出現(xiàn)解決了這尷尬的問(wèn)題,非阻塞模式下,通過(guò),我們的線程只為已就緒的通道工作,不用盲目的重試了。注意要將注冊(cè)到,首先需要將設(shè)置為非阻塞模式,否則會(huì)拋異常。 showImg(https://segmentfault.com/img/remote/1460000017053374); 背景知識(shí) 同步、異步、阻塞、非阻塞 首先,這幾個(gè)概念非常容易搞混淆,但NIO中又有涉及,所以總結(jié)一下。 ...
閱讀 2170·2021-11-23 10:13
閱讀 2869·2021-11-09 09:47
閱讀 2916·2021-09-22 15:08
閱讀 3456·2021-09-03 10:46
閱讀 2309·2019-08-30 15:54
閱讀 1005·2019-08-28 18:09
閱讀 2506·2019-08-26 18:26
閱讀 2413·2019-08-26 13:48