1. 程式人生 > >Java面試15|網絡

Java面試15|網絡

keepalive char 瀏覽器 odin 完成 出現 打開 單獨 http響應

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設計為一個順序發送協議,而不是一個定時發送協議。

所謂三次握手(Three-way Handshake),是指建立一個TCP連接時,需要客戶端和服務器總共發送3個包。 三次握手的目的是連接服務器指定端口,建立TCP連接,並同步連接雙方的序列號和確認號並交換 TCP 窗口大小信息.在socket編程中,客戶端執行connect()時,將觸發三次握手。

由於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|網絡