[總結] 網絡概念性問題(TCP/UDP, HTTP, 長鏈接/短鏈接) TOMCAT處理請求/參數配置

1 首先認識網絡模型

若是你讀過計算機專業,或者學習過網絡通訊,那你必定據說過 OSI 模型,它曾無數次讓你頭大。OSI 是 Open System Interconnection 的縮寫,譯爲「開放式系統互聯」。
OSI 模型把網絡通訊的工做分爲 7 層,從下到上分別是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。
OSI 只是存在於概念和理論上的一種模型,它的缺點是分層太多,增長了網絡工做的複雜性,因此沒有大規模應用。後來人們對 OSI 進行了簡化,合併了一些層,最終只保留了 4 層,從下到上分別是接口層、網絡層、傳輸層和應用層,這就是大名鼎鼎的 TCP/IP 模型。

咱們日常使用的程序(或者說軟件)通常都是經過應用層來訪問網絡的,程序產生的數據會 一層一層 地往下傳輸,直到最後的網絡接口層,就經過網線發送到互聯網上去了。數據每往下走一層,就會被這一層的協議增長一層包裝,等到發送到互聯網上時,已經比原始數據多了四層包裝。整個數據封裝的過程就像俄羅斯套娃。
當另外一臺計算機接收到數據包時,會從網絡接口層再一層一層往上傳輸,每傳輸一層就拆開一層包裝,直到最後的應用層,就獲得了最原始的數據,這纔是程序要使用的數據。
給數據加包裝的過程,實際上就是在數據的頭部增長一個標誌(一個數據塊),表示數據通過了這一層,我已經處理過了。給數據拆包裝的過程正好相反,就是去掉數據頭部的標誌,讓它逐漸現出原形。
你看,在互聯網上傳輸一份數據是多麼地複雜啊,而咱們卻感覺不到,這就是網絡模型的厲害之處。咱們只須要在代碼中調用一個函數,就能讓下面的全部網絡層爲咱們工做。

兩臺計算機進行通訊時,必須遵照如下原則:
必須是同一層次進行通訊,好比,A 計算機的應用層和 B 計算機的傳輸層就不能通訊,由於它們不在一個層次,數據的拆包會遇到問題。
每一層的功能都必須相同,也就是擁有徹底相同的網絡模型。若是網絡模型都不一樣,那不就亂套了,誰都不認識誰。
數據只能逐層傳輸,不能躍層。
每一層可使用下層提供的服務,並向上層提供服務。

創建相關的概念, 好比什麼是OSI七層網絡模型 什麼是 TCP/IP四層概念模型 tcp ,udp協議, http協議 socket 等區別?

1.1 網絡模型

概念上 ,他們是OSI七層網絡模型與TCP/IP四層網絡模型中的協議集
圖1-1

OSI七層網絡模型css

TCP/IP四層概念模型  html

對應網絡協議java

應用層(Application)web

應用層chrome

HTTP、TFTP, FTP, NFS, WAIS、SMTP數據庫

表示層(Presentation)apache

Telnet, Rlogin, SNMP, Gopher編程

會話層(Session)segmentfault

SMTP, DNSwindows

傳輸層(Transport)

傳輸層

TCP, UDP

網絡層(Network)

網絡層

IP, ICMP, ARP, RARP, AKP, UUCP

數據鏈路層(Data Link)

數據鏈路層

FDDI, Ethernet, Arpanet, PDN, SLIP, PPP

物理層(Physical)

IEEE 802.1A, IEEE 802.2到IEEE 802.11

  • 物理層 (對應網卡,網線,集線器,中繼器,調制解調器)
  • 數據鏈路層 (對應網橋,交換機)
  • 網絡層 (對應路由器)
  • 傳輸層(OSI下3層的主要任務是數據通訊,上3層的任務是數據處理。而傳輸層(Transport Layer)是OSI模型的第4層。所以該層是通訊子網和資源子網的接口和橋樑,起到承上啓下的做用.傳輸層的做用是向高層屏蔽下層數據通訊的細節,即向用戶透明地傳送報文。)
  • 會車層 略..
  • 表示層 略..
  • 應用層(計算機用戶,以及各類應用程序和網絡之間的接口,其功能是直接向用戶提供服務,完成用戶但願在網絡上完成的各類工做。它在其餘6層工做的基礎)

1.2 其餘概念

  • 1.2.1 TCP/IP

和OSI網絡模型相似的, 它表明四層網絡模型,  而所謂TCP/IP協議指的就是網絡模型中的一整套協議.(見 圖1-1 ).
TCP/IP 模型包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百個互爲關聯的協議,其中 TCP 和 IP 是最經常使用的兩種底層協議,因此把它們統稱爲「TCP/IP 協議族」。也就是說,「TCP/IP模型」中所涉及到的協議稱爲「TCP/IP協議族」,你能夠區分這兩個概念,也能夠認爲它們是等價的,隨便你怎麼想。 
  • 1.2.2 tcp/udp

是傳輸層的協議.
延伸的概念有:  tcp/upd的區別, tcp建立和斷開等.
---------------------引用開始----------------------------
TCP是面向鏈接的一種底層傳輸控制協議。TCP鏈接以後,客戶端和服務器能夠互相發送和接收消息,在客戶端或者服務器沒有主動斷開以前,鏈接一直存在,故稱爲長鏈接。特色:鏈接有耗時,傳輸數據無大小限制,準確可靠,先發先至
延伸閱讀: TCP的鏈接: 三次握手(建立tcp鏈接)和四次揮手(關閉tcp鏈接)
(創建起一個TCP鏈接須要通過「三次握手」:
第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。握 手過程當中傳送的包裏不包含數據,三次握手完畢後,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP鏈接一旦創建,在通訊雙方中的任何一方主動關閉連 接以前,TCP 鏈接都將被一直保持下去。斷開鏈接時服務器和客戶端都可以主動發起斷開TCP鏈接的請求,斷開過程須要通過「四次握手」(過程就不細寫 了,就是服務器和客戶端交互,最終肯定斷開)

UDP是無鏈接的用戶數據報協議,所謂的無鏈接就是在傳輸數據以前不須要交換信息沒有握手創建鏈接的過程,只須要直接將對應的數據發送到指定的地址和端口就行(沒法確認消息是否接收到)。故UDP的特色是不穩定,速度快,可廣播,通常數據包限定64KB以內,先發未必先至。 (QQ等IM通信通常可使用UDP傳輸)
 附表:tcp協議和udp協議的差異
  TCP UDP
是否鏈接 面向鏈接 面向非鏈接
傳輸可靠性 可靠 不可靠
應用場合 傳輸大量的數據,對可靠性要求較高的場合 傳送少許數據、對可靠性要求不高的場景
速度
--------------------- 引用結束 ----------------------------

  • 1.2.3  HTTP

應用層協議.定義的是傳輸數據的內容的規範。而在 傳輸層, HTTP基於的是 TCP協議.

全稱是Hypertext Transfer Protocol,即超文本傳輸協議。從名字上能夠看出該協議用於規定客戶端與服務端之間的傳輸規則,所傳輸的內容不侷限於文本(HTML 文件, 圖片文件, 查詢結果等)。

1053533-7152b9157998397d.png


HTTP協議中的數據是利用TCP協議傳輸的,因此支持HTTP也就必定支持TCP. 由於HTTP是基於TCP協議的應用,請求時需先創建TCP鏈接. 
延伸閱讀:
---------------------引用開始----------------------------
  • HTTP是無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
  • HTTP是媒體獨立的:這意味着,只要客戶端和服務器知道如何處理的數據內容,任何類型的數據均可以經過HTTP發送。客戶端以及服務器指定使用適合的MIME-type內容類型。
  • HTTP是無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。
更多內容  如(HTTP 消息結構 HTTP請求方法 HTTP 響應頭信息 HTTP狀態碼 HTTP content-type等) 
見  https://www.runoob.com/http/http-messages.html  
--------------------- 引用結束 ----------------------------

  • 1.2.4 Socket

socket只是一種鏈接模式,不是協議,socket是對TCP/IP協議的封裝,Socket自己並非協議,而是一個調用接口(API),socket本質是編程接口(API) .
經過Socket,咱們才能使用TCP/IP協議。tcp、udp,簡單的說(雖然不許確)是兩個最基本的協議,
不少其它協議都是基於這兩個協議如,http就是基於tcp的,.用socket能夠建立tcp鏈接,也能夠建立udp鏈接.
java C++ .net都有本身的socket編程實現.

Socket的大體位置以下: 爲了便於編程而設想出來的API/編程模型




2  網絡請求過程

以經過瀏覽器上網爲例:
假設咱們點擊了某網頁上的一個連接(HTTP),指向清華大學院系設置,其URL是:http://www.tsinghua.edu.cn/chn/yxsz/index.html。咱們來分析一下整個過程:
1 瀏覽器分析連接指向頁面的URL
2 瀏覽器向DNS請求解析www.tsinghua.edu.cn的IP地址
3 DNS系統解析出清華大學服務器的地址是166.111.4.100
4 瀏覽器與服務器創建TCP鏈接
5 瀏覽器發出取文件命令:GET /chn/yxsz/index.html
6 服務器www.tsinghua.edu.cn給出響應,把文件index.html返回給瀏覽器
7 釋放TCP鏈接
8 瀏覽器解析並顯示「清華大學院系設置」文件index.html中的內容

對應到網絡, 對照OSI模型是從請求方服務器物理層 到 應答方服務器物理層-> 應答方 XX-> 應答方 傳輸層(tcp/udp協議)-> 應答方 XXX-> 應答方 應用層(如http協議)
好比DNS服務器, 網卡等是屬於其餘網絡模型分層的內容.  其中最主要, 咱們會涉及到的就是傳輸層和應用層. 其餘層涉及對應的硬件和協議通常開發不會涉及.


外部請求經過路由網關等將請求發送到應答方的服務器後:
1 創建TCP 或者UDP鏈接.
若是是TCP鏈接的創建,須要三次握手則可創建鏈接造成一條TCP通道. 隨後便可進行數據的傳輸通訊(如以http協議爲標準傳輸通訊). 如TCP鏈接斷開, 則須要一個四次握手的過程.
若是UDP協議的鏈接則不用創建鏈接可直接通訊.
這部分的處理應該是由應答方操做系統負責. 以傳輸層以TCP協議通訊爲例, 創建TCP通道後的數據請求由應答方的操做系統對應轉給監聽的應用.

2 數據通訊
創建TCP鏈接(TCP通道)後, 後續的數據請求由操做系統放到 accept隊列中. 由應用層的具體應用如TOMCAT來獲取請求並處理並應答.  
在TCP/UDP鏈接後, 事實上請求方服務器和響應方服務器便可通訊. 如開發人員可經過socket(封裝tcp/udp的api)便捷的通訊. 如qq或者發送和接受gps數據.  雙方都經過socket來實現 client 和server便可. 這部分socket的框架還能用mina , netty進一步開發.
對於普通web服務器而言, 瀏覽器請求解析的協議約定爲HTTP協議, 因此請求方(通常爲瀏覽器或者httpclinet)經過和響應方服務器創建TCP鏈接後, 便可在該TCP信道上以HTTP協議的方式進行數據通訊(HTTP是傳輸數據的內容的規範, HTTP在傳輸層的傳輸實際仍是用TCP協議通訊).  而響應方通常是用NG或者TOMCAT服務器進行對應的HTTP請求的處理和響應.

延伸, Tomcat的角色
咱們可知:  網絡通訊
1 必須是同一層次進行通訊
數據只能逐層傳輸,不能躍層
每一層可使用下層提供的服務,並向上層提供服務.
Tomcat/Apache/NG之類的屬於應用層, 傳輸層的請求由操做系統來處理. 由操做系統在傳輸層處理後交給應用層( Tomcat )處理.
Tomcat處理後將響應反饋給操做系統, 再經過路由等方式反饋給請求方. (若是是LVS集羣,內部會修改請求方的地址. tomcat將請求反饋給lvs或者NG, 再由LVS或者NG反饋給外部的請求方)
Tomcat反饋數據的過程也必然是一個應用層-->傳輸層(操做系統)的過程.

3  長鏈接和短鏈接

3.1 HTTP, 長鏈接,短鏈接

HTTP是應用層協議.定義的是傳輸數據的內容的規範。
全稱是Hypertext Transfer Protocol,即超文本傳輸協議。從名字上能夠看出該協議用於規定客戶端與服務端之間的傳輸規則,所傳輸的內容不侷限於文本(HTML 文件, 圖片文件, 查詢結果等)。
HTTP對應在傳輸層使用的仍是TCP協議進行通訊.
  • HTTP基於請求響應模型。
客戶端向服務器發送一個請求,請求頭包含請求的方法、URI、協議版本、以及包含請求修飾符、客戶端信息和內容的相似MIME的消息結果。服務器則返回一個狀態行做爲響應,內容包括消息協議的版本、成功或失敗編碼加上包含服務器信息、實體元信息以及可能的實體內容。
  • HTTP是無鏈接的
雖然HTTP是基於TCP的,可是HTTP自己是無鏈接的。客戶端和服務器的連接是基於一種請求應答模式。及客戶端和服務器創建一個連接,客戶端提交一個請求,服務器端收到請求後返回一個響應,而後兩者就斷開連接。(默認最初HTTP協議就是這樣使用的)
  • HTTP協議是無狀態的協議
即每一次請求都是互相獨立的。(無狀態指的是協議對於事務處理沒有記憶能力,服務器不知道客戶端是什麼狀態。也就是說,打開一個服務器上的網頁和你以前打開這個服務器上的網頁之間沒有任何聯繫。這個識別用戶的解決方案: 在瀏覽器一方就是使用tomcat給session在本地記錄爲cookie (Cookie與Session機制) 而對於APP來講就是APP在每次請求時都帶上登陸時服務器給與的token)

HTTP協議的最初實現是,每個http請求都會打開一個tcp socket鏈接,當交互完畢後會關閉這個鏈接(TCP創建鏈接與斷開鏈接是要通過三次握手與四次揮手的)。
顯然在這種設計中,每次發送Http請求都會消耗不少的額外資源,即鏈接的創建與銷燬。
在HTTP最初的使用中(HTTP 1.1以前的版本, 默認每次HTTP請求建立TCP鏈接, 創建TCP通道後 再基於TCP使用HTTP協議傳輸數據. 一次HTTP請求(包含請求+響應或者超時) 後 即HTTP請求結束, 同時關閉TCP鏈接.  
整個過程最大的開銷實際上是建立TCP鏈接的過程. 
因而,HTTP協議的也進行了發展,經過持久鏈接的方法來進行socket鏈接複用。
創建一次TCP鏈接(TCP通道)後, 只通訊一次HTTP就關閉TCP鏈接. 這種被成爲HTTP 短鏈接.
創建一次TCP鏈接(TCP通道)後, 可屢次進行HTTP通訊, 保留必定的時間或者HTTP請求次數後才關閉TCP鏈接. 這種被成爲HTTP 長鏈接或者持久鏈接.
總之
TCP自己沒有什麼長鏈接/短鏈接. 只是HTTP建立TCP鏈接後, 一次通訊結束後是否馬上調用 socket.close()關閉TCP鏈接. 
如是則咱們稱呼爲HTTP短鏈接.
若是HTTP建立TCP鏈接後, 可屢次利用TCP通道進行HTTP協議的通訊,而非一次通訊就馬上關閉TCP鏈接,
這種使用方式咱們稱呼爲HTTP長鏈接/持久鏈接. 長鏈接的好處是省去了建立鏈接的耗時。


  從圖中能夠看到:

在串行鏈接中,每次交互都要打開關閉鏈接

在持久鏈接中,第一次交互會打開鏈接,交互結束後鏈接並不關閉,下次交互就省去了創建鏈接的過程。




3.2 HTTP的版本

HTTP版本簡單分爲三類:1.1以前,1.1,2.0 , 3.0
HTTP 1.1以前
  • 支持持久鏈接。一旦服務器對客戶端發出響應就當即斷開TCP鏈接 (即每次http請求都是短鏈接,每次請求須要先建立tcp鏈接(三次握手)後才能發送http請求,獲得響應後經過四次握手關閉tcp鏈接
  • 無請求頭跟響應頭
  • 客戶端的先後請求是同步的。下一個請求必須等上一個請求從服務端拿到響應後才能發出,有點相似多線程的同步機制。
HTTP  1.1(主流版本)
  • 與1.1以前的版本相比,作了如下性能上的提高
  • 增長請求頭跟響應頭
  • 支持持久鏈接。客戶端經過請求頭中指定Connection爲keep-alive告知服務端不要在完成響應後當即釋放鏈接。HTTP是基於TCP的,在HTTP 1.1中一次TCP鏈接能夠處理屢次HTTP請求
  • 客戶端不一樣請求之間是異步的。下一個請求沒必要等到上一個請求回來後再發出,而能夠連續發出請求,有點相似多線程的異步處理。

HTTP 2.0

本着向下兼容的原則,1.1版本有的特性2.0都具有,也使用相同的API。可是 2.0將只用於https網址。 因爲2.0的普及還須要比較長的一段時間,這裏不展開
咱們重點關注一下當前1.1版本所作幾點改變。HTTP1.1 支持持久鏈接有什麼好處呢?HTTP是基於TCP鏈接的,若是鏈接被頻繁地啓動而後斷開就會花費不少資源在TCP三次握手以及四次揮手上,效率低下。以請求一個網頁爲例, 咱們知道,一個html網頁上的圖片資源並非直接嵌入在網頁上,而只是提供url,圖片仍須要額外發 HTTP  請求去下載。一個網頁從請求到最終加載到本地每每須要通過多個HTTP請求。在1.1版本以前請求一個網頁就須要發生屢次"握手-揮手"的過程,每次鏈接之間相互獨立(即經過http短鏈接, 每次都請求都須要和web應用所在的操做系統經過三次握手創建tcp鏈接,而創建tcp鏈接很浪費時間和資源。); 而1.1及以後的版本最少只須要一次就夠。

HTTP各版本的改進說明詳見
[寫的很不錯]HTTP/2.0 相比1.0有哪些重大改進? https://www.zhihu.com/question/34074946 



3.3 長鏈接/短鏈接

3.3.1 概念

如上可知, 長鏈接(持久鏈接) 和短鏈接只是HTTP協議中使用TCP方式的不一樣而已(是否屢次使用同一個TCP通道).
短鏈接和長鏈接的優點,分別是對方的劣勢。想要圖簡單,不追求高性能,使用短鏈接合適,這樣咱們就不須要操心鏈接狀態的管理;想要追求性能,使用長鏈接.

HTTP/1.0中,默認 使用的是 短鏈接 。也就是說,瀏覽器和服務器每進行一次HTTP操做,就 創建一次鏈接,但任務結束就中斷連 若是客戶端瀏覽器訪問的某個HTML或其餘類型的 Web頁中包含有其餘的Web資源,如JavaScript文件、圖像文件、CSS文件等;當瀏覽器每遇到這樣一個Web資源,就會創建一個HTTP會話。這樣一個網頁有不少圖片或者文件的狀況下, 每次請求都要建立一個TCP鏈接很是耗時.(同時瀏覽器同域名請求, 是有的最大併發數限制的. 好比chrome在HTTP/1.0協議中同一個域名請求的最大併發是6. 若是使用chrome 以短鏈接的方式來請求同一個域名, 那麼chrome最可能是6個併發同時請求, 分別獲取網頁的資源文件(css,jsp,img 等等). 並且每次都要建立TCP鏈接.
大大影響網頁打開速度)
 
但從 HTTP/1.1起,默認使用長鏈接(持久鏈接),用以保持鏈接特性。(HTTP持久鏈接(HTTP persistent connection,也稱做HTTP keep-alive或HTTP connection reuse)是使用同一個TCP鏈接來發送和接收多個HTTP請求/應答, 而不是爲每個新的請求/應答打開新的鏈接的方法。)
使用長鏈接的HTTP協議,會在響應頭有加入這行代碼:
Connection:keep-alive 服務器和客戶端都要設置
在使用長鏈接的狀況下,當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的 TCP鏈接不會關閉,若是客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經創建的鏈接。Keep-Alive不會永久保持鏈接,它有一個保持時間,能夠在不一樣的服務器軟件(如Apache)中設定這個時間。實現長鏈接要客戶端和服務端都支持長鏈接。HTTP協議的長鏈接和短鏈接,實質上是TCP協議的長鏈接和短鏈接。

3.3.2 HTTP/1.0+的Keep-Alive

從1996年開始,不少HTTP/1.0瀏覽器與服務器都對協議進行了擴展,那就是「keep-alive」擴展協議。注意,這個擴展協議是做爲1.0的補充的「實驗型持久鏈接」出現的。keep-alive已經再也不使用了,最新的HTTP/1.1規範中也沒有對它進行說明,只是不少應用延續了下來。
在 HTTP 1.0 中, 沒有官方的 keepalive 的操做。一般是在現有協議上添加一個指數。 若是瀏覽器支持 keep-alive,它會在請求的包頭中添加:
Connection: Keep-Alive
而後當服務器收到請求,做出迴應的時候,它也添加一個頭在響應中:
Connection: Keep-Alive
這樣作,鏈接就不會中斷,而是保持鏈接。當客戶端發送另外一個請求時,它會使用同一個鏈接。 這一直繼續到客戶端或服務器端認爲會話已經結束,其中一方中斷鏈接。

經過keep-alive補充協議,客戶端與服務器之間完成了持久鏈接,然而仍然存在着一些問題:
在HTTP/1.0中keep-alive不是標準協議,客戶端必須發送Connection:Keep-Alive來激活keep-alive鏈接。
代理服務器(apache之類)可能沒法支持keep-alive,由於一些代理是"盲中繼",沒法理解首部的含義,只是將首部逐跳轉發。因此可能形成客戶端與服務端都保持了鏈接,可是代理不接受該鏈接上的數據。

3.3.3 HTTP/1.1的持久鏈接

HTTP/1.1採起持久鏈接的方式替代了Keep-Alive。 HTTP 1.1 中 全部的鏈接默認都是持續鏈接,除非特殊聲明不支持。若是要顯式關閉,須要在報文中加上Connection:Close首部。即在HTTP/1.1中,全部的鏈接都進行了複用。然而如同Keep-Alive同樣,空閒的持久鏈接也能夠隨時被客戶端與服務端關閉。不發送Connection:Close不意味着服務器承諾鏈接永遠保持打開。

然而,Apache 2.0 httpd 的默認鏈接過時時間是僅僅15秒,對於 Apache 2.2 只有5秒。 短的過時時間的優勢是可以快速的傳輸多個web頁組件,而不會綁定多個服務器進程或線程太長時間。


長鏈接短鏈接的其餘內容見  聊聊 TCP 長鏈接和心跳那些事




4 Socket編程

咱們知道網絡傳輸的協議是TCP/UDP. 咱們的請求響應交互其實對應就是client/server.
對於編程的語言的學習,咱們一般須要掌握Socket編程.
Socket的原意是「插座」,在計算機通訊領域,socket 被翻譯爲「套接字」.
socket是什麼?
Socket封裝了複雜的底層協議, 是對外的提供簡單的API, 以便編程.
因此咱們所說的 socket 編程,是站在傳輸層的基礎上,因此可使用 TCP/UDP 協議,可是不能幹「訪問網頁」這樣的事情,由於訪問網頁所須要的 http 協議位於應用層。
Socket通信的過程

Server端Listen(監聽)某個端口是否有鏈接請求,Client端向Server 端發出Connect(鏈接)請求,Server端向Client端發回Accept(接受)消息。一個鏈接就創建起來了。Server端和Client 端均可以經過Send,Write等方法與對方通訊。
對於一個功能齊全的Socket,都要包含如下基本結構,其工做過程包含如下四個基本的步驟:
  (1) 建立Socket;
  (2) 打開鏈接到Socket的輸入/出流;
  (3) 按照必定的協議對Socket進行讀/寫操做;
  (4) 關閉Socket.

java實現參考以下, 可跳過
client.java
Socket socket=new Socket("127.0.0.1",4700);

        //向本機的4700端口發出客戶請求

        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));

        //由系統標準輸入設備構造BufferedReader對象

        PrintWriter os=new PrintWriter(socket.getOutputStream());

        //由Socket對象獲得輸出流,並構造PrintWriter對象

        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //由Socket對象獲得輸入流,並構造相應的BufferedReader對象

        String readline;

        readline=sin.readLine(); //從系統標準輸入讀入一字符串

        while(!readline.equals("bye")){

        //若從標準輸入讀入的字符串爲 "bye"則中止循環

          os.println(readline);

          //將從系統標準輸入讀入的字符串輸出到Server

          os.flush();

          //刷新輸出流,使Server立刻收到該字符串

          System.out.println("Client:"+readline);

          //在系統標準輸出上打印讀入的字符串

          System.out.println("Server:"+is.readLine());

          //從Server讀入一字符串,並打印到標準輸出上

          readline=sin.readLine(); //從系統標準輸入讀入一字符串

        } //繼續循環

        os.close(); //關閉Socket輸出流

        is.close(); //關閉Socket輸入流

        socket.close(); //關閉Socket

      }catch(Exception e) {

        System.out.println("Error"+e); //出錯,則打印出錯信息

      }
server.java
  try{

        ServerSocket server=null;

        try{

          server=new ServerSocket(4700);

        //建立一個ServerSocket在端口4700監聽客戶請求

        }catch(Exception e) {

          System.out.println("can not listen to:"+e);

        //出錯,打印出錯信息

        }

        Socket socket=null;

        try{

          socket=server.accept();

          //使用accept()阻塞等待客戶請求,有客戶

          //請求到來則產生一個Socket對象,並繼續執行

        }catch(Exception e) {

          System.out.println("Error."+e);

          //出錯,打印出錯信息

        }

        String line;

        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));

         //由Socket對象獲得輸入流,並構造相應的BufferedReader對象

        PrintWriter os=newPrintWriter(socket.getOutputStream());

         //由Socket對象獲得輸出流,並構造PrintWriter對象

        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));

         //由系統標準輸入設備構造BufferedReader對象

        System.out.println("Client:"+is.readLine());

        //在標準輸出上打印從客戶端讀入的字符串

        line=sin.readLine();

        //從標準輸入讀入一字符串

        while(!line.equals("bye")){

        //若是該字符串爲 "bye",則中止循環

          os.println(line);

          //向客戶端輸出該字符串

          os.flush();

          //刷新輸出流,使Client立刻收到該字符串

          System.out.println("Server:"+line);

          //在系統標準輸出上打印讀入的字符串

          System.out.println("Client:"+is.readLine());

          //從Client讀入一字符串,並打印到標準輸出上

          line=sin.readLine();

          //從系統標準輸入讀入一字符串

        }  //繼續循環

        os.close(); //關閉Socket輸出流

        is.close(); //關閉Socket輸入流

        socket.close(); //關閉Socket

        server.close(); //關閉ServerSocket

      }catch(Exception e){

        System.out.println("Error:"+e);

        //出錯,打印出錯信息

      }

    }

在這種編程模式中, 雙方能夠互爲client/server 這樣就實現了相似QQ這類IM的功能.
若是server方遇到的請求壓力很大, 這部分的延伸就是 [Java線程池的分析和使用]如https://ifeve.com/java-threadpool/ 
而 socket的相關工具框架 見 mina, netty.
---------------------引用開始----------------------------
在java 原生的Socket編程中, 實際上Socket編程就是所謂的網絡編程也就是基於TCP/UDP網絡層協議進行編程,就拿Java和TCP來講:
對於BIO,TCP編程就是ServerSocket/Socket
對於NIO,TCP編程就是ServerSocketChannel/SocketChannel
對於AIO,TCP編程就是AsynchronousServerSocketChannel/AsynchronousSocketChanne
---------------------引用結束----------------------------


HTTP編程和鏈接池

5.1 HttpComponents

對於瀏覽器的請求, 協議天然是基於HTTP協議.
client端HTTP協議請求的編程( 好比我方系統對外部系統發起HTTP請求獲取JSON之類) 解決方案通常是使用apache的 HttpClinet 工具(已經更名爲Apache HttpComponents). 
經過 HttpComponents能夠在Java中實現httpclinet 和httpserver的編程.

5.2 HTTP鏈接池

Http鏈接須要的三次握手開銷很大,這一開銷對於比較小的http消息來講更大。可是若是咱們直接使用已經創建好的http鏈接,這樣花費就比較小,吞吐率更大。 
傳統的HttpURLConnection並不支持鏈接池,若是要實現鏈接池的機制,還須要本身來管理鏈接對象。除了HttpURLConnection,你們確定還知道HttpClient。通常狀況下,普通使用HttpClient已經能知足咱們的需求,不過有時候,在咱們須要高併發大量的請求網絡的時候,仍是用「鏈接池」這樣的概念能提高吞吐量。
好比org.apache.httpcomponents.httpclient(版本4.4)提供的鏈接池(PoolingHttpClientConnectionManager)來實現咱們的高併發網絡請求。

HttpClient如何生成持久鏈接
HttpClien中使用了鏈接池來管理持有鏈接,同一條TCP鏈路上,鏈接是能夠複用的。HttpClient經過鏈接池的方式進行鏈接持久化。
 其實「池」技術是一種通用的設計,其設計思想並不複雜:
  • 當有鏈接第一次使用的時候創建鏈接
  • 結束時對應鏈接不關閉,歸還到池中
  • 下次同個目的的鏈接可從池中獲取一個可用鏈接
  • 按期清理過時鏈接
相關httpclient的解讀可見 http://www.cnblogs.com/kingszelda/p/8988505.html 

5.2.1 使用鏈接池的好處

一、下降延遲:若是不採用鏈接池,每次鏈接發起Http請求的時候都會從新創建TCP鏈接(經歷3次握手),用完就會關閉鏈接(4次揮手),若是採用鏈接池則減小了這部分時間損耗,別小看這幾回握手,本人通過測試發現,基本上3倍的時間延遲
二、支持更大的併發:若是不採用鏈接池,每次鏈接都會打開一個端口,在大併發的狀況下系統的端口資源很快就會被用完,致使沒法創建新的鏈接

5.2.2 簡單鏈接管理器

BasicHttpClientConnectionManager是個簡單的鏈接管理器,它一次只能管理一個鏈接。儘管這個類是線程安全的,它在同一時間也只能被一個線程使用。 BasicHttpClientConnectionManager會盡可能重用舊的鏈接來發送後續的請求,而且使用相同的路由。若是後續請求的路由和舊鏈接中的路由不匹配, BasicHttpClientConnectionManager就會關閉當前鏈接,使用請求中的路由從新創建鏈接。若是當前的鏈接正在被佔用,會拋出 java.lang.IllegalStateException異常。


5.2.3 鏈接池管理器

相對BasicHttpClientConnectionManager來講,PoolingHttpClientConnectionManager是個更復雜的類,它管理着鏈接池,能夠同時爲不少線程提供http鏈接請求。Connections are pooled on a per route basis.當請求一個新的鏈接時,若是鏈接池有有可用的持久鏈接,鏈接管理器就會使用其中的一個,而不是再建立一個新的鏈接。PoolingHttpClientConnectionManager維護的鏈接數在每一個路由基礎和總數上都有限制。默認,每一個路由基礎上的鏈接不超過2個,總鏈接數不能超過20。在實際應用中,這個限制可能會過小了,尤爲是當服務器也使用Http協議時。
下面的例子演示了若是調整鏈接池的參數:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    // 將最大鏈接數增長到200
    cm.setMaxTotal(200);
    // 將每一個路由基礎的鏈接增長到20
    cm.setDefaultMaxPerRoute(20);
    //將目標主機的最大鏈接數增長到50
    HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .build();

5.2.4 關閉鏈接管理器

當一個HttpClient的實例不在使用,或者已經脫離它的做用範圍,咱們須要關掉它的鏈接管理器,來關閉掉全部的鏈接,釋放掉這些鏈接佔用的系統資源。 
CloseableHttpClient httpClient = <...>
    httpClient.close();

5.2.5 鏈接回收策略

經典阻塞I/O模型的一個主要缺點就是隻有當組側I/O時,socket才能對I/O事件作出反應。當鏈接被管理器收回後,這個鏈接仍然存活,可是卻沒法監控socket的狀態,也沒法對I/O事件作出反饋。若是鏈接被服務器端關閉了,客戶端監測不到鏈接的狀態變化(也就沒法根據鏈接狀態的變化,關閉本地的socket)。

HttpClient爲了緩解這一問題形成的影響,會在使用某個鏈接前,監測這個鏈接是否已通過時,若是服務器端關閉了鏈接,那麼鏈接就會失效。這種過期檢查並非100%有效,而且會給每一個請求增長10到30毫秒額外開銷。惟一一個可行的,且does not involve a one thread per socket model for idle connections的解決辦法,是創建一個監控線程,來專門回收因爲長時間不活動而被斷定爲失效的鏈接。這個監控線程能夠週期性的調用ClientConnectionManager類的closeExpiredConnections()方法來關閉過時的鏈接,回收鏈接池中被關閉的鏈接。它也能夠選擇性的調用ClientConnectionManager類的closeIdleConnections()方法來關閉一段時間內不活動的鏈接。



5.2.6 鏈接存活策略

Http規範沒有規定一個持久鏈接應該保持存活多久。有些Http服務器使用非標準的Keep-Alive頭消息和客戶端進行交互,服務器端會保持數秒時間內保持鏈接。HttpClient也會利用這個頭消息。若是服務器返回的響應中沒有包含Keep-Alive頭消息,HttpClient會認爲這個鏈接能夠永遠保持。然而,不少服務器都會在不通知客戶端的狀況下,關閉必定時間內不活動的鏈接,來節省服務器資源。



6 Tomcat 處理請求過程


圖6-1

外部請求傳輸層由操做系統來處理(如TCP鏈接建立) (操做系統的網絡內核對TCP鏈接的自己也有限制. 好比Linux網絡內核對本地端口號範圍有限制(Linux內核的TCP/IP協議實現模塊對系統中全部的客戶端TCP鏈接對應的本地端口號的範圍進行了限制) 或者好比Linux網絡內核的IP_TABLE防火牆對最大跟蹤的TCP鏈接數有限制. 等等. )
TCP建立後操做系統內核會把鏈接從syn隊列中取出,再把這個鏈接放到accept隊列中. 最後應用服務器(Tomcat)調用accept系統調用從accept隊列中獲取已經創建成功的鏈接套接字.

6.1 Tomcat的相關配置

6.1.1 配置文件server.xml


6.1.2 認識Connector

Tomcat Connector  的主要功能,是接收鏈接請求,建立Request和Response對象用於和請求端交換數據;而後分配線程讓Engine(也就是Servlet容器)來處理這個請求,並把產生的Request和Response對象傳給Engine。當Engine處理完請求後,也會經過Connector將響應返回給客戶端。能夠說,Servlet容器處理請求,是須要Connector進行調度和控制的,Connector是Tomcat處理請求的主幹,所以Connector的配置和使用對Tomcat的性能有着重要的影響。
Connector在處理HTTP請求時,會使用不一樣的protocol。不一樣的Tomcat版本支持的protocol不一樣,其中最典型的protocol包括BIO、NIO和APR(Tomcat7中支持這3種,Tomcat8增長了對NIO2的支持,而到了Tomcat8.5和Tomcat9.0,則去掉了對BIO的支持)。
BIO是Blocking IO,顧名思義是阻塞的IO;NIO是Non-blocking IO,則是非阻塞的IO。而APR是Apache Portable Runtime,是Apache可移植運行庫,利用本地庫能夠實現高可擴展性、高性能;Apr是在Tomcat上運行高併發應用的首選模式,可是須要安裝apr、apr-utils、tomcat-native等包。
若是沒有指定protocol,則使用默認值HTTP/1.1,其含義以下:在Tomcat7中,自動選取使用BIO或APR(若是找到APR須要的本地庫,則使用APR,不然使用BIO);在Tomcat8中,自動選取使用NIO或APR(若是找到APR須要的本地庫,則使用APR,不然使用NIO)

6.1.3 BIO/NIO有何不一樣

不管是BIO,仍是NIO,Connector處理請求的大體流程是同樣的:
在accept隊列中接收鏈接(當客戶端向服務器發送請求時,若是客戶端與操做系統完成三次握手創建了鏈接,則操做系統將該鏈接放入accept隊列);在鏈接中獲取請求的數據,生成request;調用servlet容器處理請求;返回response。爲了便於後面的說明,首先明確一下鏈接與請求的關係:鏈接是TCP層面的(傳輸層),對應socket(tcp/ip協議組的api封裝,方便調用);請求是HTTP層面的(應用層),必須依賴於TCP的鏈接實現;一個TCP鏈接中可能傳輸多個HTTP請求。
在BIO 實現的Connector中,處理請求的主要實體是JIoEndpoint對象。JIoEndpoint維護了Acceptor和Worker: Acceptor接收socket,而後從Worker工做線程池中找出空閒的線程處理socket,若是worker線程池沒有空閒線程,則Acceptor將阻塞。其中Worker是Tomcat自帶的線程池,若是經過<Executor>配置了其餘線程池,原理與Worker相似。

在NIO實現的Connector中,處理請求的主要實體是NIoEndpoint對象。NIoEndpoint中除了包含Acceptor和Worker外,還使用了Poller,處理流程以下圖所示(圖片來源:http://gearever.iteye.com/blog/1844203)。

 圖6-2

Acceptor接收socket後,不是直接使用Worker中的線程處理請求,而是先將請求發送給了Poller,而Poller是實現NIO的關鍵。Acceptor向Poller發送請求經過隊列實現,使用了典型的生產者-消費者模式。在Poller中,維護了一個Selector對象;當Poller從隊列中取出socket後,註冊到該Selector中;而後經過遍歷Selector,找出其中可讀的socket,並使用Worker中的線程處理相應請求。與BIO相似,Worker也能夠被自定義的線程池代替。

經過上述過程能夠看出,在NIoEndpoint處理請求的過程當中,不管是Acceptor接收socket,仍是線程處理請求,使用的仍然是阻塞方式;但在「讀取socket並交給Worker中的線程」的這個過程當中,使用非阻塞的NIO實現,這是NIO模式與BIO模式的最主要區別(其餘區別對性能影響較小,暫時略去不提)。而這個區別,在併發量較大的情形下能夠帶來Tomcat效率的顯著提高:

目前大多數HTTP請求使用的是長鏈接HTTP/1.1默認keep-alive爲true),而長鏈接意味着,一個TCP的socket在當前請求結束後,若是沒有新的請求到來,socket不會立馬釋放,而是等timeout後再釋放。若是使用BIO,「讀取socket並交給Worker中的線程」這個過程是阻塞的,也就意味着在socket等待下一個請求或等待釋放的過程當中,處理這個socket的工做線程會一直被佔用,沒法釋放;所以Tomcat能夠同時處理的socket數目不能超過最大線程數,性能受到了極大限制。而使用NIO,「讀取socket並交給Worker中的線程」這個過程是非阻塞的,當socket在等待下一個請求或等待釋放時,並不會佔用工做線程,所以Tomcat能夠同時處理的socket數目遠大於最大線程數,併發性能大大提升。


6.1.4 參數說明

我的理解, 可能有誤.

圖6-1所示, Tomcat在accept隊列中接收鏈接, 

1 正常狀況網絡請求從accpet隊列進入Tomcat, 佔用Tomcat的鏈接數(maxConnections爲可用鏈接的上限.)
得到Tomcat鏈接後就交由cpu線程來處理(tomcat能用的線程受硬件和maxThreads參數限制)
請求處理後Tomcat鏈接釋放, 對外進行數據響應.
2  如網絡請求進入Tomcat時, 佔用Tomcat的鏈接數已達上限(maxConnections ). 則請求進入accept隊列等待(隊列的長度收到tomcat的參數 acceptCount限制, 底層更受到操做系統的限制. acceptCount參數是tomcat server在操做系統的tcp accept隊列的大小限制設置的基礎上,在tomcat級別又再多作了一層限制。 TCP層面有兩個隊列:半鏈接隊列(syn隊列)與徹底鏈接隊列(accept隊列)。syn隊列的大小取決於max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog);accept隊列的大小取決於min(backlog, somaxconn),somaxconn是一個os級別的系統參數,而backlog的值能夠由咱們的應用程序(tomcat)去定義Tomcat的acceptCount參數對應被映射成backlog )
操做系統將請求進入accept隊列時發現隊列已滿, 則請求直接被拒絕. 

因此,  maxConnections表示有多少個socket鏈接到tomcat上。NIO模式下默認是10000。 maxThreads則是woker線程併發處理 請求的最大數.

Tomcat(應用服務器表明)的acceptor線程則負責從accept隊列中取出該Connection,而後交給工做線程去處理(讀取請求參數、處理邏輯、返回響應等等。若是該鏈接不是keep alived的話,即請求爲短鏈接,則關閉該鏈接,而後該工做線程釋放回線程池;若是是keep alived的話(長鏈接),則等待下一個數據包的到來直到keepAliveTimeout,而後關閉該鏈接釋放回線程池),而後本身接着去accept隊列取Connection。

雖然tomcat同時能夠處理的鏈接數目是maxConnections,但服務器中能夠同時接收的鏈接數爲maxConnections+acceptCount


  • acceptCount(最大排隊數)
accept隊列的長度;當accept隊列中鏈接的個數達到acceptCount時,隊列滿,進來的請求一概被拒絕。默認值是100。
acceptCount的大小要視狀況而定。若是設得過小,請求量上來的時候Client會收到read timeout或connection reset by peer;若是設得太大,請求會積壓在accept隊列得不到及時的處理,一樣會由於等待超時致使Client端返回read timeout。
  • maxConnections(最大鏈接數)
maxConnections表示有多少個socket鏈接到tomcat上( 接收和處理的最大鏈接數 )。對於BIO模式,一個線程只能處理一個連接,通常maxConnections取值與maxThreads相同(短鏈接請求完畢後會釋放tomcat鏈接, 長鏈接在請求處理後不佔工做線程,仍然佔用tomcat鏈接一段時間.)
不然Client的socket即便鏈接上了,可是都在tomcat的task queue裏頭,等待worker線程處理返回響應;對於NIO模式,一個線程同時處理多個連接,maxConnections應該配置得比maxThreads要大的多,默認狀況下是10000。
當Tomcat接收的鏈接數達到maxConnections時,Acceptor線程不會讀取accept隊列中的鏈接;這時accept隊列中的線程會一直阻塞着,直到Tomcat接收的鏈接數小於maxConnections。若是設置爲-1,則鏈接數不受限制。
默認值與鏈接器使用的協議有關:NIO的默認值是10000,APR/native的默認值是8192,而BIO的默認值爲maxThreads(若是配置了Executor,則默認值是Executor的maxThreads)。
在windows下,APR/native的maxConnections值會自動調整爲設置值如下最大的1024的整數倍;如設置爲2000,則最大值實際是1024。

  • maxThreads(處理線程的最大數量)
maxThreads指定Tomcat最大併發線程數量,即同一時刻Tomcat最多maxThreads個線程在處理客戶端的請求. 默認值是200(Tomcat7和8都是的)。若是該Connector綁定了Executor,這個值會被忽略,由於該Connector將使用綁定的Executor,而不是內置的線程池來執行任務。
maxThreads規定的是最大的線程數目,並非實際running的CPU數量;實際上,maxThreads的大小比CPU核心數量要大得多。這是由於,處理請求的線程真正用於計算的時間可能不多,大多數時間可能在阻塞,如等待數據庫返回數據、等待硬盤讀寫數據等。所以,在某一時刻,只有少數的線程真正的在使用物理CPU,大多數線程都在等待;所以線程數遠大於物理核心數纔是合理的。
換句話說,Tomcat經過使用比CPU核心數量多得多的線程數,可使CPU忙碌起來,大大提升CPU的利用率。
maxThreads的設置與應用的特色有關,也與服務器的CPU核心數量有關。對於計算密集型應用,maxThreads應該儘量設得小一些,讓線程佔用更多的CPU時間去處理邏輯;而對於IO密集型應用,maxThreads能夠設得稍大些,讓應用能夠處理更大的併發


延伸閱讀
Tomcat調優總結(Tomcat自身優化、Linux內核優化、JVM優化)

其餘

延伸閱讀: 偏流程性質的說明,以下:
相關說明並未深刻os,tomcat是如何處理請求,以及http長短鏈接的問題。
網站架構從0起步系列文章總目錄

參考

1 創建最主要的總體概念 經過 Tomcat的acceptCount與maxConnections  
2 簡單的對 acceptCount、maxConnections、maxThreads這幾個概念的簡單再說明 經過 tomcat的acceptCount與maxConnections  
3 詳細的完整補充: 見 詳解tomcat的鏈接數與線程池  對Nio、Bio、APR, acceptCount、maxConnections、maxThreads, 線程池Executor都有論述.
https://segmentfault.com/q/1010000016846975



相關文章
相關標籤/搜索