網絡編程學習筆記一:Socket 編程基本概念

 

 

 

1 概述

 

開始使用套接字編程之前,首先必須建立以下概念。


1.1 網間進程通信

 

進程通信的概念最初來源於單機系統。由於每個進程都在自己的地址範圍內運行,爲保證兩個相互通信的進程之間既互不干擾又協調一致工作,操作系統爲進程通信提供了相應設施,

如UNIX BSD(Berkeley Software Distribution, 伯克利軟件套件)有:管道(pipe)、命名管道(named pipe)軟中斷信號(signal)

UNIX system V有:消息(message)、共享存儲區(shared memory)和信號量(semaphore)等.

他們都僅限於用在本機進程之間通信。網間進程通信要解決的是不同主機進程間的相互通信問題(可把同機進程通信看作是其中的特例)。爲此,首先要解決的是網間進程標識問題。同一主機上,不同進程可用進程號(process ID)唯一標識。但在網絡環境下,各主機獨立分配的進程號不能唯一標識該進程。例如,主機A賦於某進程號5,在B機中也可以存在5號進程,因此,“5號進程”這句話就沒有意義了。 其次,操作系統支持的網絡協議衆多,不同協議的工作方式不同,地址格式也不同。因此,網間進程通信還要解決多重協議的識別問題。 爲了解決上述問題,TCP/IP協議引入了下列幾個概念。 

(1) 端口 
網絡中可以被命名和尋址的通信端口,是操作系統可分配的一種資源。 

按照OSI七層協議的描述,傳輸層與網絡層在功能上的最大區別是傳輸層提供進程通信能力。從這個意義上講,網絡通信的最終地址就不僅僅是主機地址了,還包括可以描述進程的某種標識符。爲此,TCP/IP協議提出了協議端口(protocol port,簡稱端口)的概念,用於標識通信的進程。 

端口是一種抽象的軟件結構(包括一些數據結構和I/O緩衝區)。應用程序(即進程)通過系統調用與某端口建立連接(binding)後,傳輸層傳給該端口的數據都被相應進程所接收,相應進程發給傳輸層的數據都通過該端口輸出。在TCP/IP協議的實現中,對端口的操作類似於一般的I/O操作,進程獲取一個端口,相當於獲取本地唯一的I/O文件,可以用一般的讀寫原語訪問之。 類似於文件描述符,每個端口都擁有一個叫端口號(port number)的整數型標識符,用於區別不同端口。

由於TCP/IP傳輸層的兩個協議TCP和UDP是完全獨立的兩個軟件模塊,因此各自的端口號也相互獨立,如TCP有一個255號端口,UDP也可以有一個255號端口,二者並不衝突。 

端口號的分配是一個重要問題。有兩種基本分配方式:第一種叫全局分配,這是一種集中控制方式,由一個公認的中央機構根據用戶需要進行統一分配,並將結果公佈於衆。第二種是本地分配,又稱動態連接,即進程需要訪問傳輸層服務時,向本地操作系統提出申請,操作系統返回一個本地唯一的端口號,進程再通過合適的系統調用將自己與該端口號聯繫起來(綁紮)。TCP/IP端口號的分配中綜合了上述兩種方式。TCP/IP將端口號分爲兩部分,少量的作爲保留端口,以全局方式分配給服務進程。因此,每一個標準服務器都擁有一個全局公認的端口(即周知口,well-known port),即使在不同的機器上,其端口號也相同。剩餘的爲自由端口,以本地方式進行分配。TCP和UDP均規定,小於256的端口號才能作保留端口。 

(2) 地址 
網絡通信中通信的兩個進程分別在不同的機器上。在互連網絡中,兩臺機器可能位於不同的網絡,這些網絡通過網絡互連設備(網關,網橋,路由器等)連接。因此需要三級尋址: 

(a) 某一主機可與多個網絡相連,必須指定一特定網絡地址; 
(b) 網絡上每一臺主機應有其唯一的地址; 
(c) 每一主機上的每一進程應有在該主機上的唯一標識符。 

通常主機地址由網絡ID和主機ID組成,在TCP/IP協議中用32位整數值表示;TCP和UDP均使用16位端口號標識用戶進程。 

(3) 網絡字節順序 
不同的計算機存放多字節值的順序不同,有的機器在起始地址存放低位字節(小端序),有的存高位字節(大端序)。爲保證數據的正確性,在網絡協議中須指定網絡字節順序。TCP/IP協議使用16位整數和32位整數的高價先存格式,它們均含在協議頭文件中。 詳解http://blog.csdn.net/hguisu/article/details/7449955#t1

(4)連接 
兩個進程間的通信鏈路稱爲連接。連接在內部表現爲一些緩衝區和一組協議機制,在外部表現出比無連接高的可靠性。 

(5)半相關 
綜上所述,網絡中用一個三元組可以在全局唯一標誌一個進程: 

(協議,本地地址,本地端口號) 這樣一個三元組,叫做一個半相關(half-association),它指定連接的每半部分。 

(6) 全相關 
一個完整的網間進程通信需要由兩個進程組成,並且只能使用同一種高層協議。也就是說,不可能通信的一端用TCP協議,而另一端用UDP協議。因此一個完整的網間通信需要一個五元組來標識: 

(協議,本地地址,本地端口號,遠地地址,遠地端口號) 這樣一個五元組,叫做一個相關(association),即兩個協議相同的半相關才能組合成一個合適的相關,或完全指定組成一連接。 

 


1.2 服務方式  


在網絡分層結構中,各層之間是嚴格單向依賴的,各層次的分工和協作集中體現在不同層之間的界面上。“服務”是描述不同層之間關係的抽象概念,即網絡中各層向緊鄰上層提供的一組操作。下層是服務提供者,上層是請求服務的用戶。服務的表現形式是原語(primitive),如系統調用或庫函數。系統調用是操作系統內核向網絡應用程序或高層協議提供的服務原語。網絡中的n層總要向n+1層提供比n-1層更完備的服務,否則n層就沒有存在的價值。 在OSI的術語中,網絡層及其以下各層又稱爲通信子網,只提供點到點通信,沒有程序或進程的概念。而傳輸層實現的是“端到端”通信,引進網間進程通信概念,同時也要解決差錯控制,流量控制,數據排序(報文排序),連接管理等問題,爲此提供不同的服務方式: 

(1) 面向連接(虛電路)或無連接 

面向連接服務(TCP協議):是電話系統服務模式的抽象,即每一次完整的數據傳輸都要經過建立連接,使用連接,終止連接的過程。在數據傳輸過程中,各數據分組不攜帶目的地址,而使用連接號(connect ID)。本質上,連接是一個管道,收發數據不但順序一致,而且內容相同。TCP協議提供面向連接的虛電路。
無連接服務(UDP協議):是郵政系統服務的抽象,每個分組都攜帶完整的目的地址,各分組在系統中獨立傳送。無連接服務不能保證分組的先後順序,不進行分組出錯的恢復與重傳,不保證傳輸的可靠性。UDP協議提供無連接的數據報服務。 

下面給出這兩種服務的類型及應用中的例子: 

(2) 順序 
在網絡傳輸中,兩個連續報文在端-端通信中可能經過不同路徑,這樣到達目的地時的順序可能會與發送時不同。“順序”是指接收數據順序與發送數據順序相同。TCP協議提供這項服務。 

(3) 差錯控制 
保證應用程序接收的數據無差錯的一種機制。檢查差錯的方法一般是採用檢驗“檢查和(Checksum)”的方法。而保證傳送無差錯的方法是雙方採用確認應答技術。TCP協議提供這項服務。 

(4) 流控制 
在數據傳輸過程中控制數據傳輸速率的一種機制,以保證數據不被丟失。TCP協議提供這項服務。 

(5) 字節流 
字節流方式指的是僅把傳輸中的報文看作是一個字節序列,不提供數據流的任何邊界。TCP協議提供字節流服務。 

(6) 報文 
接收方要保存發送方的報文邊界。UDP協議提供報文服務。 

(7) 全雙工/半雙工 
端-端間數據同時以兩個方向/一個方向傳送。 

(8) 緩存/帶外數據 
在字節流服務中,由於沒有報文邊界,用戶進程在某一時刻可以讀或寫任意數量的字節。爲保證傳輸正確或採用有流控制的協議時,都要進行緩存。但對某些特殊的需求,如交互式應用程序,又會要求取消這種緩存。 在數據傳送過程中,希望不通過常規傳輸方式傳送給用戶以便及時處理的某一類信息,如UNIX系統的中斷鍵(Delete或Control-c)、終端流控制符(Control-s和Control-q),稱爲帶外數據。邏輯上看,好象用戶進程使用了一個獨立的通道傳輸這些數據。該通道與每對連接的流相聯繫。由於Berkeley Software Distribution中對帶外數據的實現與RFC 1122中規定的Host Agreement不一致,爲了將互操作中的問題減到最小,應用程序編寫者除非與現有服務互操作時要求帶外數據外,最好不使用它。 

 

1.3 客戶/服務器模式

在TCP/IP網絡應用中,通信的兩個進程間相互作用的主要模式是客戶/服務器模式(Client/Server model),即客戶向服務器發出服務請求,服務器接收到請求後,提供相應的服務。客戶/服務器模式的建立基於以下兩點:首先,建立網絡的起因是網絡中軟硬件資源、運算能力和信息不均等,需要共享,從而造就擁有衆多資源的主機提供服務,資源較少的客戶請求服務這一非對等作用。其次,網間進程通信完全是異步的,相互通信的進程間既不存在父子關係,又不共享內存緩衝區,因此需要一種機制爲希望通信的進程間建立聯繫,爲二者的數據交換提供同步,這就是基於不同的客戶/服務器模式的TCP/IP。 客戶/服務器模式在工作過程中採取的是主動請求方式: 

(1) 服務器方:

首先服務器方要先啓動,並根據請求提供相應服務: 

(a) 打開一通信通道並告知本地主機,它願意在某一公認地址上(周知口,如FTP爲21)接收客戶請求; 

(b) 等待客戶請求到達該端口; 

(c) 接收到重複服務請求,處理該請求併發送應答信號。接收到併發服務請求,要激活一新進程來處理這個客戶請求(如UNIX系統中用fork、exec)。新進程處理此客戶請求,並不需要對其它請求作出應答。服務完成後,關閉此新進程與客戶的通信鏈路,並終止。 

(d) 返回第二步,等待另一客戶請求。 

(e) 關閉服務器 


(2) 客戶方: 

(1) 打開一通信通道,並連接到服務器所在主機的特定端口; 

(2) 向服務器發服務請求報文,等待並接收應答;繼續提出請求...... 

(3) 請求結束後關閉通信通道並終止。 

從上面所描述過程可知: 

(1) 客戶與服務器進程的作用是非對稱的,因此編碼不同。 

(2) 服務進程一般是先於客戶請求而啓動的。只要系統運行,該服務進程一直存在,直到正常或強迫終止。 

 

1.4 套接字類型

TCP/IP的socket提供下列三種類型套接字。 

(1) 流式套接字(SOCK_STREAM):
提供了一個面向連接、可靠的數據傳輸服務,數據無差錯、無重複地發送,且按發送順序接收。內設流量控制,避免數據流超限;數據被看作是字節流,無長度限制。文件傳送協議(FTP)即使用流式套接字。 

(2) 數據報式套接字(SOCK_DGRAM):
提供了一個無連接服務(UDP)。數據包以獨立包形式被髮送,不提供無錯保證,數據可能丟失或重複,並且接收順序混亂。網絡文件系統(NFS)使用數據報式套接字。 

(3) 原始式套接字(SOCK_RAW) :
該接口允許對較低層協議,如IP、ICMP直接訪問。常用於檢驗新的協議實現或訪問現有服務中配置的新設備。 

 

1.5 典型套接字調用過程舉例


(1) 如前所述,TCP/IP協議的應用一般採用客戶/服務器模式,因此在實際應用中,必須有客戶和服務器兩個進程,並且首先啓動服務器,其系統調用時序圖如下。 面向連接的協議(如TCP)的套接字系統調用如圖2.1所示:

 

服務器必須首先啓動,直到它執行完accept()調用,進入等待狀態後,方能接收客戶請求。假如客戶在此前啓動,則connect()將返回出錯代碼,連接不成功。


(2) 無連接協議(UDP)的套接字調用如圖2.2所示: 

 

無連接服務器也必須先啓動,否則客戶請求傳不到服務進程。無連接客戶不調用connect()。因此在數據發送之前,客戶與服務器之間尚未建立完全相關,但各自通過socket()和bind()建立了半相關。發送數據時,發送方除指定本地套接字號外,還需指定接收方套接字號,從而在數據收發過程中動態地建立了全相關。  

 

2 socket的基本操作

既然 socket 是 "open—write/read—close" 模式的一種實現,那麼socket就提供了這些操作對應的函數接口。下面以TCP爲例,介紹幾個基本的socket接口函數。

 

2.1 套接字類型

(1) 流式套接字(SOCK_STREAM)

    提供面向連接的、可靠的數據傳輸服務,數據無差錯,無重複的發送,且按發送順序接收, 對應TCP協議。

(2) 數據報式套接字(SOCK_DGRAM 數據報(datagrams))

    提供無連接服務。不提供無錯保證,數據可能丟失或重複,並且接收順序混亂, 對應UDP協議。

(3) 原始套接字(SOCK_RAW)

    使我們可以跨越傳輸層直接對IP層進行封裝傳輸。(應用層直接和IP層)

 

2.2 socket()函數

int socket(int domain, int type, int protocol);

socket函數對應於普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用於創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,後續的操作都有用到它,把它作爲參數,通過它來進行一些讀寫操作。

正如可以給fopen的傳入不同參數值,以打開不同的文件。創建socket的時候,也可以指定不同的參數創建不同的socket描述符,socket函數的三個參數分別爲:

(1) domain

即協議域,又稱爲協議族(family)。常用的協議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作爲地址。

(2) type

指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的類型有哪些?)。

(3) protocol

故名思意,就是指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議(這個協議我將會單獨開篇討論!)。
注意:並不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol爲0時,會自動選擇type類型對應的默認協議。

當我們調用socket創建一個socket時,返回的socket描述字它存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時系統會自動隨機分配一個端口。

 


2.3 bind()函數

正如上面所說bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

函數的三個參數分別爲:

sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。

addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同,如ipv4對應的是: 

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};


ipv6對應的是: 

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};


Unix域對應的是: 

#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

addrlen:對應的是地址的長度。
通常服務器在啓動的時候都會綁定一個衆所周知的地址(如ip地址+端口號),用於提供服務,客戶就可以通過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是爲什麼通常服務器端在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。


(1) 網絡字節序與主機字節序

主機字節序就是我們平常說的大端和小端模式:不同的CPU有不同的字節序類型,這些字節序是指整數在內存中保存的順序,這個叫做主機序。引用標準的Big-Endian和Little-Endian的定義如下:

  (a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。

  (b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

網絡字節序:4個字節的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit。這種傳輸次序稱作大端字節序。由於TCP/IP首部中所有的二進制整數在網絡中傳輸時都要求以這種次序,因此它又稱作網絡字節序。字節序,顧名思義字節的順序,就是大於一個字節類型的數據在內存中的存放順序,一個字節的數據沒有順序的問題了。

所以:在將一個地址綁定到socket的時候,請先將主機字節序轉換成爲網絡字節序,而不要假定主機字節序跟網絡字節序一樣使用的是Big-Endian。由於這個問題曾引發過血案!公司項目代碼中由於存在這個問題,導致了很多莫名其妙的問題,所以請謹記對主機字節序不要做任何假定,務必將其轉化爲網絡字節序再賦給socket。

 

 

2.4 listen() 和 connect()函數

如果作爲一個服務器,在調用socket()、bind()之後就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器端就會接收到這個請求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

(1) listen 函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的連接請求。

(2) connect函數的第一個參數即爲客戶端的socket描述字,第二參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接。

 


2.5 accept()函數

TCP服務器端依次調用socket()、bind()、listen()之後,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之後就想TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求之後,就會調用accept()函數取接收請求,這樣連接就建立好了。之後就可以開始網絡I/O操作了,即類同於普通文件的讀寫I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函數的第一個參數爲服務器的socket描述字,第二個參數爲指向struct sockaddr *的指針,用於返回客戶端的協議地址,第三個參數爲協議地址的長度。如果accpet成功,那麼其返回值是由內核自動生成的一個全新的描述字,代表與返回客戶的TCP連接。

注意:accept的第一個參數爲服務器的socket描述字,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字;而accept函數返回的是已連接的socket描述字。一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。

 


2.5 read() 和 write()等函數

萬事具備只欠東風,至此服務器與客戶已經建立好連接了。可以調用網絡I/O進行讀寫操作了,即實現了網咯中不同進程之間的通信!網絡I/O操作有下面幾組:

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

我推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上可以把上面的其它函數都替換成這兩個函數。它們的聲明如下:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

 

#include <sys/types.h>
#include <sys/socket.h>
       
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
               
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
                 
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);


       
(1) read函數是負責從fd中讀取內容.當讀成功時,read返回實際所讀的字節數,如果返回的值是0表示已經讀到文件的結束了,小於0表示出現了錯誤。如果錯誤爲EINTR說明讀是由中斷引起的,如果是ECONNREST表示網絡連接出了問題。

(2) write函數將buf中的nbytes字節內容寫入文件描述符fd.成功時返回寫的字節數。失敗時返回-1,並設置errno變量。 在網絡程序中,當我們向套接字文件描述符寫時有倆種可能。1)write的返回值大於0,表示寫了部分或者是全部的數據。2)返回的值小於0,此時出現了錯誤。我們要根據錯誤類型來處理。如果錯誤爲EINTR表示在寫的時候出現了中斷錯誤。如果爲EPIPE表示網絡連接出現了問題(對方已經關閉了連接)。

 

2.6 close()函數

在服務器與客戶端建立連接之後,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完打開的文件要調用fclose關閉打開的文件。

#include <unistd.h>
int close(int fd);

close 一個TCP socket的缺省行爲時把該socket標記爲以關閉,然後立即返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再作爲read或write的第一個參數。

注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數爲0的時候,纔會觸發TCP客戶端向服務器發送終止連接請求。

 


3 socket中TCP的三次握手建立連接詳解

我們知道tcp建立連接要進行"三次握手",即交換三個分組。大致流程如下:

(1) 客戶端向服務器發送一個 SYN J
(2) 服務器向客戶端響應一個 SYN K,並對SYN J 進行確認 ACK J+1
(3) 客戶端再想服務器發一個確認ACK K+1
只有就完了三次握手,但是這個三次握手發生在socket的那幾個函數中呢?請看下圖:

 

                 圖1、socket中發送的TCP三次握手


從圖中可以看出,當客戶端調用connect時,觸發了連接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到連接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,連接建立。


總結:客戶端的connect在三次握手的第二個次返回,而服務器端的accept在三次握手的第三次返回。

 

 

4 socket中TCP的四次握手釋放連接詳解

上面介紹了socket中TCP的三次握手建立過程,及其涉及的socket函數。現在我們介紹socket中的四次握手釋放連接的過程,請看下圖:

 


             圖2、socket中發送的TCP四次握手


圖示過程如下:

(1) 某個應用進程首先調用close主動關閉連接,這時TCP發送一個FIN M;
(2) 另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作爲文件結束符傳遞給應用進程,因爲FIN的接收意味着應用進程在相應的連接上再也接收不到額外數據;
(3) 一段時間之後,接收到文件結束符的應用進程調用close關閉它的socket。這導致它的TCP也發送一個FIN N;
(4) 接收到這個FIN的源發送端TCP對它進行確認。
這樣每個方向上都有一個 FIN 和 ACK。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文轉自:

https://blog.csdn.net/gneveek/article/details/8699198#t0

https://blog.csdn.net/hguisu/article/details/7444092

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章