三次握手和四次揮手說(shuō)完了,還讓我手動(dòng)寫(xiě)個(gè)HTTP協(xié)議代碼
最近阿粉的同事們?cè)跍?zhǔn)備面試,其中也有收到offer的幾個(gè)不錯(cuò)的人,畢竟疫情穩(wěn)定了,而阿粉在電話面試的時(shí)候,被問(wèn)到關(guān)于HTTP協(xié)議的內(nèi)容的時(shí)候,卻顯得有點(diǎn)麻木了,為什么呢?因?yàn)樘茁诽盍?,讓阿粉猝不及防呀?/p>
面試官:你了解TCP/IP協(xié)議么?說(shuō)實(shí)話在阿粉聽(tīng)到這個(gè)問(wèn)題的時(shí)候,阿粉的第一想法就是,我回答了這個(gè)問(wèn)題,接下來(lái)肯定還有一個(gè)三次握手和四次揮手等著我,但是還是得回答呀,于是阿粉就開(kāi)始作答了。
阿粉開(kāi)始作答:TCP/IP協(xié)議雖然會(huì)放在一起說(shuō),但是他們其實(shí)呢是屬于兩個(gè)不同的協(xié)議。
- IP協(xié)議:IP協(xié)議實(shí)際上是用來(lái)查找地址的,而它對(duì)應(yīng)的層級(jí)也是網(wǎng)絡(luò)層,也可以稱(chēng)之為網(wǎng)際互聯(lián)層,區(qū)別不大,叫法不同而已。
- TCP協(xié)議:TCP協(xié)議是用來(lái)規(guī)范傳輸規(guī)則的,和IP協(xié)議是不同的,而它對(duì)應(yīng)的層級(jí)是傳輸層,而這樣的話,也就是IP去尋找地址,把所有的傳輸任務(wù)都交給TCP,而TCP這時(shí)候就相當(dāng)于一個(gè)快遞員的身份出現(xiàn)并且存在。
面試官:那你說(shuō)說(shuō)什么是三次握手,什么是四次揮手吧
1. 三次握手
大家看這個(gè)圖,圖是來(lái)自于百度搜索,而且百度上有各種各樣的圖,當(dāng)你看到圖的時(shí)候第一時(shí)間肯定是看不懂的,也就是只能通過(guò)這個(gè)畫(huà)的標(biāo)志的“線”來(lái)進(jìn)行分析,其實(shí)這僅僅只是一個(gè)方面。
那么我們就來(lái)根據(jù)圖來(lái)解析一下這個(gè)圖中都代表了什么意思,圖中存在著兩個(gè)序號(hào)和三個(gè)不同的標(biāo)志位其中有大小寫(xiě)容易混淆的呦。
序號(hào):
- seq:sequence number 的縮寫(xiě),直譯的話,序號(hào),對(duì)沒(méi)錯(cuò),它就是序號(hào),你沒(méi)有翻譯錯(cuò),相信自己,而這個(gè)seq表示的則是自己傳遞的序號(hào),TCP在傳輸?shù)臅r(shí)候,其中的每一個(gè)字節(jié),都會(huì)有一個(gè)序號(hào),發(fā)送數(shù)據(jù)的時(shí)候,會(huì)把第一個(gè)數(shù)據(jù)的第一個(gè)序號(hào)發(fā)送給對(duì)方,就是我們所看到的第一步,而接收的這一方面,會(huì)按照這個(gè)序號(hào)來(lái)檢查是否是一個(gè)連接完整的數(shù)據(jù),如果說(shuō)你數(shù)據(jù)是完整的,那么好,我們可以繼續(xù)下一步,如果你不是完整的,那就重新傳送唄,而這樣的話也能保證數(shù)據(jù)的完整性不被破壞。
- ack:注意,這是小寫(xiě)的ack,也就是acknoledgement number的縮寫(xiě),而他表示的是確認(rèn)號(hào),這個(gè)要和ACK(確認(rèn)位)進(jìn)行區(qū)分,接收端這時(shí)候用它來(lái)給發(fā)送端返回成功接收消息的數(shù)據(jù)信息,而這時(shí)候,它的值就是表明,我現(xiàn)在想接收下一個(gè)數(shù)據(jù)包了,而這個(gè)值就是下一個(gè)數(shù)據(jù)包的開(kāi)始的序號(hào),而這個(gè)ack所代表的的值的序號(hào)前面的數(shù)據(jù)都已經(jīng)接收成功了。
- ACK:確認(rèn)位,確認(rèn)位來(lái)了,只有當(dāng)ACK=1的時(shí)候ack才會(huì)起到自己應(yīng)該起的作用,而在我們第一次發(fā)起請(qǐng)求的時(shí)候,因?yàn)闆](méi)有需要我們確認(rèn)的接收的數(shù)據(jù),所以這個(gè)時(shí)候的ACK就是0,而正常通信的情況下,ACK就1.
- SYN:同步位,而同步位的作用就是用于建立連接時(shí)同步序號(hào),而剛連接的時(shí)候,說(shuō)ACK是0,那么ack就不起作用,這時(shí)候SYN就來(lái)說(shuō),你看沒(méi)我你們不行了把,要你們有何用,當(dāng)接收端接收到SYN=1的報(bào)文的時(shí)候,就會(huì)將ack設(shè)置為接收到的seq+1的值,這也是大家在看百度上提供的內(nèi)容的時(shí)候看到的,各種seq=k,ACK=k+1,這玩意就是這么來(lái)的,這時(shí)候ack的值就是根據(jù)SYN來(lái)直接設(shè)置的,這樣你才能正常的進(jìn)行傳輸,而SYN有時(shí)候會(huì)被面試官問(wèn)到為什么在前兩次握手的時(shí)候都是1呢?其實(shí)這是因?yàn)閭鬏敂?shù)據(jù)的雙方的ack都是要一個(gè)初始值的,不然你還怎么傳輸,還怎么玩。
- FIN:終止位,這個(gè)在本圖中,并沒(méi)有完全的體現(xiàn),在四次揮手的時(shí)候就能完全的體現(xiàn)出來(lái)了。而它則是用來(lái)在數(shù)據(jù)傳輸都完成之后來(lái)釋放連接的。
那么關(guān)于這個(gè)圖,我們?cè)趺唇o面試官說(shuō)呢?
(1) 第一次握手(SYN=1, seq=x):
客戶端發(fā)送一個(gè) TCP 的 SYN 標(biāo)志位置1的包,指明客戶端打算連接的服務(wù)器的端口,以及初始序號(hào) X,保存在包頭的序列號(hào)(Sequence Number)字段里。
發(fā)送完畢后,客戶端進(jìn)入 SYN_SEND 狀態(tài)。
(2) 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服務(wù)器發(fā)回確認(rèn)包(ACK)應(yīng)答。即 SYN 標(biāo)志位和 ACK 標(biāo)志位均為1。服務(wù)器端選擇自己 ISN 序列號(hào),放到 Seq 域里,同時(shí)將確認(rèn)序號(hào)(Acknowledgement Number)設(shè)置為客戶的 ISN 加1,即X+1。發(fā)送完畢后,服務(wù)器端進(jìn)入 SYN_RCVD 狀態(tài)。
(3) 第三次握手(ACK=1,ACKnum=y+1)
客戶端再次發(fā)送確認(rèn)包(ACK),SYN 標(biāo)志位為0,ACK 標(biāo)志位為1,并且把服務(wù)器發(fā)來(lái) ACK 的序號(hào)字段+1,放在確定字段中發(fā)送給對(duì)方,并且在數(shù)據(jù)段放寫(xiě)ISN的+1
發(fā)送完畢后,客戶端進(jìn)入 ESTABLISHED 狀態(tài),當(dāng)服務(wù)器端接收到這個(gè)包時(shí),也進(jìn)入 ESTABLISHED 狀態(tài),TCP 握手結(jié)束。
你如果這么說(shuō),面試官有可能還會(huì)問(wèn),你這也太官方了,能不能說(shuō)說(shuō)你的理解,那么你可以用一個(gè)實(shí)際上的例子來(lái)給他說(shuō)一下,
阿粉:雞丁,嘿,我是阿粉,你聽(tīng)的到我說(shuō)話么?
雞?。撼吵成?,聽(tīng)到了,除了你我還能認(rèn)識(shí)誰(shuí)。
阿粉:你聽(tīng)的到你還不趕緊回復(fù),怪不得你沒(méi)有女朋友呢。那我們?cè)倮^續(xù)交流一下吧。
而這三次的對(duì)話過(guò)程就是通俗的三次握手,期間對(duì)話三次,以此來(lái)確定兩個(gè)方向上的數(shù)據(jù)傳輸通道是否正常。
2. 四次揮手
那么四次揮手怎么來(lái)回答呢?
(1)第一次揮手(FIN=1,seq=x)
假設(shè)客戶端想要關(guān)閉連接,客戶端發(fā)送一個(gè) FIN 標(biāo)志位置為1的包,表示自己已經(jīng)沒(méi)有數(shù)據(jù)可以發(fā)送了,但是仍然可以接受數(shù)據(jù)。
發(fā)送完畢后,客戶端進(jìn)入 FIN_WAIT_1 狀態(tài)。
(2) 第二次揮手(ACK=1,ACKnum=x+1)
服務(wù)器端確認(rèn)客戶端的 FIN 包,發(fā)送一個(gè)確認(rèn)包,表明自己接受到了客戶端關(guān)閉連接的請(qǐng)求,但還沒(méi)有準(zhǔn)備好關(guān)閉連接。
發(fā)送完畢后,服務(wù)器端進(jìn)入 CLOSE_WAIT 狀態(tài),客戶端接收到這個(gè)確認(rèn)包之后,進(jìn)入 FIN_WAIT_2 狀態(tài),等待服務(wù)器端關(guān)閉連接。
(3) 第三次揮手(FIN=1,seq=y)
服務(wù)器端準(zhǔn)備好關(guān)閉連接時(shí),向客戶端發(fā)送結(jié)束連接請(qǐng)求,F(xiàn)IN 置為1。
發(fā)送完畢后,服務(wù)器端進(jìn)入 LAST_ACK 狀態(tài),等待來(lái)自客戶端的最后一個(gè)ACK。
(4) 第四次揮手(ACK=1,ACKnum=y+1)
客戶端接收到來(lái)自服務(wù)器端的關(guān)閉請(qǐng)求,發(fā)送一個(gè)確認(rèn)包,并進(jìn)入 TIME_WAIT狀態(tài),等待可能出現(xiàn)的要求重傳的 ACK 包。
服務(wù)器端接收到這個(gè)確認(rèn)包之后,關(guān)閉連接,進(jìn)入 CLOSED 狀態(tài)。
客戶端等待了某個(gè)固定時(shí)間(兩個(gè)最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,沒(méi)有收到服務(wù)器端的 ACK ,認(rèn)為服務(wù)器端已經(jīng)正常關(guān)閉連接,于是自己也關(guān)閉連接,進(jìn)入 CLOSED 狀態(tài)。兩次后會(huì)重傳直到超時(shí)。如果多了會(huì)有大量半鏈接阻塞隊(duì)列。
那么怎么去通俗的給面試官說(shuō)呢?
阿粉:雞丁呀,我要說(shuō)的都說(shuō)完了,你還有啥事么?
雞?。耗阏f(shuō)的我都明白了,但是別斷,我還有要囑咐你的,給我找女朋友的事情。
雞丁:xxxxx,我說(shuō)完了。
阿粉,行啦,別BB了,記住了,掛了把。
如果面試官問(wèn)你的時(shí)候,你這么回答的話,既有官方的解釋?zhuān)€有本身自己的理解,那么這個(gè)問(wèn)題就已經(jīng)算是差不多了,
而面試官顯然不可能會(huì)這么放過(guò)你,肯定再給你來(lái)個(gè)雷,為啥是三次握手,而是四次揮手呢?為啥不是三次呢?
這是因?yàn)榉?wù)端在LISTEN狀態(tài)下,收到建立連接請(qǐng)求的SYN報(bào)文后,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端。而關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方是否現(xiàn)在關(guān)閉發(fā)送數(shù)據(jù)通道,需要上層應(yīng)用來(lái)決定,因此,己方ACK和FIN一般都會(huì)分開(kāi)發(fā)送。所以這時(shí)候揮手的時(shí)候就是四次,而不再是三次了。
那么我們?cè)趺慈ナ謱?xiě)一個(gè)HTTP協(xié)議呢?代碼送上:
- public class Server {
- public static void main(String[] args) throws Exception{
- ServerSocketChannel ssc = ServerSocketChannel.open();
- ssc.socket().bind(new InetSocketAddress(8080));
- ssc.configureBlocking(false);
- Selector selector = Selector.open();
- ssc.register(selector, SelectionKey.OP_ACCEPT);
- while (true){
- if (selector.select(3000)==0){
- continue;
- }
- Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
- while (keyIterator.hasNext()){
- SelectionKey key = keyIterator.next();
- new Thread(new HttpHandler(key)).run();
- keyIterator.remove();
- }
- }
- }
- private static class HttpHandler implements Runnable{
- private int bufferSize = 1024;
- private String localCharset = "UTF-8";
- private SelectionKey key;
- public HttpHandler(SelectionKey key){
- this.key=key;
- }
- public void handleAccept()throws IOException{
- SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
- clientChannel.configureBlocking(false);
- clientChannel.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
- }
- @Override
- public void run() {
- try {
- if (key.isAcceptable()){
- handleAccept();
- }
- if (key.isReadable()){
- handleRead();
- }
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- public void handleRead() throws IOException{
- SocketChannel sc = (SocketChannel) key.channel();
- ByteBuffer buffer = (ByteBuffer) key.attachment();
- buffer.clear();
- if (sc.read(buffer)==-1){
- sc.close();
- }else {
- buffer.flip();
- String receiveString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
- String[] requestMessage = receiveString.split("\r\n");
- for (String s:requestMessage) {
- System.out.println(s);
- if (s.isEmpty()){
- break;
- }
- String[] firstLine = requestMessage[0].split(" ");
- System.out.println();
- System.out.println("Method:\t"+firstLine[0]);
- System.out.println("url:\t"+firstLine[1]);
- System.out.println("HTTP Version:\t"+firstLine[2]);
- System.out.println();
- StringBuffer sendString = new StringBuffer();
- sendString.append("HTTP/1.1 200 OK\r\n");
- sendString.append("Content-Type:text/html;charset="+localCharset+"\r\n");
- sendString.append("\r\n");
- sendString.append("<html><head><title>顯示報(bào)文</title></head><body>");
- sendString.append("接受到的請(qǐng)求報(bào)文是:<br/>");
- for (String s1:requestMessage) {
- sendString.append(s1+"<br/>");
- }
- sendString.append("</body></html>");
- buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
- sc.write(buffer);
- sc.close();
- }
- }
- }
- }
- }
這是一個(gè)簡(jiǎn)單的實(shí)現(xiàn),只是實(shí)現(xiàn)思路,并不是真正的處理請(qǐng)求,而大家也要注意設(shè)置Content-Type的類(lèi)型,不然容易出問(wèn)題的,畢竟長(zhǎng)度是有限制的。


2019-02-01 09:38:16




