1. 程式人生 > >http與tcp面試題2

http與tcp面試題2

1.當你用瀏覽器開啟一個連結的時候,計算機做了哪些工作步驟

     (1)、解析域名。
     (2)、發起TCP的3次握手。
     (3)、建立TCP請求後發起HTTP請求。
     (4)、伺服器相應HTTP請求。
     (5)、瀏覽器得到HTML程式碼,進行解析和處理JSON資料,並請求HTML程式碼中的靜態資源(JS、CSS、圖片等)。
     (6)、瀏覽器對頁面進行渲染。
--------------------- ------------------------------------------------------------------------

當訪問一個域名地址時,瀏覽器首先要把域名解析成公網IP地址,這一步是通過DNS來解析,會訪問DNS服務商查詢域名對應的IP,查詢到後會把結果快取下來,後面對該域名的訪問就不再進行DNS解析,瀏覽器第一次開啟一個網站時會比較慢這個DNS初次解析是慢的原因之一。當然瀏覽器會先檢查快取中域名對應的IP有沒有,然後檢查本地的hosts有沒有配置該域名,都沒有的話才會去訪問DNS服務商。如果是內部網路需要通過閘道器才能上網的,這裡還可能會發生一次ARP廣播查詢閘道器機器。當同一子網內直接通過IP通訊時也需要ARP廣播來找到目的機器的Mac地址。

拿到IP之後,客戶端首先要通過TCP三次握手來建立連線, TCP協議會對請求資料包進行封裝並由IP協議進行傳輸,ICMP協議進行控制,中間會經過不同的路由器最終到達目的主機的網絡卡介面,這中間的過程我是講不明白,也不太清楚,省略。

說說TCP三次握手吧,TCP協議通過三次握手建立一個可靠的連線,TCP是IP的上層協議,反正IP層是並不知道什麼三次握手四次揮手,IP只管運輸資料。第一次握手:Client首先發送一個連線試探,ACK=0 表示確認號無效,SYN = 1 表示這是一個連線請求或連線接受報文,同時表示這個資料報不能攜帶資料,seq = x 表示Client自己的初始序號(seq = 0 就代表這是第0號包),這時候Client進入syn_sent狀態,表示客戶端等待伺服器的回覆。第二次握手:Server監聽到連線請求報文後,如同意建立連線,則向Client傳送確認。TCP報文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到對方下一個報文段的第一個資料位元組序號是x+1,同時表明x為止的所有資料都已正確收到(ack=1其實是ack=0+1,也就是期望客戶端的第1個包),seq = y 表示Server 自己的初始序號(seq=0就代表這是伺服器這邊發出的第0號包)。這時伺服器進入syn_rcvd,表示伺服器已經收到Client的連線請求,等待client的確認。第三次握手:Client收到確認後還需再次傳送確認,同時攜帶要傳送給Server的資料。ACK 置1 表示確認號ack= y + 1 有效(代表期望收到伺服器的第1個包),Client自己的序號seq= x + 1(表示這就是我的第1個包,相對於第0個包來說的),一旦收到Client的確認之後,這個TCP連線就進入Established狀態,完成三次握手,就可以發起http請求,傳送正常的請求資料包。TCP為什麼要進行三次握手呢,大致講就是為了確認雙方機器是否正常,網路通不通,通訊協議是否支援。

IP協議只認IP地址,TCP在IP之上添加了埠,通過埠來區分不同的應用程式。當然我們並不是直接操作TCP/IP的,它之上還抽象了一個Socket層,HTTP也是建立在Socket之上進行通訊。一個socket是由一個五元組來唯一標示的,即(協議,server_ip, server_port, client_ip, client_port)。只要該五元組中任何一個值不同,則其代表的socket就不同。Socket對外抽象出了bind,listen,accept以及send,write等幾個基本的操作。就跟常見的檔案操作一樣(在Linux看來,什麼都可以是檔案)。

服務端首先需要幫定埠並監聽請求 new ServerSocket(80).accept() ,這裡幫定了80埠後,其它應用程式就不能再使用該埠了,一個指定的埠號不能被多個程式共用。這其實是向TCP/IP協議棧聲明瞭其對80埠的佔有,以後,所有目標是80埠的TCP資料包都會轉發給該程式。客戶端會以一個隨機埠(大於1024並小於65535)向伺服器的WEB程式(如nginx)80埠發起TCP的連線請求,由accept接收。所謂accept函式,其實抽象的是TCP的連線建立過程,當客戶端有一個新的請求過來後,accept函式返回的新socket其實指代的是本次建立的連線,accept可以產生多個不同的socket,這個socket跟檔案控制代碼很相似,可以認為它是用來區分不同的連線請求然後回撥不同的處理程式。建立的socket中包含了客戶端的IP及Port、服務端的IP及Port,這其中服務端的IP及Port都是一樣的。服務端Socket雖然只佔用了一個埠,但accept建立的Socket在Linux中也是一種特殊的檔案,自然也受到Linux檔案描述符的限制。建立連線也並不是真在存在這樣一條連著的連線。

Socket在Linux上還涉及到epoll,它是Linux核心為處理大批量檔案描述符而作了改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被核心IO事件非同步喚醒而加入Ready佇列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得使用者空間程式有可能快取IO狀態,減少epoll_wait/epoll_pwait的呼叫,提高應用程式效率。

建立連線後就開始進行HTTP請求報文和響應報文流程,HTTP協議報文是有它特定的格式,關於報文格式一篇文章也講不下,我也不是很清楚所有報文內容。目前我們使用最流行是HTTP協議是1.1版本,它有更多的請求方法,更精細的快取控制,持久連線支援。一個WEB站點每天可能要接收到上百萬的使用者請求,為了提高系統的效率,HTTP 1.0規定瀏覽器與伺服器只保持短暫的連線,瀏覽器的每次請求都需要與伺服器建立一個TCP連線,伺服器完成請求處理後立即斷開TCP連線,伺服器不跟蹤每個客戶也不記錄過去的請求。但是,這也造成了一些效能上的缺陷。為了克服HTTP 1.0的這個缺陷,HTTP 1.1支援持久連線,在一個TCP連線上可以傳送多個HTTP請求和響應,減少了建立和關閉連線的消耗和延遲。一個包含有許多影象的網頁檔案的多個請求和應答可以在一個連線中傳輸,但每個單獨的網頁檔案的請求和應答仍然需要使用各自的連線。HTTP 1.1還允許客戶端不用等待上一次請求結果返回,就可以發出下一次請求,但伺服器端必須按照接收到客戶端請求的先後順序依次回送響應結果,以保證客戶端能夠區分出每次請求的響應內容,這樣也顯著地減少了整個下載過程所需要的時間。瀏覽器第一次訪問一個網頁,首先要建立一個連線需要三次握手,建立連線後也不是一股腦把所有請求都發出去,而是先發一個請求,成功返回後再發起兩次請求,然後再是四次請求,這個也是導致第一次訪問頁面比較慢的一個小原因。

請求到達伺服器之後,如果有Http Server,則由Http Server轉發請求,如果沒有就是直接請求Tomcat之類的應用伺服器,由它生成響應內容。響應的內容有可能是HTML,也有可能是JSON資料。在這其中也牽扯到負載均衡、叢集、不同級別的快取、訊息處理、資料庫操作等等。這裡面的東西夠寫N篇文章,此處省略。

瀏覽器拿到html檔案後,就開始解析其中的html程式碼,遇到js/css/image等靜態資源時,就向伺服器端去請求下載(會使用多執行緒下載,每個瀏覽器的執行緒數不一樣)。瀏覽器在請求靜態資源時(在未過期的情況下),向伺服器端發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果伺服器端返回304狀態碼(告訴瀏覽器伺服器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的快取檔案。

很多大型網站在一個頁面中會使用多個不同的域名,這裡的主要原因有:1、CDN快取更方便。2、突破瀏覽器併發限制,像地圖之類的需要大量併發下載圖片的站點,這個非常重要,另一個重要因素是節約主域名的連線數,說起來就是分流。3、Cookieless,節省頻寬,尤其是上行頻寬一般比下行要慢,像主站使用者的每次訪問,都會帶上自己的cookie,挺大的。假如twitter的圖片放在主站域名下,那麼使用者每次訪問圖片時,request header裡就會帶有自己的cookie,header裡的cookie還不能壓縮,而圖片是不需要知道使用者的cookie,所以這部分頻寬就白白浪費了。4、對於UGC的內容和主站隔離,防止不必要的安全問題(上傳js竊取主站cookie之類的),正是這個原因要求使用者內容的域名不是自己主站的子域名,而是一個完全獨立的第三方域名。5、資料做了劃分,甚至切到了不同的物理叢集,通過子域名來分流比較省事,這個在分系統的時候用的比較多。最後,多域名也不是越多越好,雖然伺服器端可以做泛解釋,瀏覽器做dns解釋也是耗時間,而且太多域名,如果要走https的話,還有要多買證書和部署的問題。

如果靜態資源使用了CDN,則會向CDN請求靜態資源。CDN即內容分佈網路(Content Delivery Network),它是構築在現有Internet上的一種先進的流量分配網路。其目的是通過現有的Internet中增加一層新的網路架構,將網站的內容釋出到最接近使用者的網路“邊緣”,使使用者可以就近取得所需的內容,提高使用者訪問網站的響應速度。

瀏覽器利用自己內部的工作機制,把請求到的靜態資源和html程式碼進行渲染,渲染之後呈現給使用者。自此一次完整的HTTP事務宣告完成。

來個題外話,伺服器單機最大連線數問題。TCP的埠數最大值為65535,但單個伺服器程式可承受最大連線數和這個沒有關係,因為服務端實際上只使用到了一個埠。Linux上連線併發數的限制有:可開啟檔案數限制、記憶體容量、CPU資源。上面也講到了一個連線就是一個Socket,一個Socket就是一個開啟著的檔案。每個連線都要消耗記憶體,在Linux Epoll下並不是一個連線對應一個執行緒,而是複用執行緒。但在Tomcat中一個連線對應一個作業系統執行緒,執行緒共享程序的堆空間,但每個執行緒都有自己的棧空間,一個棧要在核心區及使用者區分別各佔一塊記憶體。執行緒和程序一樣都是可以使用CPU分片時間,如果併發請求太高,則會導致過多的執行緒以及網路中斷來消耗CPU資源,這個時會產生大量的上下文切換(核心態及使用者態切換),這會使CPU大量消耗在任務排程上而不是實際的業務處理中。以及間接導致CPU快取命中下降及失效問題。也正是這個原因,所以一般都是一臺Nginx後面掛了一群Tomcat,Nginx程序個數一般都是CPU邏輯個數(或是CPU個數減1,為了和網路中斷錯開),避免上下文切換。

2說你知道的幾種HTTP響應碼。

      ☆ 200 OK:表示客戶端請求成功。
      ☆ 400 Bad Request 語義有誤,不能被當前伺服器理解。
      ☆ 401 Unauthorized 當前請求需要使用者驗證。
      ☆ 403 Forbidden 伺服器收到訊息,但是拒絕提供服務。
      ☆ 404 Not Found 請求資源不存在。
      ☆ 408 Request Timeout 請求超時,客戶端沒有在伺服器預備等待的時間內完成傳送。
      ☆ 500 Internal Server Error 伺服器發生不可預期的錯誤。
      ☆ 503 Server Unavailable 由於臨時的伺服器維護或過載,伺服器當前不能處理請求,此狀況知識臨時的,可恢復。