1. 程式人生 > >面試問題整理之TCP/IP和網路程式設計

面試問題整理之TCP/IP和網路程式設計

本文為本人面試問題中關於TCP/IP和網路程式設計的整理彙總。

(1)常見問題

1.TCP和UDP的區別?

TCP面向連線(如打電話要先撥號建立連線);UDP是無連線的,即傳送資料之前不需要建立連線

TCP提供可靠的服務。也就是說,通過TCP連線傳送的資料,無差錯,不丟失,不重複,且按序到達; UDP盡最大努力交付,即不保證可靠交付

TCP面向位元組流,實際上是TCP把資料看成一連串無結構的位元組流;UDP是面向報文的,
UDP沒有擁塞控制,因此網路出現擁塞不會使源主機的傳送速率降低

每一條TCP連線只能是點到點的;UDP支援一對一,一對多,多對一和多對多的互動通訊

TCP首部開銷20位元組;UDP的首部開銷小,只有8個位元組

TCP的邏輯通訊通道是全雙工的可靠通道,UDP則是不可靠通道

2.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狀態,完成三次握手。

連線建立後,客戶端和伺服器就可以開始進行資料傳輸了。

為什麼客戶端需要再發送一次確認?

client發出的第一個連線請求報文段並沒有丟失,而是在某個網路結點長時間的滯留了,以致延誤到連線釋放以後的某個時間才到達server。本來這是一個早已失效的報文段。但server收到此失效的連線請求報文段後,就誤認為是client再次發出的一個新的連線請求。於是就向client發出確認報文段,同意建立連線。假設不採用三次握手,那麼只要server發出確認,新的連線就建立了。由於現在client並沒有發出建立連線的請求,因此不會理睬server的確認,也不會向server傳送資料。但server卻以為新的運輸連線已經建立,並一直等待client發來資料。這樣,server的很多資源就白白浪費掉了。採用三次握手的辦法可以防止上述現象發生。例如剛才那種情況,client不會向server的確認發出確認。server由於收不到確認,就知道client並沒有要求建立連線。

3.TCP的四次揮手協議?

(1)客戶端A傳送一個FIN,用來關閉客戶A到伺服器B的資料傳送。

(2)伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將佔用一個序號。

(3)伺服器B關閉與客戶端A的連線,傳送一個FIN給客戶端A。

(4)客戶端A發回ACK報文確認,並將確認序號設定為收到序號加1。

為什麼TCP連線是三次,揮手確是四次?

在TCP連線中,伺服器端的SYN和ACK向客戶端傳送是一次性發送的,而在斷開連線的過程中,B端向A端傳送的ACK和FIN是是分兩次傳送的。因為在B端接收到A端的FIN後,B端可能還有資料要傳輸,所以先發送ACK,等B端處理完自己的事情後就可以傳送FIN斷開連線了。

為什麼在第四次揮手後會有2個MSL的延時?

MSL是Maximum Segment Lifetime,最大報文段生存時間,2個MSL是報文段傳送和接收的最長時間。
假定網路不可靠,那麼第四次傳送的ACK可能丟失,即B端無法收到這個ACK,如果B端收不到這個確認ACK,B端會定時向A端重複傳送FIN,直到B端收到A的確認ACK。所以這個2MSL就是用來處理這個可能丟失的ACK的。而且能確保下一個新的連線中沒有這個舊連線的報文。

4.Linux程序間的通訊方式?

linux下程序間通訊的幾種主要手段:
1. 管道(Pipe)及有名管道(named pipe):管道可用於具有親緣關係程序間的通訊,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係程序間的通訊;
2. 訊號(Signal):訊號是比較複雜的通訊方式,用於通知接受程序有某種事件生,除了用於程序間通訊外,程序還可以傳送訊號給程序本身;linux除了支援Unix早期 訊號語義函式sigal外,還支援語義符合Posix.1標準的訊號函式sigaction(實際上, 該函式是基於BSD的,BSD為了實現可靠訊號機制,又能夠統一對外介面,sigaction函式重新實現了signal函式);
3. 報文(Message)佇列(訊息佇列):訊息佇列是訊息的連結表,包括Posix訊息佇列system V訊息佇列。有足夠許可權的程序可以向佇列中新增訊息,被賦予讀許可權的程序則可以讀走佇列中的訊息。訊息佇列克服了訊號承載資訊量少,管道只能承載無格式位元組流以及緩衝區大小受限等缺點;
4. 共享記憶體:使得多個程序可以訪問同一塊記憶體空間,是最快的可用IPC形式。是針其他通訊機制執行效率較低設計的。往往與其它通訊機制,如訊號量結合使用, 來達到程序間的同步及互斥。
5. 訊號量(semaphore):主要作為程序間以及同一程序不同執行緒之間的同步手段。
6. 套接字(Socket):更為一般的程序間通訊機制,可用於不同機器之間的程序間通訊。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix 系統上:Linux和System V的變種都支援套接字。

5.用UDP協議通訊時怎樣得知目標機是否獲得了資料包?

在UDP之上自定義一個通訊協議:每個資料包中包含一個唯一標識,可以用編號也可以用時間;接收端收到資料包後回發一個數據包,包含收到的這個唯一標識;傳送端在預定時間內沒有收到回執則自動重發,重發一定次數後仍未收到回執則認為傳送失敗。

6.列出常見的訊號,訊號怎麼處理?

SIGHUP:本訊號在使用者終端結束時發出,通常是在終端的控制程序結束時,通知同一會話期內的各個作業,這時他們與控制終端不在關聯。比如,登入linux時,系統會自動分配給登入使用者一個控制終端,在這個終端執行的所有程式,包括前臺和後臺程序組,一般都屬於同一個會話。當用戶退出時,所有程序組都將收到該訊號,這個訊號的預設操作是終止程序。此外對於與終端脫離關係的守護程序,這個訊號用於通知它重新讀取配置檔案。
SIGINT:程式終止訊號。當用戶按下CRTL+C時通知前臺程序組終止程序。
SIGQUIT:Ctrl+\控制,程序收到該訊號退出時會產生core檔案,類似於程式錯誤訊號。
SIGILL:執行了非法指令。通常是因為可執行檔案本身出現錯誤,或者資料段、堆疊溢位時也有可能產生這個訊號。
SIGTRAP:由斷點指令或其他陷進指令產生,由偵錯程式使用。
SIGABRT:呼叫abort函式產生,將會使程式非正常結束。
SIGBUS:非法地址。包括記憶體地址對齊出錯。比如訪問一個4個字長的整數,但其地址不是4的倍數。它與SIGSEGV的區別在於後者是由於對合法地址的非法訪問觸發。
SIGFPE:發生致命的算術運算錯誤。
SIGKILL:用來立即結束程式的執行。
SIGUSR1:留給使用者使用,使用者可自定義。
SIGSEGV:訪問未分配給使用者的記憶體區。或操作沒有許可權的區域。
SIGUSR2:留給使用者使用,使用者可自定義。
SIGPIPE:管道破裂訊號。當對一個讀程序已經執行結束的管道執行寫操作時產生。
SIGALRM:時鐘定時訊號。由alarm函式設定的時間終止時產生。
SIGTERM:程式結束訊號。shell使用kill產生該訊號,當結束不了該程序,嘗試使用SIGKILL訊號。
SIGSTKFLT:堆疊錯誤。
SIGCHLD:子程序結束,父程序會收到。如果子程序結束時父程序不等待或不處理該訊號,子程序會變成殭屍程序。
SIGCONT:讓一個停止的程序繼續執行。
SIGSTOP:停止程序執行。暫停執行。
SIGTSTP:停止執行,可以被忽略。Ctrl+z。
SIGTTIN:當後臺程序需要從終端接收資料時,所有程序會收到該訊號,暫停執行。
SIGTTOU:與SIGTTIN類似,但在寫終端時產生。
SIGURG:套接字上出現緊急情況時產生。
SIGXCPU:超過CPU時間資源限制時產生的訊號。
SIGXFSZ:當程序企圖擴大檔案以至於超過檔案大小資源限制時產生。
SIGVTALRM:虛擬使用訊號。計算的是程序佔用CPU呼叫的時間。
SIGPROF:包括程序使用CPU的時間以及系統呼叫的時間。
SIGWINCH:視窗大小改變時。
SIGIO:檔案描述符準備就緒,表示可以進行輸入輸出操作。
SIGPWR:電源失效訊號。
SIGSYS:非法的系統呼叫。

7.守護程序?

守護程序(Daemon)是執行在後臺的一種特殊程序。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。守護程序是一種很有用的程序。Linux的大多數伺服器就是用守護程序實現的。比如,Internet伺服器inetd,Web伺服器httpd等。同時,守護程序完成許多系統任務。比如,作業規劃程序crond,列印程序lpd等。

8.select伺服器端的實現,select與epoll的區別?

select的幾大缺點:
1. 每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大
2. 同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
3. select支援的檔案描述符數量太小了,預設是1024

epoll解決了以上確定:
1. 支援的FD上限是最大可以開啟檔案的數目;
2. IO效率不隨FD數目增加而線性下降,只會對”活躍”的socket進行操作;
3. epoll在epoll_ctl函式中,每次註冊新的事件到epoll控制代碼中時,會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。
4. 使用mmap加速核心與使用者空間的訊息傳遞.

9.什麼是滑動視窗?

  滑動視窗協議是用來改善吞吐量的一種技術,即容許傳送方在接收任何應答之前傳送附加的包。接收方告訴傳送方在某一時刻能送多少包(稱視窗尺寸)。

  TCP中採用滑動視窗來進行傳輸控制,滑動視窗的大小意味著接收方還有多大的緩衝區可以用於接收資料。傳送方可以通過滑動視窗的大小來確定應該傳送多少位元組的資料。當滑動視窗為0時,傳送方一般不能再發送資料報,但有兩種情況除外,一種情況是可以傳送緊急資料,例如,允許使用者終止在遠端機上的執行程序。另一種情況是傳送方可以傳送一個1位元組的資料報來通知接收方重新宣告它希望接收的下一位元組及傳送方的滑動視窗大小。

  滑動視窗機制為端到端裝置間的資料傳輸提供了可靠的流量控制機制。然而,它只能在源端裝置和目的端裝置起作用,當網路中間裝置(例如路由器等)發生擁塞時,滑動視窗機制將不起作用。

10.connect會阻塞,怎麼解決?

  • 非阻塞

步驟1: 設定非阻塞,啟動連線

實現非阻塞 connect ,首先用Fcntl()函式把 sockfd 設定成非阻塞的。這樣呼叫connect 可以立刻返回,根據返回值和 errno 處理三種情況:

(1) 如果返回 0,表示 connect 成功。

(2) 如果返回值小於 0, errno 為 EINPROGRESS, 表示連線
建立已經啟動但是尚未完成。這是期望的結果,不是真正的錯誤。

(3) 如果返回值小於0,errno 不是 EINPROGRESS,則連接出錯了。

步驟2:判斷可讀和可寫

然後把 sockfd 加入 select 的讀寫監聽集合,通過 select 判斷 sockfd 是否可寫,

(1) 如果連線建立好了,那麼 sockfd 是可寫的

(2) 如果連線發生錯誤,sockfd 也是可讀和可寫的。

步驟3:使用 getsockopt 函式檢查錯誤

getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len)

在 sockfd 都是可讀和可寫的情況下,我們使用 getsockopt 來檢查連線是否出錯。

步驟4:重新將套接字設定為阻塞

  • 用訊號設定超時

定義訊號處理函式:


sigset(SIGALRM, u_alarm_handler);
alarm(2);
code = connect(socket_fd, (struct sockaddr*)&socket_st, sizeof(struct sockaddr_in));
alarm(0);
sigrelse(SIGALRM);

  首先定義一箇中斷訊號處理函式u_alarm_handler,用於超時後的報警處理,然後定義一個2秒的定時器,執行connect,當系統connect成功,則系統正常執行下去;如果connect不成功阻塞在這裡,則超過定義的2秒後,系統會產生一個訊號,觸發執行u_alarm_handler函式,當執行完u_alarm_handler後,程式將繼續從connect的下面一行執行下去。

其中,處理函式可以如下定義,也可以加入更多的錯誤處理。

void u_alarm_handler()
{
}

11.TCP的connect函式與UDP的connect函式區別?

  在網路程式設計中,connect函式通常用於客戶端建立tcp連線。tcp連線的建立實際上就是三次“握手”的過程。

  udp協議提供的是面向非連線的服務,通訊雙方不需要建立連線。一方只需要建立好套接字,並顯式或由系統繫結地址和埠號後就可以傳送/接收資料包。和tcp不同的是,使用udp協議的資料報套接字(SOCK_DGRAM)並不限定唯一的通訊方。既可以傳送(sendto)資料給任意的接受方,也可以從任意的傳送方接收(recvfrom)資料。

   如果希望為一個數據報套接字指定唯一的通訊方時,可以使用connect來實現這一功能。需要注意的是,在資料報套接字上使用connect並不是建立連線,不存在“握手”的過程。僅僅是為這個套接字指定一個通訊方,一旦指定了對方的地址,就可以通過send/recv來發送/接收資料了。而且可以在這個資料報套接字上多次呼叫connect函式來指定不同的通訊方。
在udp中使用connect的方法和tcp中類似,只需在建立套接字時,把套接字的型別由SOCK_STREAM換成SOCK_DGRAM即可。

14.tcp頭多少位元組?哪些欄位?

15.socket什麼情況下可讀?

套接字準備好讀的條件:

a)該套接字接受緩衝區中的資料位元組數大於等於套接字接受緩衝區低水位標記的當前大小。對這樣的套接字執行讀操作不會阻塞並將返回一個大於0的值(也就是返回準備好讀入的資料)。我們可以使用SO_RCVLOWAT套接字選項設定該套接字的低水位標記。對於tcp和udp套接字而言,其預設值為1。

b)該套接字的讀半部關閉(也就是接受了FIN的tcp連線)。對這樣的套接字的讀操作將不阻塞並返回0.(也就是返回EOF)

c)該套接字是一個監聽套接字且已完成的連線數不為0。對這樣的套接字的accept通常不阻塞。

d)其上有一個套接字錯誤待處理。對這樣的套接字的讀操作將不阻塞並返回-1(也就是返回一個錯誤),同時把errno設定成確切的錯誤條件。這樣待處理錯誤(pending error)也可以通過指定SO_ERROR套接字選項呼叫getsockopt獲取並清除。

套接字準備好寫的條件:

a)該套接字傳送緩衝區中的可用空間位元組數大於等於套接字傳送緩衝區低水位標記的當前大小,並且或者該套接字已連線,或者該套接字不需要連線(如udp套接字)。這意味著如果我們把這樣的套接字設定成非阻塞,寫操作將不阻塞並返回一個正值(例如由傳輸層接受的位元組數)。我們可以使用SO_SNDLOWAT套接字選項來設定該套接字的低水位標記。對於tcp和udp而言,其預設值通常為2048。

b)該連線的寫半部關閉。對這樣的套接字的寫操作將產生SIGPIPE訊號。

c) 使用非阻塞connect的套接字已建立連線,或者connect已經以失敗告終。

d) 其上有一個套接字錯誤待處理。對這樣的套接字的寫操作將不阻塞並返回-1(也就是返回一個錯誤),同時把errno設定成確切的錯誤條件。這些待處理的錯誤也可以通過指定SO_ERROR套接字選項呼叫getsockopt獲取並清除。

16.寫一個c程式辨別系統是大端or小端位元組序。

大端模式,是指資料的高位元組儲存在記憶體的低地址中,而資料的低位元組儲存在記憶體的高地址中。
小端模式,是指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在記憶體的低地址中。

#include <stdio.h>
int main(int argc, char* argv[]){
    union {
        short s;
        char c[sizeof(short)];
    } un;

    un.s = 0x0102;  // 使un為2位元組,一節為1,一節為2

    if (sizeof(short) == 2) {
        if (un.c[0] == 1 && un.c[1] == 2) printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1) printf("little-endian\n");
        else printf("unknown\n");
    } else printf("sizeof(short) = %d\n", sizeof(short));
    return 0;
}

17.路由表的網段怎麼儲存、查詢

儲存:
根據RIP協議報文(包含目的網路,距離以及下一跳),若目的網路在原路由表中不存在,則直接加入路由表中;若存在,且距離比原路由表比較小,則更新距離,否則忽略。

查詢:
路由表中有目的地址網路,子網掩碼,下一跳這些資料;
先從資料包中獲取目的地址,讓該地址與路由表中的子網掩碼逐個進行與操作,若結果和子網掩碼對應的目的地址相匹配,則從下一跳指向的埠進行轉發,否則查詢下一個路由表項;

18.伺服器和客戶端都是怎麼工作的(從建立socket到工作的流程)

TCP:
伺服器端:socket(SOCK_STREAM)、bind、listen、accept
客戶端: socket(SOCK_STREAM)、connect

UDP:
伺服器端:socket(SOCK_DGRAM)、bind
客戶端:socket(SOCK_DGRAM)

19.listen的兩個引數分別是什麼

int listen(int sockfd, int backlog);

backlog是為sockfd套接字維護的 未完成連線(SYN-RECV) 和 已完成連線(ESTAVBLISHED) 兩個佇列的大小之和。

20.當併發量很大時,可以進行哪些處理解決這個問題

伺服器叢集;

I/O複用

多程序:(單伺服器)
收到連線就fork一個子程序處理連線;
建立程序池。

多執行緒:(單伺服器)
收到連線就建立一個執行緒處理連線;
建立執行緒池。

21.如果select在監聽的時候,描述符對端被關閉了會發生什麼

如果有客戶端連線請求,select函式將監聽socket描述符設定為可讀。注意:如果監聽socket為阻塞模式,那麼,當使用select進行多路分離時,可能造成select返回可讀但是呼叫accept會被阻塞住的情況,原因是在呼叫accept之前客戶端可能主動關閉連線或者傳送RST異常關閉連線.

22.send、recv都是阻塞的,怎麼控制在一定時間內完成send、recv,如果超時就不執行,怎麼跟select聯絡起來

套接字超時:
1. 呼叫alarm(),超時時產生SIGALRM訊號
2. 在select中阻塞等待IO,select內建定時器
3. 使用SO_RCVTIMEO和SO_SNDTIMEO套接字選項

對於recv,在select的讀檔案描述符集引數中開啟對應的描述符的位,然後把select內建的定時器設為想要的時間就行;同理對於send,則在寫檔案描述符集中開啟對應的位。

23.epoll底層使用了紅黑樹,說一下紅黑樹的特點和優點

紅黑樹能夠以O(log2 n)的時間複雜度進行搜尋、插入、刪除操作。

滿足下列條件的二叉搜尋樹是紅黑樹:
* 每個結點要麼是“紅色”,要麼是“黑色”
* 所有的葉結點都是空結點,並且是“黑色”的
* 如果一個結點是“紅色”的,那麼它的兩個子結點都是“黑色”的
* 如果結點是黑色的,那麼它的子節點可以是紅色或者是黑色的
* 結點到其子孫結點的每條簡單路徑都包含相同數目的“黑色”結點
* 根結點永遠是“黑色”的

24.dns怎麼解析的?

主機通過ISP接入了網際網路,那麼ISP就會分配一個DNS伺服器;
主機向ISP DNS發起查詢www.baidu.com請求;
ISP DNS收拿到請求後,先檢查一下自己的快取中有沒有這個地址,有的話就直接返回,如果快取中沒有的話,ISP DNS會從配置檔案裡面讀取13個根域名伺服器的地址。並向其中一臺發起請求。
根伺服器拿到這個請求後,知道他是com.這個頂級域名下的,所以就會返回com域中的NS記錄;
ISP DNS向其中一臺再次發起請求,com域的伺服器發現你這請求是baidu.com這個域的,一查發現了這個域的NS,那我就返回給你,你再去查;
ISP DNS不厭其煩的再次向baidu.com這個域的權威伺服器發起請求,baidu.com收到之後,查了下有www的這臺主機,就把這個IP返回給你了;
然後ISPDNS拿到了之後,將其返回給了客戶端,並且把這個儲存在快取記憶體中。

25.ipv4與ipv6:

一、==IPv6具有更大的地址空間==。IPv4中規定IP地址長度為32,最大地址個數為2^32;而IPv6中IP地址的長度為128,即最大地址個數為2^128。與32位地址空間相比,其地址空間增加了2^128-2^32個。

二、==IPv6使用更小的路由表==。IPv6的地址分配一開始就遵循聚類(Aggregation)的原則,這使得路由器能==在路由表中用一條記錄(Entry)表示一片子網,大大減小了路由器中路由表的長度,提高了路由器轉發資料包的速度。==

三、IPv6增加了增強的組播(Multicast)支援以及對流的控制(Flow Control),這使得網路上的多媒體應用有了長足發展的機會,為服務質量(QoS,Quality of Service)控制提供了良好的網路平臺。

四、IPv6加入了對自動配置(Auto Configuration)的支援。這是對DHCP協議的改進和擴充套件,使得網路(尤其是區域網)的管理更加方便和快捷。

五、IPv6具有更高的安全性。==在使用IPv6網路中使用者可以對網路層的資料進行加密並對IP報文進行校驗,在IPV6中的加密與鑑別選項提供了分組的保密性與完整性。極大的增強了網路的安全性。==

六、允許擴充。如果新的技術或應用需要時,IPV6允許協議進行擴充。

七、更好的頭部格式。IPV6使用新的頭部格式,其選項與基本頭部分開,如果需要,可將選項插入到基本頭部與上層資料之間。這就簡化和加速了路由選擇過程,因為大多數的選項不需要由路由選擇。

八、新的選項。IPV6有一些新的選項來實現附加的功能。

(2)知識補充

通訊的真正端點不是主機而是主機中的程序。端到端之間的通訊是應用程序之間的通訊。

伺服器端使用的埠號: 熟知埠號 (0~1023)和登記埠號 (1024~49151)
客戶端使用的埠號:49152~65535

使用者資料報協議UDP

UDP主要特點 :
無連線 傳送前不需要建立連線
盡最大努力交付
面向報文 : 應用層交下來的報文直接加上UDP頭部就往IP層扔, 不合並也不拆分
沒有擁塞控制
支援一對一, 一對多, 多對一和多對多的互動通訊
首部開銷小, 只有8個位元組

傳輸控制協議TCP

TCP主要特點 :
面向連線的運輸層協議,使用TCP協議前,必須先建立連線。
每一條TCP連線只能有2個端點, TCP是點對點的。
提供可靠交付,無差錯,不丟失,不重複,並且按序到達。
全雙工通訊,TCP連線的兩端都設有傳送快取和接收快取,用來臨時存放雙向通訊的資料。
面向位元組流,TCP把應用程式交下來的資料看成僅僅是一連串的無結構位元組流。

源埠和目的埠 :

同UDP埠作用
序號seq : 本報文段的資料的第一個位元組的序號
確認號ack(注意和大寫ACK區分) : 期望收到對方下一個報文段的第一個資料位元組的序號
若確認號 = N, 則表明 : 到序號N-1為止的所有資料都已正常收到
資料偏移 : TCP報文段的首部長度
保留 : 以後用, 目前為0
緊急URG : 若URG = 1時, 說明緊急指標欄位有效, 告訴系統這是緊急資料, 應儘快傳送. 例如突然要中斷傳送
確認ACK : ACK = 1時確認號才有效, ACK = 0時確認號無效. TCP規定, 連線建立後所有傳送的報文段都必須把ACK置1
推送PSH : 若PSH = 1, 則接收方收到報文段之後不再等到整個快取滿而是直接向上交付
復位RST : 當RST = 1, 說明TCP連線有嚴重錯誤, 必須釋放連線再重連
同步SYN : 在連線建立時用來同步序號. 當SYN = 1, ACK = 0時表明這是一個連線請求報文段, 對方若同意建立連線, 則在響應的報文段中置SYN = 1, ACK = 1
終止FIN : 當FIN = 1, 表明此報文段的傳送方資料已傳送完畢, 並要求釋放連線
視窗 : 告訴對方 : 從本報文段首部中的確認號算起, 接收方目前允許對方傳送的資料量. 這是作為接收方讓傳送方設定其傳送視窗的依據
檢驗和 : 同UDP, 檢驗首部和資料部分
緊急指標 : 當URG = 1時有效, 指出緊急資料的末尾在報文段的位置
選項 : 最大可40位元組, 沒有則為0。
a. 視窗擴大:視窗欄位長度為16位,因此視窗最大為64K位元組,視窗擴大將視窗值擴大至(16+S)位,S最大值規定是14.
b. 時間戳:用於計算RTT,也用於防止序號繞回。
c. 選擇確認(SACK):選擇確認某個連續的位元組塊,而不用只確認按序到達的塊。
最大報文段長度MSS(Maximum Segment Size) : 每一個TCP報文段中資料欄位的最大長度, 若不填寫則為預設的536位元組.因此所有在因特網上的主機都應該能接受的報文段長度是536+20(TCP首部長度)=556位元組。

TCP的連線

每一條TCP連線唯一地被通訊兩端的兩個端點(socket)所確定. 即 :
TCP連線 ::= {socket1, socket2} = {(IP1 : port1), (IP2 : port2)}

可靠傳輸的工作原理

1、停止等待協議:
(1)停止等待協議:每傳送完一個分組就停止傳送,等待對方確認。收到確認後再發送下一個分組。
無差錯情況:收到確認再發送
出現差錯:傳送方超過一段時間仍沒有收到確認就會重新發送(超時重傳)。因此傳送完必須保留分組副本,分組和確認分組都必須編號,超時計時器應該比平均往返時間長一點。
確認丟失和確認遲到:接收方如果重複收到某個分組,就說明確認資訊丟失了。此時接收方會丟棄重複分組並向傳送方再次傳送確認。傳送方重複收到確認,則把重複的確認丟棄就行。
(2)連續ARQ協議:傳送方每收到一個按序到達的確認,就把傳送視窗向前移動一個分組的位置。

TCP的資料編號與確認

  TCP將所要傳送的整個報文(這可能包括許多個報文段)看成是一個個位元組組成的資料流,並使每一個位元組對應於一個序號。TCP的確認是對接收到的資料的最高序號(即收到的資料流中的最後一個序號)表示確認。但接收端返回的確認序號是已收到的資料的最高序號加1。也就是說,確認序號表示接收端期望下次收到的資料中的第一個資料位元組的序號。

在傳送端,TCP是怎樣決定傳送個報文段的時機呢

  TCP有三種基本機制來控制報文段的傳送。
  第一種機制是TCP維持一個變數,它等於最大報文段長度MSS,只要傳送快取從傳送程序得到的資料達到MSS位元組時,就組裝成—個TCP報文段,然後傳送出去。
  第二種機制是傳送端的應用程序指明要求傳送報文段,即TCP支援的推送(push)操作。
  第三種機制是傳送端的一個計時器時間到了,這時就把當前已有的快取資料裝入報文段傳送出去。

TCP流量控制:

  接受方告訴傳送方自己的接受視窗大小,傳送方調整自己的傳送視窗大小。視窗以位元組為單位。
  在TCP的實現中廣泛使用Nagle演算法:Nagle演算法就是為了儘可能傳送大塊資料,避免網路中充斥著許多小資料塊。若傳送端應用程序將欲傳送的資料逐個位元組地達到傳送端的TCP快取,則傳送端就將第一個字元(—個字元的長度是一個位元組)傳送出去,將後面到達的字元將都快取起來。當接收端收到對第一個字元的確認後,再將快取中的所有字元裝成一個報文段傳送出去,同時繼續對隨後到達的字元進行快取。只有在收到對前一個報文段的確認時才繼續傳送下一個報文段。當字元到達較快而網路速率較慢時,用這樣的方法可明顯的減少所用的網路頻寬,演算法還規定,當到達的字元已達到視窗大小的一半或己達到報文段的最大長度時,就立即傳送一個報文段。

Nagle演算法的規則:

(1)如果包長度達到MSS,則允許傳送;
(2)如果該包含有FIN,則允許傳送;
(3)設定了TCP_NODELAY選項,則允許傳送;
(4)未設定TCP_CORK選項時,若所有發出的小資料包(長度小於MSS)均被確認,則允許傳送;
(5)上述條件都未滿足,但發生了超時(一般為200ms),則立即傳送。

糊塗視窗綜合症(silly window syndrome)

  有時也會使TCP的效能變壞。
  設想這種情況:接收端的快取已滿,而互動的應用程序一次只從快取中讀取一個字元(這樣就在快取產生1個位元組的空位,然後向傳送端傳送確認,並將視窗設定為1個位元組(但傳送的資料報是40位元組長)。接著,傳送端又傳來1個字元(但發來的IP資料報是41位元組長。接收端發回確認,仍然將視窗設定為一個位元組。這樣進行下去,網路的效率將會很低。
  要解決這個問題,可讓接收端等待一段時間,使得或者快取已能有足夠的空間容納—個最長的報文段,或者快取已有一半的中間處於空的狀態。只要出現這兩種情況之一,就發出確認報文,並向傳送端通知當前的視窗大小。此外,傳送端也不要傳送太小的報文段,而是將數掘積累成足夠大的報文段,或達到接收端快取的空間的—半大小。
  上述兩種方法(nagle和糊塗視窗)可配合使用。使得在傳送端不傳送很小的報文段的同時,接收端也不要在快取剛剛有了一點小的空位置就急忙將一個很小的視窗大小通知給傳送端。

擁塞控制

  若對網路中某一資源的需求超過了該資源所能提供的可用部分,網路的效能就要變壞。這種情況叫做擁塞。所謂擁塞控制就是防止過多的資料注入到網路中,這樣可以使網路中的路由器或鏈路不致過載。擁塞控制是一個全域性性過程,設計所有主機,所有的路由器。而流量控制是個端到端的問題。
  四種擁塞控制的演算法:慢開始、擁塞避免、快重傳、快恢復
  慢開始和擁塞避免(以下cwnd的大小單位都是報文段)
  傳送方維持一個擁塞視窗cwnd, 傳送方讓傳送視窗 等於 擁塞視窗和 接收方 接收視窗的最小值。
  傳送方控制擁塞視窗的原則是:只要沒有出現擁塞,擁塞視窗就再增大一些,以便把更多的分組傳送出去。但只要網路出現擁塞,擁塞視窗就減小一些,以減少注入到網路中的分組數。
  慢開始 : 開始傳送資料時,先探測一下,有小到大逐漸增大發送視窗。cwnd = 1, 然後每經過一個傳輸輪次就翻倍
  擁塞避免 : 讓cwnd緩慢增大, 每經過一個傳輸輪次就+1
  慢開始門限ssthresh : 只要傳送方判斷網路出現擁塞(根據就是沒有按時收到確認),就要把慢開始門限ssthresh設定為出現擁塞時的傳送方視窗值的一般,然後把cwnd重新設定為1
  cwnd < ssthresh, 使用慢開始演算法
  cwnd > ssthresh, 使用擁塞避免演算法
  cwnd = ssthresh, 隨意
  快重傳和快恢復
  快重傳 : 接收方及時傳送確認, 而傳送方只要一連收到三個重複確認, 馬上重傳而不等待重傳計時器。(需要明確的是確認指的是確認收到的有序的最大分節序號)由於儘早重傳未被確認的報文段,整的網路的吞吐量提高20%
  快恢復 : 當傳送方一連收到三個重複確認時, ssthresh減半, cwnd設為ssthresh,然後執行擁塞避免演算法。

TCP的運輸連線管理
TCP的連線建立(三次握手)

  B的TCP伺服器程序先建立傳輸控制塊TCB,準備接受客戶程序A的請求。然後伺服器出於LISTEN狀態,等待客戶A的連線請求。
  A的客戶端程序首先建立TCP,然後向B傳送連線請求報文段。這時首部中的同步位SYN = 1,同時選擇一個初始序號seq = x。TCP規定,SYN(SYN = 1)報文段不能攜帶資料,但是要消耗一個序號。這時客戶機A進入同步已傳送狀態(SYN-SENT)。
B收到連線請求的報文段後,如果同意建立連線,則向A傳送確認。在確認報文段中應當把SYN和ACK的值都置為1,確認號是ack = x + 1,同時也為自己初始化一個序號seq = y。注意該報文也不能攜帶資料,但是需要消耗掉一個序號。此時TCP伺服器程序進入同步收到狀態(SYN-RCVD)。
  TCP客戶程序收到伺服器端的確認後,還要向B傳送確認。報文段的ACK置為1,確認號ack = y + 1,而自己的序號為seq = x + 1。TCP的標準規定,ACK報文段可以攜帶資料,如果不攜帶資料則不消耗序號,在這種情況下,下一個報文段的序號仍是seq = x + 1.這時TCP連線已經建立,此時A已經進入ESTABLISHED狀態。當B收到確認後,也進入ESTABLISHED狀態。
至此,A與B已經建立連線,我們稱作“三次握手”或者“三次聯絡”。

為什麼要三次握手, 兩次不可以嗎?

   試想一下, A第一次傳送請求連線, 但是在網路某節點滯留了, A超時重傳, 然後這一次一切正常, A跟B就愉快地進行資料傳輸了. 等到連線釋放了以後, 那個迷失了的連線請求突然到了B那, 如果是兩次握手的話, B傳送確認, 它們就算是建立起了連線了. 事實上A並不會理會這個確認, 因為我壓根沒有要傳資料啊. 但是B卻傻傻地以為有資料要來, 苦苦等待. 結果就是造成資源的浪費.
  更加接地氣的解釋就是 : A打電話給B
  第一次握手 : 你好, 我是A, 你能聽到我說話嗎? 第二次握手 : 聽到了, 我是B, 你能聽到我說話嗎? 第三次握手 : 聽到了, 我們可以開始聊天了。 三次握手其實就是為了檢測雙方的傳送和接收能力是否正常。

TCP的連線釋放(TCP四次揮手)

資料傳輸結束後,通訊的雙發都可釋放連線,現在A B 都處於ESTABLISHED狀態。
A的應用程序先向其TCP發出連線釋放報文段,然後停止傳送資料,主動關閉TCP連線。A的連線釋放報文段把FIN置為1,其序號為seq = u,它等於前面已傳送過的最後一個位元組的序號加1。此時A進入FIN-WAIT-1狀態,等待B的確認。TCP規定,FIN不攜帶資料,但是要消耗掉一個序號。
B收到連線釋放報文段後向A發出確認,確認號是ack = u + 1,這個報文段自己的序號是v,等於B前面已傳送資料的最後一個位元組的序號加1。然後B進入CLOSE-WAIT狀態。TCP伺服器程序這時通知高層應用程序,因而從A到B這個方向的連線就釋放了,這時TCP的連線處於半關閉狀態,即A已經沒有資料向B傳送了,但是若B仍要傳送資料,A依舊要接受。也就是說從B到A這個方向的連線並未關閉。
A收到來自B的報文段後進入FIN-WAIT-2狀態,等待B的連續釋放報文。
如果B已經沒有資料向A傳送了,其應用程序就會通知TCP釋放連線。這時B傳送連續確認報文段必須使FIN = 1,現在B的序號為w(在半關閉狀態,B可能又傳送了一段資料)B還必須重複已經發送過的確認號ack = u + 1。這時B進入了LAST-ACK狀態,等待A的確認。
A在收到B的報文段後進行確認,其確認號ack為w + 1(TCP規定,FIN報文段需要消耗一個序號),其自己的序號為seq = u + 1。然後進入到TIME-WAIT狀態。這時需要注意的是TCP連線還沒有釋放掉,必須經過時間等待計時器(TIME-WAIT timer)設定的2MSL(Maximum Segment Lifetime),A才進入關閉狀態。MSL叫做最大報文段壽命。
SYN=1和FIN=1都要消耗掉一個位元組序號,且都不能攜帶資料。ACK=1可以攜帶資料,攜帶則消耗序號,否則不消耗。
Q : 為什麼要四次揮手, 而不是兩次, 三次?
首先, 由於TCP的全雙工通訊, 雙方都能作為資料傳送方. A想要關閉連線, 必須要等資料都發送完畢, 才傳送FIN給B. (此時A處於半關閉狀態)
然後, B傳送確認ACK, 並且B此時如果要傳送資料, 就傳送(例如做一些釋放前的處理)
再者, B傳送完資料之後, 傳送FIN給A. (此時B處於半關閉狀態)
然後, A傳送ACK, 進入TIME-WAIT狀態
最後, 經過2MSL時間後沒有收到B傳來的報文, 則確定B收到了ACK了. (此時A, B才算是處於完全關閉狀態)
PS : 仔細分析以上步驟就知道為什麼不能少於四次揮手了.

為什麼要等待2MSL(Maximum Segment Lifetime)時間, 才從TIME_WAIT到CLOSED?

這有兩個理由:
(1)保證A傳送的最後一個ACK報文能夠到達B。在Client傳送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複傳送FIN+ACK片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在傳送出ACK之後進入到TIME_WAIT狀態。Client會設定一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。如果Client不等待一段時間,則有可能會對Server發來的FIN+ACK報文回以RST導致Server無法進入CLOSED狀態。
(2)防止舊連線請求報文影響新的連線。MSL指一個片段在網路中最大的存活時間,2MSL就是一個傳送和一個回覆所需的最大時間。Client在傳送完ACK後,再經過2MSL,就可以使本連線持續時間內所產生的所有報文段從網路中消失。
更加接地氣的解釋 :
第一次揮手 : A告訴B, 我沒資料發了, 準備關閉連線了, 你要傳送資料嗎第二次揮手 : B傳送最後的資料第三次揮手 : B告訴A, 我也要關閉連線了第四次揮手 : A告訴B你可以關閉了, 我這邊也關閉了

TCP的有限狀態機

TCP狀態及描述
CLOSED:無連線是活動的或正在進行
LISTEN:伺服器在等待進入呼叫
SYN_RECV:一個連線請求已經到達,等待確認
SYN_SENT:應用已經開始,開啟一個連線
ESTABLISHED:正常資料傳輸狀態
FIN_WAIT1:應用說它已經完成
FIN_WAIT2:另一邊已同意釋放
CLOSE_WAIT:等待所有分組死掉
CLOSING:兩邊同時嘗試關閉
TIME_WAIT:另一邊已初始化一個釋放
LAST_ACK:等待所有分組死掉

TCP定時器

(1)重傳計時器:
重傳定時器:為了控制丟失的報文段或丟棄的報文段,也就是對報文段確認的等待時間。當TCP傳送報文段時,就建立這個特定報文段的重傳計時器,可能發生兩種情況:若在計時器超時之前收到對報文段的確認,則撤銷計時器;若在收到對特定報文段的確認之前計時器超時,則重傳該報文,並把計時器復位;
重傳時間=2*RTT;
RTT的值應該動態計算。常用的公式是:RTT=previous RTT*i + (1-i)*current RTT。i的值通常取90%,即新的RTT是以前的RTT值的90%加上當前RTT值的10%.
Karn演算法:對重傳報文,在計算新的RTT時,不考慮重傳報文的RTT。因為無法推理出:傳送端所收到的確認是對上一次報文段的確認還是對重傳報文段的確認。乾脆不計入。
(2)堅持計時器:persistent timer
專門為對付零視窗通知而設立的。
當傳送端收到零視窗的確認時,就啟動堅持計時器,當堅持計時器截止期到時,傳送端TCP就傳送一個特殊的報文段,叫探測報文段,這個報文段只有一個位元組的資料。探測報文段有序號,但序號永遠不需要確認,甚至在計算對其他部分資料的確認時這個序號也被忽略。探測報文段提醒接收端TCP,確認已丟失,必須重傳。
堅持計時器的截止期設定為重傳時間的值,但若沒有收到從接收端來的響應,則傳送另一個探測報文段,並將堅持計時器的值加倍和並復位,傳送端繼續傳送探測報文段,將堅持計時器的值加倍和復位,知道這個值增大到閾值為止(通常為60秒)。之後,傳送端每隔60s就傳送一個報文段,直到視窗重新開啟為止;
(3)保活計時器:keeplive timer
每當伺服器收到客戶的資訊,就將keeplive timer復位,超時通常設定2小時,若伺服器超過2小時還沒有收到來自客戶的資訊,就傳送探測報文段,若傳送了10個探測報文段(沒75秒傳送一個)還沒收到響應,則終止連線。
(4)時間等待計時器:Time_Wait Timer
在連線終止期使用,當TCP關閉連線時,並不認為這個連線就真正關閉了,在時間等待期間,連線還處於一種中間過度狀態。這樣就可以時重複的fin報文段在到達終點後被丟棄,這個計時器的值通常設定為一格報文段壽命期望值的兩倍。

高併發網路伺服器的實現?

  1. 多執行緒,衍生出執行緒池、執行緒安全、執行緒數限制、使用者執行緒和核心執行緒等等問題
  2. reactor機制
  3. proactor機制,詳情可以看這裡