Linux C編程第十六章 網絡編程基礎 - socket 網絡相關術語名詞解析(linux系統)

      網友投稿 980 2025-03-31

      一、協議的概念

      1. 什么是協議

      從應用的角度出發,協議可理解為“規則”,是數據傳輸和數據的解釋的規則。

      假設,A、B雙方欲傳輸文件。規定:

      第一次,傳輸文件名,接收方接收到文件名,應答OK給傳輸方;

      第二次,發送文件的尺寸,接收方接收到該數據再次應答一個OK;

      第三次,傳輸文件內容。同樣,接收方接收數據完成后應答OK表示文件內容接收成功。

      由此,無論A、B之間傳遞何種文件,都是通過三次數據傳輸來完成。A、B之間形成了一個最簡單的數據傳輸規則。雙方都按此規則發送、接收數據。A、B之間達成的這個相互遵守的規則即為協議。

      這種僅在A、B之間被遵守的協議稱之為原始協議。當此協議被更多的人采用,不斷的增加、改進、維護、完善。最終形成一個穩定的、完整的文件傳輸協議,被廣泛應用于各種文件傳輸過程中。

      該協議就成為一個標準協議。最早的ftp協議就是由此衍生而來。

      TCP協議注重數據的傳輸。http協議著重于數據的解釋。

      2. 典型協議

      傳輸層:常見協議有TCP/UDP協議。

      應用層:常見的協議有HTTP協議,FTP協議。

      網絡層:常見協議有IP協議、ICMP協議、IGMP協議。

      網絡接口層:常見協議有ARP協議、RARP協議。

      TCP傳輸控制協議(Transmission Control Protocol)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。

      UDP用戶數據報協議(User Datagram Protocol)是OSI參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務。

      HTTP超文本傳輸協議(Hyper Text Transfer Protocol)是互聯網上應用最為廣泛的一種網絡協議。

      FTP文件傳輸協議(File Transfer Protocol)

      IP協議是因特網互聯協議(Internet Protocol)

      ICMP協議是Internet控制報文協議(Internet Control Message Protocol)它是TCP/IP協議族的一個子協議,用于在IP主機、路由器之間傳遞控制消息。

      IGMP協議是 Internet 組管理協議(Internet Group Management Protocol),是因特網協議家族中的一個組播協議。該協議運行在主機和組播路由器之間。

      ARP協議是正向地址解析協議(Address Resolution Protocol),通過已知的IP,尋找對應主機的MAC地址。

      RARP是反向地址轉換協議,通過MAC地址確定IP地址。

      二、網絡應用程序設計模式

      1. C/S模式

      傳統的網絡應用設計模式,客戶機(client)/服務器(server)模式。需要在通訊兩端各自部署客戶機和服務器來完成數據通信。

      2. B/S模式

      瀏覽器(browser)/服務器(server)模式。只需在一端部署服務器,而另外一端使用每臺PC都默認配置的瀏覽器即可完成數據的傳輸。

      3. 優缺點

      對于C/S模式來說,其優點明顯。客戶端位于目標主機上可以保證性能,將數據緩存至客戶端本地,從而提高數據傳輸效率。且,一般來說客戶端和服務器程序由一個開發團隊創作,所以他們之間

      所采用的協議相對靈活。可以在標準協議的基礎上根據需求裁剪及定制。例如,騰訊公司所采用的通信協議,即為ftp協議的修改剪裁版。

      因此,傳統的網絡應用程序及較大型的網絡應用程序都首選C/S模式進行開發。如,知名的網絡游戲魔獸世界。3D畫面,數據量龐大,使用C/S模式可以提前在本地進行大量數據的緩存處理,從而

      提高觀感。

      C/S模式的缺點也較突出。由于客戶端和服務器都需要有一個開發團隊來完成開發。工作量將成倍提升,開發周期較長。另外,從用戶角度出發,需要將客戶端安插至用戶主機上,對用戶主機的安

      全性構成威脅。這也是很多用戶不愿使用C/S模式應用程序的重要原因。

      B/S模式相比C/S模式而言,由于它沒有獨立的客戶端,使用標準瀏覽器作為客戶端,其工作開發量較小。只需開發服務器端即可。另外由于其采用瀏覽器顯示數據,因此移植性非常好,不受平臺

      限制。如早期的偷菜游戲,在各個平臺上都可以完美運行。

      B/S模式的缺點也較明顯。由于使用第三方瀏覽器,因此網絡應用支持受限。另外,沒有客戶端放到對方主機上,緩存數據不盡如人意,從而傳輸數據量受到限制。應用的觀感大打折扣。第三,必

      須與瀏覽器一樣,采用標準http協議進行通信,協議選擇不靈活。

      因此在開發過程中,模式的選擇由上述各自的特點決定。根據實際需求選擇應用程序設計模式。

      三、分層模型

      1. OS七層模型

      物理層:主要定義物理設備標準,如網線的接口類型、光纖的接口類型、各種傳輸介質的傳輸速率等。它的主要作用是傳輸比特流(就是由1、0轉化為電流強弱來進行傳輸,到達目的地后再轉化

      為1、0,也就是我們常說的數模轉換與模數轉換)。這一層的數據叫做比特。

      數據鏈路層:定義了如何讓格式化數據以幀為單位進行傳輸,以及如何讓控制對物理介質的訪問。這一層通常還提供錯誤檢測和糾正,以確保數據的可靠傳輸。如:串口通信中使用到的115200、

      8、N、1。

      網絡層:在位于不同地理位置的網絡中的兩個主機系統之間提供連接和路徑選擇。Internet的發展使得從世界各站點訪問信息的用戶數大大增加,而網絡層正是管理這種連接的層。

      傳輸層:定義了一些傳輸數據的協議和端口號(WWW端口80等),如:TCP(傳輸控制協議,傳輸效率低,可靠性強,用于傳輸可靠性要求高,數據量大的數據),UDP(用戶數據報協議,與

      TCP特性恰恰相反,用于傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是通過這種方式傳輸的)。 主要是將從下層接收的數據進行分段和傳輸,到達目的地址后再進行重組。常常把這一

      層數據叫做段。

      會話層:通過傳輸層(端口號:傳輸端口與接收端口)建立數據傳輸的通路。主要在你的系統之間發起會話或者接受會話請求(設備之間需要互相認識可以是IP也可以是MAC或者是主機名)。

      表示層:可確保一個系統的應用層所發送的信息可以被另一個系統的應用層讀取。例如,PC程序與另一臺計算機進行通信,其中一臺計算機使用擴展二一十進制交換碼(EBCDIC),而另一臺則使

      用美國信息交換標準碼(ASCII)來表示相同的字符。如有必要,表示層會通過使用一種通格式來實現多種數據格式之間的轉換。

      應用層:是最靠近用戶的OSI層。這一層為用戶的應用程序(例如電子郵件、文件傳輸和終端仿真)提供網絡服務。

      2.?TCP/IP四層模型

      TCP/IP網絡協議棧分為應用層(Application)、傳輸層(Transport)、網絡層(Network)和鏈路層(Link)四層。如下圖所示:

      一般在應用開發過程中,討論最多的是TCP/IP模型。

      四、通信過程

      兩臺計算機通過TCP/IP協議通訊的過程如下所示:

      上圖對應兩臺計算機在同一網段中的情況,如果兩臺計算機在不同的網段中,那么數據從一臺計算機到另一臺計算機傳輸過程中要經過一個或多個路由器,如下圖所示:

      跨路由通信

      鏈路層有以太網、令牌環網等標準,鏈路層負責網卡設備的驅動、幀同步(即從網線上檢測到什么信號算作新幀的開始)、沖突檢測(如果檢測到沖突就自動重發)、數據差錯校驗等工作。交換

      機是工作在鏈路層的網絡設備,可以在不同的鏈路層網絡之間轉發數據幀(比如十兆以太網和百兆以太網之間、以太網和令牌環網之間),由于不同鏈路層的幀格式不同,交換機要將進來的數據包

      拆掉鏈路層首部重新封裝之后再轉發。

      網絡層的IP協議是構成Internet的基礎。Internet上的主機通過IP地址來標識,Inter-net上有大量路由器負責根據IP地址選擇合適的路徑轉發數據包,數據包從Internet上的源主機到目的主機往往要

      經過十多個路由器。路由器是工作在第三層的網絡設備,同時兼有交換機的功能,可以在不同的鏈路層接口之間轉發數據包,因此路由器需要將進來的數據包拆掉網絡層和鏈路層兩層首部并重新封

      裝。IP協議不保證傳輸的可靠性,數據包在傳輸過程中可能丟失,可靠性可以在上層協議或應用程序中提供支持。

      網絡層負責點到點(ptop,point-to-point)的傳輸(這里的“點”指主機或路由器),而傳輸層負責端到端(etoe,end-to-end)的傳輸(這里的“端”指源主機和目的主機)。傳輸層可選擇TCP或UDP

      協議。

      TCP是一種面向連接的、可靠的協議,有點像打電話,雙方拿起電話互通身份之后就建立了連接,然后說話就行了,這邊說的話那邊保證聽得到,并且是按說話的順序聽到的,說完話掛機斷開連

      接。也就是說TCP傳輸的雙方需要首先建立連接,之后由TCP協議保證數據收發的可靠性,丟失的數據包自動重發,上層應用程序收到的總是可靠的數據流,通訊之后關閉連接。

      UDP是無連接的傳輸協議,不保證可靠性,有點像寄信,信寫好放到郵筒里,既不能保證信件在郵遞過程中不會丟失,也不能保證信件寄送順序。使用UDP協議的應用程序需要自己完成丟包重

      發、消息排序等工作。

      目的主機收到數據包后,如何經過各層協議棧最后到達應用程序呢?其過程如下圖所示:

      以太網驅動程序首先根據以太網首部中的“上層協議”字段確定該數據幀的有效載荷(payload,指除去協議首部之外實際傳輸的數據)是IP、ARP還是RARP協議的數據報,然后交給相應的協議處

      理。假如是IP數據報,IP協議再根據IP首部中的“上層協議”字段確定該數據報的有效載荷是TCP、UDP、ICMP還是IGMP,然后交給相應的協議處理。假如是TCP段或UDP段,TCP或UDP協議再根

      據TCP首部或UDP首部的“端口號”字段確定應該將應用層數據交給哪個用戶進程。IP地址是標識網絡中不同主機的地址,而端口號就是同一臺主機上標識不同進程的地址,IP地址和端口號合起來標識

      網絡中唯一的進程。

      雖然IP、ARP和RARP數據報都需要以太網驅動程序來封裝成幀,但是從功能上劃分,ARP和RARP屬于鏈路層,IP屬于網絡層。雖然ICMP、IGMP、TCP、UDP的數據都需要IP協議來封裝成數據

      報,但是從功能上劃分,ICMP、IGMP與IP同屬于網絡層,TCP和UDP屬于傳輸層。

      五、 協議格式

      1. 數據包的封裝

      傳輸層及其以下的機制由內核提供,應用層由用戶進程提供(后面將介紹如何使用socket API編寫應用程序),應用程序對通訊數據的含義進行解釋,而傳輸層及其以下處理通訊的細節,將數據

      從一臺計算機通過一定的路徑發送到另一臺計算機。應用層數據通過協議棧發到網絡上時,每層協議都要加上一個數據首部(header),稱為封裝(Encapsulation),如下圖所示:

      不同的協議層對數據包有不同的稱謂,在傳輸層叫做段(segment),在網絡層叫做數據報(datagram),在鏈路層叫做幀(frame)。數據封裝成幀后發到傳輸介質上,到達目的主機后每層協議

      再剝掉相應的首部,最后將應用層數據交給應用程序處理。

      2. 以太網幀格式

      以太網的幀格式如下所示:

      以太網幀格式

      其中的源地址和目的地址是指網卡的硬件地址(也叫MAC地址),長度是48位,是在網卡出廠時固化的。可在shell中使用ifconfig命令查看,“HWaddr 00:15:F2:14:9E:3F”部分就是硬件地址。協議

      字段有三種值,分別對應IP、ARP、RARP。幀尾是CRC校驗碼。

      以太網幀中的數據長度規定最小46字節,最大1500字節,ARP和RARP數據包的長度不夠46字節,要在后面補填充位。最大值1500稱為以太網的最大傳輸單元(MTU),不同的網絡類型有不同

      的MTU,如果一個數據包從以太網路由到撥號鏈路上,數據包長度大于撥號鏈路的MTU,則需要對數據包進行分片(fragmentation)。ifconfig命令輸出中也有“MTU:1500”。注意,MTU這個概念指

      數據幀中有效載荷的最大長度,不包括幀頭長度。

      3.?ARP數據報格式

      在網絡通訊時,源主機的應用程序知道目的主機的IP地址和端口號,卻不知道目的主機的硬件地址,而數據包首先是被網卡接收到再去處理上層協議的,如果接收到的數據包的硬件地址與本機不

      符,則直接丟棄。因此在通訊前必須獲得目的主機的硬件地址。ARP協議就起到這個作用。源主機發出ARP請求,詢問“IP地址是192.168.0.1的主機的硬件地址是多少”,并將這個請求廣播到本地網

      段(以太網幀首部的硬件地址填FF:FF:FF:FF:FF:FF表示廣播),目的主機接收到廣播的ARP請求,發現其中的IP地址與本機相符,則發送一個ARP應答數據包給源主機,將自己的硬件地址填寫在應

      答包中。

      每臺主機都維護一個ARP緩存表,可以用arp -a命令查看。緩存表中的表項有過期時間(一般為20分鐘),如果20分鐘內沒有再次使用某個表項,則該表項失效,下次還要發ARP請求來獲得目的

      主機的硬件地址。想一想,為什么表項要有過期時間而不是一直有效?

      ARP數據報的格式如下所示:

      ARP數據報格式

      源MAC地址、目的MAC地址在以太網首部和ARP請求中各出現一次,對于鏈路層為以太網的情況是多余的,但如果鏈路層是其它類型的網絡則有可能是必要的。硬件類型指鏈路層網絡類型,1為

      以太網,協議類型指要轉換的地址類型,0x0800為IP地址,后面兩個地址長度對于以太網地址和IP地址分別為6和4(字節),op字段為1表示ARP請求,op字段為2表示ARP應答。

      看一個具體的例子。

      請求幀如下(為了清晰在每行的前面加了字節計數,每行16個字節):

      以太網首部(14字節) 0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06 ARP幀(28字節) 0000: 00 01 0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37 0020: 00 00 00 00 00 00 c0 a8 00 02 填充位(18字節) 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00

      以太網首部:目的主機采用廣播地址,源主機的MAC地址是00:05:5d:61:58:a8,上層協議類型0x0806表示ARP。

      ARP幀:硬件類型0x0001表示以太網,協議類型0x0800表示IP協議,硬件地址(MAC地址)長度為6,協議地址(IP地址)長度為4,op為0x0001表示請求目的主機的MAC地址,源主機MAC地址

      為00:05:5d:61:58:a8,源主機IP地址為c0 a8 00 37(192.168.0.55),目的主機MAC地址全0待填寫,目的主機IP地址為c0 a8 00 02(192.168.0.2)。

      由于以太網規定最小數據長度為46字節,ARP幀長度只有28字節,因此有18字節填充位,填充位的內容沒有定義,與具體實現相關。

      應答幀如下:

      以太網首部 0000: 00 05 5d 61 58 a8 00 05 5d a1 b8 40 08 06 ARP幀 0000: 00 01 0010: 08 00 06 04 00 02 00 05 5d a1 b8 40 c0 a8 00 02 0020: 00 05 5d 61 58 a8 c0 a8 00 37 填充位 0020: 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00

      以太網首部:目的主機的MAC地址是00:05:5d:61:58:a8,源主機的MAC地址是00:05:5d:a1:b8:40,上層協議類型0x0806表示ARP。

      ARP幀:硬件類型0x0001表示以太網,協議類型0x0800表示IP協議,硬件地址(MAC地址)長度為6,協議地址(IP地址)長度為4,op為0x0002表示應答,源主機MAC地址為

      00:05:5d:a1:b8:40,源主機IP地址為c0 a8 00 02(192.168.0.2),目的主機MAC地址為00:05:5d:61:58:a8,目的主機IP地址為c0 a8 00 37(192.168.0.55)。

      思考題:如果源主機和目的主機不在同一網段,ARP請求的廣播幀無法穿過路由器,源主機如何與目的主機通信?

      下圖為不同網段下兩臺機器的ARP通信協議過程:

      4.?IP段格式

      IP數據報的首部長度和數據長度都是可變長的,但總是4字節的整數倍。對于IPv4,4位版本字段是4。4位首部長度的數值是以4字節為單位的,最小值為5,也就是說首部長度最小是4x5=20字節,

      也就是不帶任何選項的IP首部,4位能表示的最大值是15,也就是說首部長度最大是60字節。8位TOS字段有3個位用來指定IP數據報的優先級(目前已經廢棄不用),還有4個位表示可選的服務類型

      (最小延遲、最大?吐量、最大可靠性、最小成本),還有一個位總是0。總長度是整個數據報(包括IP首部和IP層payload)的字節數。每傳一個IP數據報,16位的標識加1,可用于分片和重新組裝

      數據報。3位標志和13位片偏移用于分片。TTL(Time to live)是這樣用的:源主機為數據包設定一個生存時間,比如64,每過一個路由器就把該值減1,如果減到0就表示路由已經太長了仍然找不到

      目的主機的網絡,就丟棄該包,因此這個生存時間的單位不是秒,而是跳(hop)。協議字段指示上層協議是TCP、UDP、ICMP還是IGMP。然后是校驗和,只校驗IP首部,數據的校驗由更高層協議

      負責。IPv4的IP地址長度為32位。

      想一想,前面講了以太網幀中的最小數據長度為46字節,不足46字節的要用填充字節補上,那么如何界定這46字節里前多少個字節是IP、ARP或RARP數據報而后面是填充字節?

      5.? UDP數據報格式

      下面分析一幀基于UDP的TFTP協議幀。

      以太網首部 0000: 00 05 5d 67 d0 b1 00 05 5d 61 58 a8 08 00 IP首部 0000: 45 00 0010: 00 53 93 25 00 00 80 11 25 ec c0 a8 00 37 c0 a8 0020: 00 01 UDP首部 0020: 05 d4 00 45 00 3f ac 40 TFTP協議 0020: 00 01 'c'':''\''q' 0030: 'w''e''r''q''.''q''w''e'00 'n''e''t''a''s''c''i' 0040: 'i'00 'b''l''k''s''i''z''e'00 '5''1''2'00 't''i' 0050: 'm''e''o''u''t'00 '1''0'00 't''s''i''z''e'00 '0' 0060: 00以太網首部:源MAC地址是00:05:5d:61:58:a8,目的MAC地址是00:05:5d:67:d0:b1,上層協議類型0x0800表示IP。

      IP首部:每一個字節0x45包含4位版本號和4位首部長度,版本號為4,即IPv4,首部長度為5,說明IP首部不帶有選項字段。服務類型為0,沒有使用服務。16位總長度字段(包括IP首部和IP層

      payload的長度)為0x0053,即83字節,加上以太網首部14字節可知整個幀長度是97字節。IP報標識是0x9325,標志字段和片偏移字段設置為0x0000,就是DF=0允許分片,MF=0此數據報沒有更多

      分片,沒有分片偏移。TTL是0x80,也就是128。上層協議0x11表示UDP協議。IP首部校驗和為0x25ec,源主機IP是c0 a8 00 37(192.168.0.55),目的主機IP是c0 a8 00 01(192.168.0.1)。

      UDP首部:源端口號0x05d4(1492)是客戶端的端口號,目的端口號0x0045(69)是TFTP服務的well-known端口號。UDP報長度為0x003f,即63字節,包括UDP首部和UDP層pay-load的長度。

      UDP首部和UDP層payload的校驗和為0xac40。

      TFTP是基于文本的協議,各字段之間用字節0分隔,開頭的00 01表示請求讀取一個文件,接下來的各字段是:

      c:\qwerq.qwe netascii blksize 512 timeout 10 tsize 0

      一般的網絡通信都是像TFTP協議這樣,通信的雙方分別是客戶端和服務器,客戶端主動發起請求(上面的例子就是客戶端發起的請求幀),而服務器被動地等待、接收和應答請求。客戶端的IP地

      址和端口號唯一標識了該主機上的TFTP客戶端進程,服務器的IP地址和端口號唯一標識了該主機上的TFTP服務進程,由于客戶端是主動發起請求的一方,它必須知道服務器的IP地址和TFTP服務進

      程的端口號,所以,一些常見的網絡協議有默認的服務器端口,例如HTTP服務默認TCP協議的80端口,FTP服務默認TCP協議的21端口,TFTP服務默認UDP協議的69端口(如上例所示)。在使用

      客戶端程序時,必須指定服務器的主機名或IP地址,如果不明確指定端口號則采用默認端口,請讀者查閱ftp、tftp等程序的man page了解如何指定端口號。/etc/services中列出了所有well-known的服

      務端口和對應的傳輸層協議,這是由IANA(Internet Assigned Numbers Authority)規定的,其中有些服務既可以用TCP也可以用UDP,為了清晰,IANA規定這樣的服務采用相同的TCP或UDP默認

      端口號,而另外一些TCP和UDP的相同端口號卻對應不同的服務。

      很多服務有well-known的端口號,然而客戶端程序的端口號卻不必是well-known的,往往是每次運行客戶端程序時由系統自動分配一個空閑的端口號,用完就釋放掉,稱為ephemeral的端口號,想

      想這是為什么?

      前面提過,UDP協議不面向連接,也不保證傳輸的可靠性,例如:

      發送端的UDP協議層只管把應用層傳來的數據封裝成段交給IP協議層就算完成任務了,如果因為網絡故障該段無法發到對方,UDP協議層也不會給應用層返回任何錯誤信息。

      接收端的UDP協議層只管把收到的數據根據端口號交給相應的應用程序就算完成任務了,如果發送端發來多個數據包并且在網絡上經過不同的路由,到達接收端時順序已經錯亂了,UDP協議層也

      不保證按發送時的順序交給應用層。

      通常接收端的UDP協議層將收到的數據放在一個固定大小的緩沖區中等待應用程序來提取和處理,如果應用程序提取和處理的速度很慢,而發送端發送的速度很快,就會丟失數據包,UDP協議層

      并不報告這種錯誤。

      因此,使用UDP協議的應用程序必須考慮到這些可能的問題并實現適當的解決方案,例如等待應答、超時重發、為數據包編號、流量控制等。一般使用UDP協議的應用程序實現都比較簡單,只是

      發送一些對可靠性要求不高的消息,而不發送大量的數據。例如,基于UDP的TFTP協議一般只用于傳送小文件(所以才叫trivial的ftp),而基于TCP的FTP協議適用于???? 各種文件的傳輸。TCP協議

      又是如何用面向連接的服務來代替應用程序解決傳輸的可靠性問題呢。

      6. TCP數據報格式

      與UDP協議一樣也有源端口號和目的端口號,通訊的雙方由IP地址和端口號標識。32位序號、32位確認序號、窗口大小稍后詳細解釋。4位首部長度和IP協議頭類似,表示TCP協議頭的長度,以4

      字節為單位,因此TCP協議頭最長可以是4x15=60字節,如果沒有選項字段,TCP協議頭最短20字節。URG、ACK、PSH、RST、SYN、FIN是六個控制位,本節稍后將解釋SYN、ACK、FIN、RST

      四個位,其它位的解釋從略。16位檢驗和將TCP協議頭和數據都計算在內。緊急指針和各種選項的解釋從略。

      六、TCP協議

      1.?TCP通信時序

      下圖是一次TCP通訊的時序圖。TCP連接建立斷開。包含大家熟知的三次握手和四次握手。

      TCP通訊時序

      在這個例子中,首先客戶端主動發起連接、發送請求,然后服務器端響應請求,然后客戶端主動關閉連接。兩條豎線表示通訊的兩端,從上到下表示時間的先后順序,注意,數據從一端傳到網絡

      的另一端也需要時間,所以圖中的箭頭都是斜的。雙方發送的段按時間順序編號為1-10,各段中的主要信息在箭頭上標出,例如段2的箭頭上標著SYN, 8000(0), ACK1001, ,表示該段中的SYN位置

      1,32位序號是8000,該段不攜帶有效載荷(數據字節數為0),ACK位置1,32位確認序號是1001,帶有一個mss(Maximum Segment Size,最大報文長度)選項值為1024。

      建立連接(三次握手)的過程:

      (1)客戶端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的段1

      客戶端發出段1,SYN位表示連接請求。序號是1000,這個序號在網絡通訊中用作臨時的地址,每發一個數據字節,這個序號要加1,這樣在接收端可以根據序號排出數據包的正確順序,也可以發

      現丟包的情況,另外,規定SYN位和FIN位也要占一個序號,這次雖然沒發數據,但是由于發了SYN位,因此下次再發送應該用序號1001。mss表示最大段尺寸,如果一個段太大,封裝成幀后超過了

      鏈路層的最大幀長度,就必須在IP層分片,為了避免這種情況,客戶端聲明自己的最大段尺寸,建議服務器端發來的段不要超過這個長度。

      (2)服務器端回應客戶端,是三次握手中的第2個報文段,同時帶ACK標志和SYN標志。它表示對剛才客戶端SYN的回應;同時又發送SYN給客戶端,詢問客戶端是否準備好進行數據通訊

      服務器發出段2,也帶有SYN位,同時置ACK位表示確認,確認序號是1001,表示“我接收到序號1000及其以前所有的段,請你下次發送序號為1001的段”,也就是應答了客戶端的連接請求,同時

      也給客戶端發出一個連接請求,同時聲明最大尺寸為1024。

      (3)客戶必須再次回應服務器端一個ACK報文,這是報文段3

      客戶端發出段3,對服務器的連接請求進行應答,確認序號是8001。在這個過程中,客戶端和服務器分別給對方發了連接請求,也應答了對方的連接請求,其中服務器的請求和應答在一個段中發

      出,因此一共有三個段用于建立連接,稱為“三方握手(three-way-handshake)”。在建立連接的同時,雙方協商了一些信息,例如雙方發送序號的初始值、最大段尺寸等。

      在TCP通訊中,如果一方收到另一方發來的段,讀出其中的目的端口號,發現本機并沒有任何進程使用這個端口,就會應答一個包含RST位的段給另一方。例如,服務器并沒有任何進程使用8080

      端口,我們卻用telnet客戶端去連接它,服務器收到客戶端發來的SYN段就會應答一個RST段,客戶端的telnet程序收到RST段后報告錯誤Connection refused:

      $ telnet 192.168.0.200 8080 Trying 192.168.0.200... telnet: Unable to connect to remote host: Connection refused

      數據傳輸的過程:

      (1)客戶端發出段4,包含從序號1001開始的20個字節數據。

      (2)服務器發出段5,確認序號為1021,對序號為1001-1020的數據表示確認收到,同時請求發送序號1021開始的數據,服務器在應答的同時也向客戶端發送從序號8001開始的10個字節數據,這

      稱為piggyback。

      (3)客戶端發出段6,對服務器發來的序號為8001-8010的數據表示確認收到,請求發送序號8011開始的數據。

      在數據傳輸過程中,ACK和確認序號是非常重要的,應用程序交給TCP協議發送的數據會暫存在TCP層的發送緩沖區中,發出數據包給對方之后,只有收到對方應答的ACK段才知道該數據包確實

      發到了對方,可以從發送緩沖區中釋放掉了,如果因為網絡故障丟失了數據包或者丟失了對方發回的ACK段,經過等待超時后TCP協議自動將發送緩沖區中的數據包重發。

      關閉連接(四次握手)的過程:

      由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流

      動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

      (1)客戶端發出段7,FIN位表示關閉連接的請求。

      (2)服務器發出段8,應答客戶端的關閉連接請求。

      (3)服務器發出段9,其中也包含FIN位,向客戶端發送關閉連接請求。

      (4)客戶端發出段10,應答服務器的關閉連接請求。

      建立連接的過程是三方握手,而關閉連接通常需要4個段,服務器的應答和關閉連接請求通常不合并在一個段中,因為有連接半關閉的情況,這種情況下客戶端關閉連接之后就不能再發送數據給服

      務器了,但是服務器還可以發送數據給客戶端,直到服務器也關閉連接為止。

      2.?滑動窗口 (TCP流量控制)

      介紹UDP時我們描述了這樣的問題:如果發送端發送的速度較快,接收端接收到數據后處理的速度較慢,而接收緩沖區的大小是固定的,就會丟失數據。TCP協議通過“滑動窗口(Sliding

      Window)”機制解決這一問題。看下圖的通訊過程:

      (1)發送端發起連接,聲明最大段尺寸是1460,初始序號是0,窗口大小是4K,表示“我的接收緩沖區還有4K字節空閑,你發的數據不要超過4K”。接收端應答連接請求,聲明最大段尺寸是

      1024,初始序號是8000,窗口大小是6K。發送端應答,三方握手結束。

      (2)發送端發出段4-9,每個段帶1K的數據,發送端根據窗口大小知道接收端的緩沖區滿了,因此停止發送數據。

      (3)接收端的應用程序提走2K數據,接收緩沖區又有了2K空閑,接收端發出段10,在應答已收到6K數據的同時聲明窗口大小為2K。

      (4)接收端的應用程序又提走2K數據,接收緩沖區有4K空閑,接收端發出段11,重新聲明窗口大小為4K。

      (5)發送端發出段12-13,每個段帶2K數據,段13同時還包含FIN位。

      (6)接收端應答接收到的2K數據(6145-8192),再加上FIN位占一個序號8193,因此應答序號是8194,連接處于半關閉狀態,接收端同時聲明窗口大小為2K。

      (7)接收端的應用程序提走2K數據,接收端重新聲明窗口大小為4K。

      (8)接收端的應用程序提走剩下的2K數據,接收緩沖區全空,接收端重新聲明窗口大小為6K。

      (9)接收端的應用程序在提走全部數據后,決定關閉連接,發出段17包含FIN位,發送端應答,連接完全關閉。

      上圖在接收端用小方塊表示1K數據,實心的小方塊表示已接收到的數據,虛線框表示接收緩沖區,因此套在虛線框中的空心小方塊表示窗口大小,從圖中可以看出,隨著應用程序提走數據,虛線

      框是向右滑動的,因此稱為滑動窗口。

      從這個例子還可以看出,發送端是一K一K地發送數據,而接收端的應用程序可以兩K兩K地提走數據,當然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據。也就是說,應用程序

      所看到的數據是一個整體,或說是一個流(stream),在底層通訊中這些數據可能被拆成很多數據包來發送,但是一個數據包有多少字節對應用程序是不可見的,因此TCP協議是面向流的協議。而

      UDP是面向消息的協議,每個UDP段都是一條消息,應用程序必須以消息為單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不同的。

      3、TCP狀態轉換

      這個圖N多人都知道,它排除和定位網絡或系統故障時大有幫助,但是怎樣牢牢地將這張圖刻在腦中呢?那么你就一定要對這張圖的每一個狀態,及轉換的過程有深刻的認識,不能只停留在一知半

      解之中。下面對這張圖的11種狀態詳細解析一下,以便加強記憶!不過在這之前,先回顧一下TCP建立連接的三次握手過程,以及 關閉連接的四次握手過程。

      TCP狀態轉換圖

      CLOSED:表示初始狀態。

      LISTEN:該狀態表示服務器端的某個SOCKET處于監聽狀態,可以接受連接。

      SYN_SENT:這個狀態與SYN_RCVD遙相呼應,當客戶端SOCKET執行CONNECT連接時,它首先發送SYN報文,隨即進入到了SYN_SENT狀態,并等待服務端的發送三次握手中的第2個報文。

      SYN_SENT狀態表示客戶端已發送SYN報文。

      SYN_RCVD:該狀態表示接收到SYN報文,在正常情況下,這個狀態是服務器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態,很短暫。此種狀態時,當收到客戶端的ACK報

      文后,會進入到ESTABLISHED狀態。

      ESTABLISHED:表示連接已經建立。

      FIN_WAIT_1:?FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。區別是:

      FIN_WAIT_1狀態是當socket在ESTABLISHED狀態時,想主動關閉連接,向對方發送了FIN報文,此時該socket進入到FIN_WAIT_1狀態。

      FIN_WAIT_2狀態是當對方回應ACK后,該socket進入到FIN_WAIT_2狀態,正常情況下,對方應馬上回應ACK報文,所以FIN_WAIT_1狀態一般較難見到,而FIN_WAIT_2狀態可用netstat看到。

      FIN_WAIT_2:主動關閉鏈接的一方,發出FIN收到ACK以后進入該狀態。稱之為半連接或半關閉狀態。該狀態下的socket只能接收數據,不能發。

      TIME_WAIT:?表示收到了對方的FIN報文,并發送出了ACK報文,等2MSL后即可回到CLOSED可用狀態。如果FIN_WAIT_1狀態下,收到對方同時帶 FIN標志和ACK標志的報文時,可以直接進入到

      TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。

      CLOSING:這種狀態較特殊,屬于一種較罕見的狀態。正常情況下,當你發送FIN報文后,按理來說是應該先收到(或同時收到)對方的 ACK報文,再收到對方的FIN報文。但是CLOSING狀態表示

      你發送FIN報文后,并沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什么情況下會出現此種情況呢?如果雙方幾乎在同時close一個SOCKET的話,那么就出現了雙方同時發送FIN報文

      的情況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET連接。

      CLOSE_WAIT:此種狀態表示在等待關閉。當對方關閉一個SOCKET后發送FIN報文給自己,系統會回應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,察看是否還有數據發送

      給對方,如果沒有可以 close這個SOCKET,發送FIN報文給對方,即關閉連接。所以在CLOSE_WAIT狀態下,需要關閉連接。

      LAST_ACK:該狀態是被動關閉一方在發送FIN報文后,最后等待對方的ACK報文。當收到ACK報文后,即可以進入到CLOSED可用狀態。

      4.?半關閉

      當TCP鏈接中A發送FIN請求關閉,B端回應ACK后(A端進入FIN_WAIT_2狀態),B沒有立即發送FIN給A時,A方處在半鏈接狀態,此時A可以接收B發送的數據,但是A已不能再向B發送數據。

      從程序的角度,可以使用API來控制實現半連接狀態。

      #include int shutdown(int sockfd, int how); sockfd: 需要關閉的socket的描述符 how: 允許為shutdown操作選擇以下幾種方式: SHUT_RD(0):關閉sockfd上的讀功能,此選項將不允許sockfd進行讀操作。該套接字不再接受數據,任何當前在套接字接受緩沖區的數據將被無聲的丟棄掉。 SHUT_WR(1): 關閉sockfd的寫功能,此選項將不允許sockfd進行寫操作。進程不能在對此套接字發出寫操作。 SHUT_RDWR(2): 關閉sockfd的讀寫功能。相當于調用shutdown兩次:首先是以SHUT_RD,然后以SHUT_WR。

      使用close中止一個連接,但它只是減少描述符的引用計數,并不直接關閉連接,只有當描述符的引用計數為0時才關閉連接。

      相當于dup2(sfd, fd) 使 fd 指向sfd所指向的問價描述符(套接字),當close(fd)時,該文件描述符未關閉,而是用shutdown(fd,?SHUT_RDWR)則徹底關閉了該文件描述符。

      shutdown不考慮描述符的引用計數,直接關閉描述符。也可選擇中止一個方向的連接,只中止讀或只中止寫。

      注意:

      1)如果有多個進程共享一個套接字,close每被調用一次,計數減1,直到計數為0時,也就是所用進程都調用了close,套接字將被釋放。

      2)在多進程中如果一個進程調用了shutdown(sfd, SHUT_RDWR)后,其它的進程將無法進行通信。但,如果一個進程close(sfd)將不會影響到其它進程。

      5.?2MSL

      2MSL (Maximum Segment Lifetime) TIME_WAIT狀態的存在有兩個理由:

      1)讓4次握手關閉流程更加可靠;4次握手的最后一個ACK是是由主動關閉方發送出去的,若這個ACK丟失,被動關閉方會再次發一個FIN過來。若主動關閉方能夠保持一個2MSL的TIME_WAIT

      狀態,則有更大的機會讓丟失的ACK被再次發送出去。

      2)防止lost duplicate對后續新建正常鏈接的傳輸造成破壞。lost uplicate在實際的網絡中非常常見,經常是由于路由器產生故障,路徑無法收斂,導致一個packet在路由器A,B,C之間做類似

      死循環的跳轉。IP頭部有個TTL,限制了一個包在網絡中的最大跳數,因此這個包有兩種命運,要么最后TTL變為0,在網絡中消失;要么TTL在變為0之前路由器路徑收斂,它憑借剩余的TTL跳數終

      于到達目的地。但非常可惜的是TCP通過超時重傳機制在早些時候發送了一個跟它一模一樣的包,并先于它達到了目的地,因此它的命運也就注定被TCP協議棧拋棄。

      另外一個概念叫做incarnation connection,指跟上次的socket pair一摸一樣的新連接,叫做incarnation of previous connection。lost uplicate加上incarnation connection,則會對我們的傳輸造成致

      命的錯誤。

      TCP是流式的,所有包到達的順序是不一致的,依靠序列號由TCP協議棧做順序的拼接;假設一個incarnation connection這時收到的seq=1000, 來了一個lost duplicate為seq=1000,len=1000, 則

      【Linux C編程】第十六章 網絡編程基礎 - socket 網絡相關術語名詞解析(linux系統)

      TCP認為這個lost duplicate合法,并存放入了receive buffer,導致傳輸出現錯誤。通過一個2MSL TIME_WAIT狀態,確保所有的lost duplicate都會消失掉,避免對新連接造成錯誤。

      該狀態為什么設計在主動關閉這一方:

      1)發最后ACK的是主動關閉一方。

      2)只要有一方保持TIME_WAIT狀態,就能起到避免incarnation connection在2MSL內的重新建立,不需要兩方都有。

      如何正確對待2MSL TIME_WAIT?

      RFC要求socket pair在處于TIME_WAIT時,不能再起一個incarnation connection。但絕大部分TCP實現,強加了更為嚴格的限制。在2MSL等待期間,socket中使用的本地端口在默認情況下不能

      再被使用。

      若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666建立了連接,A主動關閉,那么在A端只要port為1234,無論對方的port和ip是什么,都不允許再起服務。這甚至比RFC限制更為嚴格,RFC僅僅是要

      求socket pair不一致,而實現當中只要這個port處于TIME_WAIT,就不允許起連接。這個限制對主動打開方來說是無所謂的,因為一般用的是臨時端口;但對于被動打開方,一般是server,就悲劇

      了,因為server一般是熟知端口。比如http,一般端口是80,不可能允許這個服務在2MSL內不能起來。

      解決方案是給服務器的socket設置SO_REUSEADDR選項,這樣的話就算熟知端口處于TIME_WAIT狀態,在這個端口上依舊可以將服務啟動。當然,雖然有了SO_REUSEADDR選項,但sockt

      pair這個限制依舊存在。比如上面的例子,A通過SO_REUSEADDR選項依舊在1234端口上起了監聽,但這時我們若是從B通過6666端口去連它,TCP協議會告訴我們連接失敗,原因為Address

      already in use.

      RFC 793中規定MSL為2分鐘,實際應用中常用的是30秒,1分鐘和2分鐘等。

      RFC (Request For Comments),是一系列以編號排定的文件。收集了有關因特網相關資訊,以及UNIX和因特網社群的軟件文件。

      做一個測試,首先啟動server,然后啟動client,用Ctrl-C終止server,馬上再運行server,運行結果:

      [root@centos socket]# ./tcp_server bind error: Address already in use

      這是因為,雖然server的應用程序終止了,但TCP協議層的連接并沒有完全斷開,因此不能再次監聽同樣的server端口。我們用netstat命令查看一下:

      [root@centos ~]# netstat -apn | grep 9999 tcp 0 0 127.0.0.1:9999 127.0.0.1:56308 FIN_WAIT2 - tcp 1 0 127.0.0.1:56308 127.0.0.1:9999 CLOSE_WAIT 8226/./tcp_client

      server終止時,socket描述符會自動關閉并發FIN段給client,client收到FIN后處于CLOSE_WAIT狀態,但是client并沒有終止,也沒有關閉socket描述符,因此不會發FIN給server,因此server的

      TCP連接處于FIN_WAIT2狀態。

      現在用Ctrl-C把client也終止掉,再觀察現象:

      [root@centos ~]# netstat -apn | grep 9999 tcp 0 0 127.0.0.1:9999 127.0.0.1:56308 TIME_WAIT - [root@centos socket]# ./tcp_server bind error: Address already in use

      client終止時自動關閉socket描述符,server的TCP連接收到client發的FIN段后處于TIME_WAIT狀態。TCP協議規定,主動關閉連接的一方要處于TIME_WAIT狀態,等待兩個MSL(maximum

      segment lifetime)的時間后才能回到CLOSED狀態,因為我們先Ctrl-C終止了server,所以server是主動關閉連接的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server端口。

      MSL在RFC 1122中規定為兩分鐘,但是各操作系統的實現不同,在Linux上一般經過半分鐘后就可以再次啟動server了。至于為什么要規定TIME_WAIT的時間,可參考UNP 2.7節。

      端口復用最常用的用途是:

      防止服務器重啟時之前綁定的端口還未釋放

      程序突然退出而系統沒有釋放端口

      在server的TCP連接沒有完全斷開之前不允許重新監聽是不合理的。因為,TCP連接沒有完全斷開指的是connfd(127.0.0.1:9999)沒有完全斷開,而我們重新監聽的是lis-tenfd(0.0.0.0:9999),

      雖然是占用同一個端口,但IP地址不同,connfd對應的是與某個客戶端通訊的一個具體的IP地址,而listenfd對應的是wildcard address。解決這個問題的方法是使用setsockopt()設置socket描述符的

      選項SO_REUSEADDR為1,表示允許創建端口號相同但IP地址不同的多個socket描述符。

      在server代碼的socket()和bind()調用之間插入如下代碼:

      int opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

      有關setsockopt可以設置的其它選項請參考UNP第7章。

      補充:

      查看網絡相關狀態信息 命令:netstat 參數: -a (all)顯示所有選項,默認不顯示LISTEN相關 -p 顯示建立相關鏈接的程序名 -n 拒絕顯示別名,能顯示數字的全部轉化成數字。 -t (tcp)僅顯示tcp相關選項 -u (udp)僅顯示udp相關選項 -l 僅列出有在 Listen (監聽) 的服務狀態

      6.?TCP異常斷開

      (1)心跳檢測機制

      在TCP網絡通信中,經常會出現客戶端和服務器之間的非正常斷開,需要實時檢測查詢鏈接狀態。常用的解決方法就是在程序中加入心跳機制。

      Heart-Beat線程

      這個是最常用的簡單方法。在接收和發送數據時個人設計一個守護進程(線程),定時發送Heart-Beat包,客戶端/服務器收到該小包后,立刻返回相應的包即可檢測對方是否實時在線。

      該方法的好處是通用,但缺點就是會改變現有的通訊協議!大家一般都是使用業務層心跳來處理,主要是靈活可控。

      UNIX網絡編程不推薦使用SO_KEEPALIVE來做心跳檢測,還是在業務層以心跳包做檢測比較好,也方便控制。

      (2)設置TCP的屬性

      SO_KEEPALIVE 保持連接檢測對方主機是否崩潰,避免(服務器)永遠阻塞于TCP連接的輸入。設置該選項后,如果2小時內在此套接口的任一方向都沒有數據交換,TCP就自動給對方發一個保

      持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會導致以下三種情況:對方接收一切正常:以期望的ACK響應。2小時后,TCP將發出另一個探測分節。對方已崩潰且已重新

      啟動:以RST響應。套接口的待處理錯誤被置為ECONNRESET,套接 口本身則被關閉。對方無任何響應:源自berkeley的TCP發送另外8個探測分節,相隔75秒一個,試圖得到一個響應。在發出第

      一個探測分節11分鐘 15秒后若仍無響應就放棄。套接口的待處理錯誤被置為ETIMEOUT,套接口本身則被關閉。如ICMP錯誤是“host unreachable(主機不可達)”,說明對方主機并沒有崩潰,但是不

      可達,這種情況下待處理錯誤被置為EHOSTUNREACH。

      根據上面的介紹我們可以知道對端以一種非優雅的方式斷開連接的時候,我們可以設置SO_KEEPALIVE屬性使得我們在2小時以后發現對方的TCP連接是否依然存在。

      keepAlive = 1; setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));

      如果我們不能接受如此之長的等待時間,從TCP-Keepalive-HOWTO上可以知道一共有兩種方式可以設置,一種是修改內核關于網絡方面的 配置參數,另外一種就是SOL_TCP字段的

      TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT三個選項。

      1. The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours). /*開始首次KeepAlive探測前的TCP空閉時間 */ 2. The tcp_keepintvl parameter specifies the interval between the nine retriesthat are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keep ntvldefaults to 150 (75 seconds). /* 兩次KeepAlive探測間的時間間隔 */ 3. The tcp_keepcnt option specifies the maximum number of keepalive probes tobe sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n s the value of the systemwide tcp_keepcnt parameter. /* 判定斷開前的KeepAlive探測次數*/

      int keepIdle = 1000; int keepInterval = 10; int keepCount = 10; Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)); Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval)); Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

      SO_KEEPALIVE設置空閑2小時才發送一個“保持存活探測分節”,不能保證實時檢測。對于判斷網絡斷開時間太長,對于需要及時響應的程序不太適應。

      當然也可以修改時間間隔參數,但是會影響到所有打開此選項的套接口!關聯了完成端口的socket可能會忽略掉該套接字選項。

      補充:判斷客戶端和服務器是否處于連接狀態?

      心跳機制

      a. 不會攜帶大量的數據;

      b. 每個一定時間服務器->客戶端/客戶端->服務器發送一個數據包

      心跳包看成一個協議

      應用層協議

      判斷網絡是否斷開

      a. 有多個連續的心跳包沒收到/沒有回復

      b. 關閉通信的套接字

      重連

      a. 重新初始套接字

      b. 繼續發送心跳包

      乒乓包

      a. 比心跳包攜帶的數據多一些

      b. 除了知道連接是否存在, 還能獲取一些信息

      七、網絡名詞術語解

      網絡相關術語名詞解析

      Linux Socket編程 TCP/IP

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:word中被剪掉的圖片可以恢復嗎?(word2010中被剪掉的圖片可以恢復嗎)
      下一篇:Excel中進行表格設置共享工作簿的操作方法(Excel共享工作簿)
      相關文章
      亚洲天堂一区二区三区| 亚洲国产精品无码专区在线观看| 亚洲精品乱码久久久久久| 亚洲第一永久AV网站久久精品男人的天堂AV | 国产亚洲人成网站观看| 亚洲综合色区在线观看| 亚洲乱码日产精品a级毛片久久| 亚洲AV无码一区二区三区国产 | 亚洲成AV人在线观看天堂无码| 久久国产成人亚洲精品影院| 亚洲成a人在线看天堂无码| 亚洲Av无码乱码在线观看性色| 国产青草亚洲香蕉精品久久| 小说区亚洲自拍另类| 色偷偷亚洲第一综合| 久久久久亚洲精品无码网址色欲| 亚洲AV无码成人精品区狼人影院 | 亚洲国产最大av| 国产成人精品日本亚洲18图| 亚洲免费闲人蜜桃| 国产精品亚洲综合五月天| 亚洲午夜一区二区三区| 亚洲欧洲国产综合AV无码久久| 亚洲日韩精品无码专区加勒比| 亚洲欧美日韩中文无线码| 亚洲狠狠婷婷综合久久蜜芽| 久久亚洲AV成人无码国产电影| 精品久久久久亚洲| 亚洲精品在线视频| 亚洲人成网77777亚洲色| 亚洲AV无码精品色午夜在线观看| 91在线精品亚洲一区二区| 亚洲日韩乱码中文无码蜜桃臀| 亚洲av永久无码精品三区在线4| 亚洲精品综合在线影院| 亚洲第一综合天堂另类专| 亚洲AV无码一区二三区| 伊人久久大香线蕉亚洲五月天| 亚洲AV日韩精品久久久久| 亚洲视频一区在线观看| 77777亚洲午夜久久多喷|