windows下客户端连接上马上会断开连接_浅尝Java NIO与Tomcat简单连接调优
這周遇到一個連接斷開的問題,便沿著這條線學習了一下Java NIO,順便驗證一下Tomcat作為spring boot默認的web容器,是怎樣管理空閑連接的。
Java NIO(new IO/non-blockingIO)不同于BIO,BIO是堵塞型的,并且每一條學習路線的IO章節都會從BIO說起,因此大家非常熟悉。而NIO涉及Linux底層的select,poll,epoll等,要求對Linux的網絡編程有扎實功底,反正我是沒有搞清楚,在此推薦一篇通俗易懂的入門文章:
https://www.jianshu.com/p/ef418ccf2f7d
此處先引用文章的結論:
對于socket的文件描述符才有所謂BIO和NIO。
多線程+BIO模式會帶來大量的資源浪費,而NIO+IO多路復用可以解決這個問題。
在Linux下,基于epoll的IO多路復用是解決這個問題的最佳方案;epoll相比select和poll有很大的性能優勢和功能優勢,適合實現高性能網絡服務。
底層的技術先交給大神們解決,我們著重從Java上層應用的角度了解一下。
從JDK 1.5起使用epoll代替了傳統的select/poll,極大提升了NIO的通信性能,因此下文提到Java NIO都是使用epoll的。
Java NIO涉及到的三大核心部分Channel、Buffer、Selector,它們都十分復雜,單單其中一部分都能寫成一篇文章,就不班門弄斧了。此處貼上一個自己學習NIO時設計的樣例,功能是服務器發布服務,客戶端連上服務器,客戶端向服務器發送若干次請求,達到若干次答復后,服務器率先斷開連接,隨后客戶端也斷開連接。
NIO服務器端關鍵代碼
public?void?handleRead(SelectionKey?key)?{????SocketChannel?sc?=?(SocketChannel)?key.channel();
????ByteBuffer?buf?=?(ByteBuffer)?key.attachment();try?{long?bytesRead?=?sc.read(buf);
????????StringBuffer?sb?=?new?StringBuffer();while?(bytesRead?>?0)?{
????????????buf.flip();while?(buf.hasRemaining())?{
????????????????sb.append((char)?buf.get());
????????????}
????????????buf.clear();
????????????bytesRead?=?sc.read(buf);
????????}
????????LOGGER.info("收到客戶端的消息:{}",?sb.toString());
????????writeResponse(sc,?sb.toString());if?(sb.toString().contains("3"))?{
????????????sc.close();
????????}
????}?catch?(IOException?e)?{
????????key.cancel();
????????e.printStackTrace();
????????LOGGER.info("疑似一個客戶端斷開連接");try?{
????????????sc.close();
????????}?catch?(IOException?e1)?{
????????????LOGGER.info("SocketChannel?關閉異常");
????????}
????}
}
NIO客戶端關鍵代碼
Iterator?iter?=?selector.selectedKeys().iterator();while?(iter.hasNext())?{????SelectionKey?key?=?iter.next();if?(key.isConnectable())?{while?(!socketChannel.finishConnect())?;
????????socketChannel.configureBlocking(false);
????????socketChannel.register(key.selector(),?SelectionKey.OP_READ,?ByteBuffer.allocateDirect(1024));
????????LOGGER.info("與服務器連接成功,使用本地端口{}",?socketChannel.socket().getLocalPort());
????}if?(key.isReadable())?{
????????SocketChannel?sc?=?(SocketChannel)?key.channel();
????????ByteBuffer?buf?=?(ByteBuffer)?key.attachment();long?bytesRead;try?{
????????????bytesRead?=?sc.read(buf);
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????????LOGGER.info("遠程服務器斷開了與本機的連接,本機也進行斷開");
????????????sc.close();continue;
????????}while?(bytesRead?>?0)?{
????????????buf.flip();while?(buf.hasRemaining())?{
????????????????System.out.print((char)?buf.get());
????????????}
????????????System.out.println();
????????????buf.clear();
????????????bytesRead?=?sc.read(buf);
????????}
????????TimeUnit.SECONDS.sleep(2);
????????String?info?=?"I'm?"?+?i++?+?"-th?information?from?client";
????????buffer.clear();
????????buffer.put(info.getBytes());
????????buffer.flip();while?(buffer.hasRemaining())?{
????????????sc.write(buffer);
????????}
????}
????iter.remove();
}
服務器日志
客戶端日志
從這個樣例可以看到,客戶端和服務器都能根據自身的策略,與對端斷開連接,本例中是服務器首先斷開連接,根據TCP協議,必然有一個時刻服務器處于FIN_WAIT_2狀態,而客戶端處于CLOSE_WAIT狀態
我們通過netstat命令找出這個狀態,果不其然。
但是JDK提供的NIO接口還是很復雜很難寫的,要用好它就必須借助于Netty、Mina等第三方庫的封裝,這部分就先不寫了。接下來考慮另外一個問題,在大并發的場景下,成千上萬的客戶端涌入與服務器連接,連接成功后不發送請求,浪費了服務器寶貴的資源,這時服務器該如何應對?
答案當然是設計合適的連接池來管理這些寶貴的資源,為此我們選用Tomcat作為學習對象,了解一下它是如何管理空閑連接的。
Tomcat的Connector組件用于管理連接,Tomcat8默認使用Http11NioProtocol,它有一個屬性ConnectionTimeout,注釋如下:
可以簡單理解成空閑超時時間,超時后Tomcat會主動關閉該連接來回收資源。
我們將它修改為10秒,得到如下配置類,并將該spring boot應用打包成tomcat-server.jar
@Componentpublic?class?MyEmbeddedServletContainerFactory?extends?TomcatServletWebServerFactory?{public?WebServer?getWebServer(ServletContextInitializer...?initializers)?{//?設置端口this.setPort(8080);return?super.getWebServer(initializers);????}protected?void?customizeConnector(Connector?connector)?{super.customizeConnector(connector);
????????Http11NioProtocol?protocol?=?(Http11NioProtocol)?connector.getProtocolHandler();//?設置最大連接數
????????protocol.setMaxConnections(2000);//?設置最大線程數
????????protocol.setMaxThreads(2000);//?設置連接空閑超時
????????protocol.setConnectionTimeout(10?*?1000);
????}
}
我們將上文的NIO客戶端略微修改一下形成TomcatClient,功能就是連上服務器后什么都不做。
Iterator?iter?=?selector.selectedKeys().iterator();while?(iter.hasNext())?{????SelectionKey?key?=?iter.next();if?(key.isConnectable())?{while?(!socketChannel.finishConnect())?;
????????socketChannel.configureBlocking(false);
????????socketChannel.register(key.selector(),?SelectionKey.OP_READ,?ByteBuffer.allocateDirect(1024));
????????LOGGER.info("與遠程服務器連接成功,使用本地端口{}",?socketChannel.socket().getLocalPort());
????}if?(key.isReadable())?{
????????SocketChannel?sc?=?(SocketChannel)?key.channel();
????????ByteBuffer?buf?=?(ByteBuffer)?key.attachment();long?readCount;
????????readCount?=?sc.read(buf);while?(readCount?>?0)?{
????????????buf.flip();while?(buf.hasRemaining())?{
????????????????System.out.print((char)?buf.get());
????????????}
????????????System.out.println();
????????????buf.clear();
????????????readCount?=?sc.read(buf);
????????}//?遠程服務器斷開連接后會不停觸發OP_READ,并收到-1代表End-Of-Streamif?(readCount?==?-1)?{
????????????LOGGER.info("遠程服務器斷開了與本機的連接,本機也進行斷開");
????????????sc.close();
????????}
????}
????iter.remove();
}
分別運行服務器和客戶端,可以看到客戶端打印如下日志
30:27連上服務器,不進行任何請求,經過10秒后到30:37被服務器斷開了連接。
此時netstat會發現還有一個TIME_WAIT的連接
根據TCP協議主動斷開方必須等待2MSL才能關閉連接,Linux默認的2MSL=60秒(順帶說一句網上很多資料說CentOS的/proc/sys/net/ipv4/tcp_fin_timeout能修改2MSL的時間,實際并沒有效果,這個參數應該是被寫進內核,必須重新編譯內核才能修改2MSL)。持續觀察netstat發現31:36的時候TIME_WAIT連接還在,到了31:38連接消失了,可以認為是31:37關閉連接,對比上文30:37剛好經過了2MSL(默認60秒)的時間。
總結
以上是生活随笔為你收集整理的windows下客户端连接上马上会断开连接_浅尝Java NIO与Tomcat简单连接调优的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 点击按钮出现图片_坪山电动车上牌丨部分手
- 下一篇: 两个date 相差得到月份_DATE和T