Java面試15|網絡
1、查看網絡的統計信息:
netstat -s 結果中顯示統計信息,保護收發包,建立連接的數量
netstat -at 列出所有TCP端口
netstat -au 列出所有的UDP端口
netstat -aut 目前正在運行的TCP/UDP服務
netstat常用的參數如下:
-s或–statistice 顯示網絡工作信息統計表。
-t或–tcp 顯示TCP傳輸協議的連線狀況。
-u或–udp 顯示UDP傳輸協議的連線狀況。
-a或–all 顯示所有連接中的Socket。
-n或–numeric 直接使用IP地址,而不通過域名服務器。
netstat -n | awk ‘/^tcp/ {++S[$NF]} END{for(a in S) print a, S[a]}‘
$NF表示倒數第一個參數,某次運行的結果如下:
TIME_WAIT狀態從上圖可知由3種狀態可以轉換而來,根據TCP協議定義的3次握手斷開連接規定,發起socket主動關閉的一方 socket將進入TIME_WAIT狀態。TIME_WAIT狀態將持續2個MSL(Max Segment Lifetime),在Windows下默認為4分鐘,即240秒。TIME_WAIT狀態下的socket不能被回收使用. 具體現象是對於一個處理大量短連接的服務器,如果是由服務器主動關閉客戶端的連接,將導致服務器端存在大量的處於TIME_WAIT狀態的socket, 甚至比處於Established狀態下的socket多的多,嚴重影響服務器的處理能力,甚至耗盡可用的socket,停止服務。
通過調試Linux內核參數可以進行一些優化:
(1)net.ipv4.tcp_fin_timeout,默認60s,減小fin_timeout,減少TIME_WAIT連接數量。
(2)net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0,表示關閉;
(3)net.ipv4.tcp_tw_recycle = 1表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。
2、TCP(Transmission Control Protocol)三次握手與四次分手
TCP在不可靠的傳輸信道上提供了可靠傳輸的抽象,隱藏了我們的應用程序大部分的復雜性功能:丟包重傳,按序傳送,擁塞控制和避免,數據完整性,其他特性。當您使用TCP流,TCP協議保證您的所有字節發送與接收的數據是相同的,他們會以相同的順序到達對端。TCP設計為一個順序發送協議,而不是一個定時發送協議。
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
TCP協議的連接是全雙工連接,一個TCP連接存在雙向的讀寫通道。
簡單說來是 “先關讀,後關寫”,一共需要四個階段。以客戶機發起關閉連接為例:
1.服務器讀通道關閉
2.客戶機寫通道關閉
3.客戶機讀通道關閉
4.服務器寫通道關閉
關閉行為是在發起方數據發送完畢之後,給對方發出一個FIN(finish)數據段。直到接收到對方發送的FIN,且對方收到了接收確認ACK之後,雙方的數據通信完全結束,過程中每次接收都需要返回確認數據段ACK。
詳細過程:
第一階段
(1)客戶機發送完數據之後,向服務器發送一個FIN數據段,序列號為i;
(2)服務器收到FIN(i)後,返回確認段ACK,序列號為i+1,關閉服務器讀通道;
(3)客戶機收到ACK(i+1)後,關閉客戶機寫通道;
(此時,客戶機仍能通過讀通道讀取服務器的數據,服務器仍能通過寫通道寫數據)
第二階段
(1)服務器發送完數據之後,向客戶機發送一個FIN數據段,序列號為j;
(2)客戶機收到FIN(j)後,返回確認段ACK,序列號為j+1,關閉客戶機讀通道;
(3)服務器收到ACK(j+1)後,關閉服務器寫通道。
這是標準的TCP關閉兩個階段,服務器和客戶機都可以發起關閉,完全對稱。
綠色箭頭為client,而藍色的為server。
參考: (1)http://www.cnblogs.com/Jessy/p/3535612.html (2)http://www.oschina.net/question/2011290_2199294?fromerr=o3YIYWOP (3)http://itindex.net/detail/51010-httpclient-%E5%8E%9F%E7%90%86-%E6%97%B6%E5%BA%8F%E5%9B%BE
3、TCP協議的流量控制和擁塞控制
4、Java NIO
是一種同步非阻塞的I/O模型,也是I/O多路復用的基礎。具體說就是Selector會不斷輪詢註冊在其上的Channel,如果某個Channel上有新的TCP連接,讀或者寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectorKey可以獲取就緒Channel的集合,進行後續I/O操作。
NIO單線程輪詢事件,找到可以進行讀寫的網絡描述符進行讀寫。除了事件的輪詢是阻塞的(沒有可幹的事情必須要阻塞),剩余的I/O操作都是純CPU操作,沒有必要開啟多線程。
並且由於線程的節約,連接數大的時候因為線程切換帶來的問題也隨之解決,進而為處理海量連接提供了可能。
單線程處理I/O的效率確實非常高,沒有線程切換,只是拼命的讀、寫、選擇事件。但現在的服務器,一般都是多核處理器,如果能夠利用多核心進行I/O,無疑對效率會有更大的提高。
public class MultiplexerTimeServer implements Runnable { private Selector selector; private ServerSocketChannel servChannel; private volatile boolean stop; /** * 初始化多路復用器、綁定監聽端口 */ public MultiplexerTimeServer(int port) { try { selector = Selector.open(); // Channel主要用來讀寫網絡上的數據的。打開ServerSocketChannel,用於監聽客戶端的連接,它是所有客戶端連接的父管道 servChannel = ServerSocketChannel.open(); // 設置為非阻塞模式 servChannel.configureBlocking(false); // 綁定監聽端口 8080 servChannel.socket().bind(new InetSocketAddress(port), 1024); /* * Selector會不斷地輪詢在其上的Channel,如果某個Channel上面有新的TCP * 連接接入、讀和寫事件,這個Channel就處於就緒狀態 * * 註冊到Reacotr線程的多路復用器Selector上,監聽ACCEPT事件 */ servChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("The time server is start in port : " + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void stop() { this.stop = true; } public void run() { while (!stop) { try { // This method performs a blocking selection operation. // It returns only after at least one channel is selected,(只有在至少有一個事件就緒後才會進行返回,所以是阻塞的) // this selector‘s wakeup method is invoked, the current thread is interrupted, // or the given timeout period expires, whichever comes first. selector.select(1000); // 阻塞等待,休眠時間為1s Set<SelectionKey> selectedKeys = selector.selectedKeys(); /* * 當有處於就緒狀態的Channel時,selector將返回就緒狀態的Channel的SelectionKey * 集合,通過對就緒狀態的Channel集合進行叠代,可以進行網絡的異步讀寫操作 */ Iterator<SelectionKey> it = selectedKeys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); try { // 事件分發器,單線程選擇就緒的事件。 // I/O處理器,包括connect、read、write等,這種純CPU操作,一般開啟CPU核心個線程就可以。 // 業務線程,在處理完I/O後,業務一般還會有自己的業務邏輯,有的還會有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要單獨的線程。 handleInput(key); // 所以在這裏其實最好是用其它的線程來處理,而不要影響了事件分發器線程 } catch (Exception e) { if (key != null) { key.cancel(); if (key.channel() != null) key.channel().close(); } } } } catch (Throwable t) { t.printStackTrace(); } } // 多路復用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重復釋放資源 if (selector != null) try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } private void handleInput(SelectionKey key) throws IOException { if (key.isValid()) { // 處理新接入的請求消息 if (key.isAcceptable()) { // 接受一個新的客戶端接入請求 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); // 設置客戶端為異步非阻塞 sc.configureBlocking(false); // Add the new connection to the selector sc.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { /* * 讀取數據 * 讀取到的字節數,返回值有以下有三種結果: * (1)大於0,讀取到字節,對其進行解編碼 * (2)等於0,沒有讀取到字節,南紡股份正常場景,忽略 * (3)-1 ,鏈路已經關閉,需要關閉SocketChannel,釋放資源 */ SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(1024); // 由於設置了SocketChannel為異步非阻塞的,所以它的read是非阻塞的 int readBytes = sc.read(readBuffer); if (readBytes > 0) { /* * 將緩沖區當前的limit設置為position,position為0,用於後續對緩沖區的讀取操作。 * 然後根據緩沖區可讀的字節個數創建字節數組,調用get()操作將緩沖區可讀的字節數 * 組復制到新創建的字節數組中 */ readBuffer.flip(); byte[] bytes = new byte[readBuffer.remaining()]; readBuffer.get(bytes); String body = new String(bytes, "UTF-8"); System.out.println("The time server receive order : " + body); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER"; doWrite(sc, currentTime); } else if (readBytes < 0) { // 對端鏈路關閉 key.cancel(); sc.close(); } else{ ; // 讀到0字節,忽略 } } } } private void doWrite(SocketChannel channel, String response)throws IOException { /* * 由於SocketChannel是異步非阻塞的,並不能保證一次能夠把所有需要發送的數據發送,此時會出現寫半包問題。 * 需要註冊寫操作???,不斷輪詢Selector將沒有發送完的bytebuffer發送完畢。可以通過byteBuffer的hasRemain() * 方法判斷是否發送完畢。 */ if (response != null && response.trim().length() > 0) { // 將應答消息異步發送給客戶端 byte[] bytes = response.getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); // 將緩沖區中的字節數據發送 writeBuffer.put(bytes); writeBuffer.flip(); // 緩存區復位 channel.write(writeBuffer); } } }
Selector輪詢是阻塞的,而真正的I/O是異步非阻塞的。
對於NIO來說,緩存可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般來說可以減少一次系統空間到用戶空間的拷貝。但Buffer創建和銷毀的成本更高,更不宜維護,一般用來讀取大文件時使用。
參考文章:http://blog.csdn.net/szzt_lingpeng/article/details/50612018
5、NIO中, 如果不顯式的調用 system.gc()
那會出現什麽問題?
DirectBuffer的GC規則與堆對象的回收規則是一樣的,只有垃圾對象才會被回收,而判定是否為垃圾對象依然是根據引用樹中的存活節點來判定。
如果DirectByteBuffer的空間夠用,那麽System.gc()是不會觸發FullGC的。 也就是說在空間不夠用時,顯示調用才能進行回收,如果不顯式調用,那只能是拋出內存異常了。
在垃圾收集時,雖然虛擬機會對DirectMemory進行回收,但是DirectMemory卻不像新生代和老年代那樣,發現空間不足了就通知收集器進行垃圾回收,它只能等待老年代滿了後FullGC,然後“順便地”幫它清理掉內存中廢棄的對象。否則,只能等到拋出內存溢出異常時,在catch塊裏調用System.gc()。
參考:http://blog.csdn.net/donsonzhang/article/details/46666353
6、https 的工作原理,和 http 的區別
關於https可以參考:http://www.cnblogs.com/xinzhao/p/4949344.html
7、描述HTTP協議(HTTP請求和響應報文的格式)
HTTP協議采用“請求-應答”模式,當使用普通模式(非KeepAlive模式)時,每個請求/應答客戶和服務器都要新建一個連接,完成之後立即斷開連接(HTTP協議為無連接的協議);當使用Keep-Alive模式(又稱持久連接、連接重用)時,Keep-Alive功能使客戶端到服務器端的連接持續有效,當出現對服務器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連接。
(1)在HTTP 1.0中Keep-Alive默認是關閉的,需要在HTTP頭中加入“Connection: Keep-Alive” ,才能啟用Keep-Alive
(2)HTTP 1.1中Keep-Alive默認啟用,加入“Connection: close”可關閉。目前大部分瀏覽器都是用HTTP 1.1協議,也就是說默認都會發起Keep-Alive的連接請求了,所以是否能完成一個完整的Keep- Alive連接就看Web服務器的設置情況。
1,請求行
由3部分組成,分別為:請求方法、URL(見備註1)以及協議版本,之間由空格分隔
請求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及擴展方法,當然並不是所有的服務器都實現了所有的方法,部分方法即便支持,處於安全性的考慮也是不可用的
協議版本的格式為:HTTP/主版本號.次版本號,常用的有HTTP/1.0和HTTP/1.1
2,請求頭部
請求頭部為請求報文添加了一些附加信息,由"名/值"對組成,每行一對,名和值之間使用冒號分隔
常見請求頭如下:
請求頭 |
說明 |
Host |
接受請求的服務器地址,可以是IP:端口號,也可以是域名 |
User-Agent |
發送請求的應用程序名稱 |
Connection |
指定與連接相關的屬性,如Connection:Keep-Alive |
Accept-Charset |
通知服務端可以發送的編碼格式 |
Accept-Encoding |
通知服務端可以發送的數據壓縮格式 |
Accept-Language |
通知服務端可以發送的語言 |
請求頭部的最後會有一個空行,表示請求頭部結束,接下來為請求正文,這一行非常重要,必不可少
HTTP響應報文格式:
HTTP響應報文主要由狀態行、響應頭部、響應正文3部分組成
1,狀態行
由3部分組成,分別為:協議版本,狀態碼,狀態碼描述,之間由空格分隔。常見的狀態碼如下:
狀態碼 |
說明 |
200 |
響應成功 |
302 |
跳轉,跳轉地址通過響應頭中的Location屬性指定(JSP中Forward和Redirect之間的區別) |
400 |
客戶端請求有語法錯誤,不能被服務器識別 |
403 |
服務器接收到請求,但是拒絕提供服務(認證失敗) |
404 |
請求資源不存在 |
500 |
服務器內部錯誤 |
2,響應頭部
與請求頭部類似,為響應報文添加了一些附加信息。常見響應頭部如下:
響應頭 |
說明 |
Server |
服務器應用程序軟件的名稱和版本 |
Content-Type |
響應正文的類型(是圖片還是二進制字符串) |
Content-Length |
響應正文長度 |
Content-Charset |
響應正文使用的編碼 |
Content-Encoding |
響應正文使用的數據壓縮格式 |
Content-Language |
響應正文使用的語言 |
Java面試15|網絡