運輸層UDP、TCP網絡概念辨析以及socket編程流程圖示

OUTLINEhtml

  • udp和tcp區別linux

  • udp碰見包丟失怎麼辦,設計一下編程

  • udp的包若是出現錯誤怎麼辦?上層保證嗎安全

    • 差錯檢測能夠百分百檢錯嗎
    • 有哪些校驗和計算方法
  • TCP如何保證可靠傳輸、沾包服務器

  • Linux下高併發socket最大鏈接數網絡

    • linux下高性能網絡框架,一個服務器連成千上萬socket怎麼辦
  • RFID項目中服務器須要連多個客戶,如何receive消息。併發

1 UDP和TCP區別

網絡層爲主機之間提供邏輯通訊,運輸層爲應用進程之間提供端到端的邏輯通訊。[1]框架

根據應用程序的不一樣需求,運輸層須要兩種協議:dom

  • 面向鏈接的TCP(transmission control protocol)協議
  • 無鏈接的UDP(User datagram protocol)協議

二者區別在於:異步

  • UDP是無鏈接的,TCP是面向鏈接的
  • UDP盡最大努力交付報文,TCP保證可靠交付。
  • UDP是基於報文的,並不合併或拆分上層交下來的報文,而是添加首部後交給IP層。TCP是基於byte流的。

[2]從更多的角度比較:

UDP是message-based無鏈接協議,也就是發送數據以前不須要創建專用的端到端鏈接。傳輸數據的時候也不須要檢查接收方的readiness和狀態。

  • 1)不保證交付

UDP message發送以後便沒法知道其狀態了,有可能順利到達接收方,也可能丟失。UDP沒有確認收到、重傳、超時這些概念。

  • 2)無序的
  • 3)輕量級,只是在IP的數據報服務之上增長了不多一點功能[1]
  • 4)datagrams

Packets相互獨立發送,而且各自有明確的邊界。若是發送方一個packet,那接收方必定原樣收到。

  • 5)無擁塞控制

UDP自己不提供擁塞控制,若有必要,擁塞控制方法應該在應用層實現。

  • 6)支持廣播

而TCP是connection-oriented協議。意味着通訊以前必須經過三次握手創建端到端鏈接。鏈接創建後,雙方均可以經過這個鏈接發送數據。特色是

  • 1)可靠交付

這裏的可靠只限於傳輸層。在應用層,仍然須要一個獨立的acknowledgement flow control。

TCP管理message確認、重傳和超時。

這裏提到If it gets lost along the way, the server will re-request the lost part.這和[1]中提到可靠傳輸過程不符: 接收方不須要請求重傳某個出錯的分組。由於發送方每發完一個分組會設置一個超時計時器,若是超時計時器到期,發送方沒有收到接收方對前一個分組的確認。那麼發送方會自動重傳。

在TCP中只有兩種狀況:1. 全部數據正確發送 2. 網絡已經斷掉,超時屢次。

  • 2)有序的
  • 3)重量級,支持可靠交付和擁塞控制
  • 4)數據以stream的方式讀入。沒有message(segment)的明顯邊界

1.1 辨析data、packet、datagram、message、segment、frame

data的範圍最廣,什麼均可以叫作data,說發送data確定是不會錯的。

首先討論這些名詞的聯繫,他們均可以叫作PDU(protocol data unit)[3],也就是通訊協議使用的數據單元。

[1]中使用的是TPDU(transport protocol data unit)。

因此他們的區別在於所在的協議不一樣。

在傳輸層叫segment(報文段)

在網絡層叫datagram(數據報)

在數據鏈路層叫frame(幀)

他們能夠統稱爲packet(分組)。

我的認爲message和packet同義,好比[3]中這幾句能夠證實:

But in other cases the term "segment" includes the whole TCP message, including the TCP headers. A single whole IP message is a "datagram". The original IP RFC refers to link-layer messages as "packets".

2 包丟失的處理

UDP不保證可靠交付,像擁塞控制同樣,若有須要應該由應用層實現。

3 差錯檢測

數據從發送方到接收方可能會產生一些隨即錯誤,差錯檢測指檢測出錯誤數據[4]。

而糾錯的方式有兩種:

  • 1)Automatic repeat request (ARQ),網絡中用的這種。
  • 2)Forward error correction (FEC)

差錯檢測方式(Error detection schemes)包括:

  • 1 Repetition codes
  • 2 Parity bits
  • 3 Checksums
  • 4 Cyclic redundancy checks (CRCs)
  • 5 Cryptographic hash functions
  • 6 Error-correcting codes

其中checksum是經過某種算術公式把原始數據算出算術和。

CRC是不安全的哈希函數,用來檢測數據中隨機錯誤。

UDP和TCP的校驗和不只要對整個IP協議負載(包括UDP/TCP協議頭和UDP/TCP協議負載)進行計算,還要先對一個僞協議頭進行計算:先要填充僞首部各個字段,而後再將UDP/TCP報頭及以後的數據附加到僞首部的後面,再對僞首部使用校驗和計算,所獲得的值纔是UDP/TCP報頭部分的校驗和。[5]

具體計算方法是:

1)首先把校驗和字段清零;

2)而後對每 16 位(2 字節)進行二進制反碼求和;

反碼求和時,最高位的進位要進到最低位,也就是循環進位。

接收方進行校驗:按二進制反碼求這些16bit字的和。若不差錯,結果應爲全1

4 TCP可靠傳輸

TCP下面的網絡提供的是不可靠的傳輸,因此TCP必須採起適當的措施使得兩個運輸層之間的通訊變得可靠。[1]

傳輸過程可能出現兩方面問題:

  • 信道產生差錯
  • 接收方來不及處理收到的數據

TCP經過中止等待協議、滑動窗口來規避以上問題。

其中,中止等待協議指每發送完一個包就中止發送,等待對方確認。 中止等待協議的設計採用超時重傳機制。

連續ARQ協議是滑動窗口的簡化模型。

附:鏈接創建三次握手,鏈接釋放四次揮手示意圖。

5 Linux下高併發socket最大鏈接數

5.1 限制最大鏈接數的一些因素,以及去除各類限制的方法。[6]

去除各類限制後,理論上單獨一個進程最多能夠同時創建60000多個TCP客戶端鏈接。

注意,做者server端口範圍方面的觀點有誤,具體見5.2

原文摘要:

  • 1)用戶進程可打開文件數限制

在Linux平臺上,不管編寫客戶端程序仍是服務端程序,在進行高併發TCP鏈接處理時,最高的併發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是由於系統爲每一個TCP鏈接都要建立一個socket句柄,每一個socket句柄同時也是一個文件句柄)。

$ ulimit -n
1024

這表示當前用戶的每一個進程最多容許同時打開1024個文件

  • 2)網絡內核對TCP鏈接的有關限制

    • 內核對本地端口號範圍的限制(誤)
    • Linux網絡內核的IP_TABLE防火牆對最大跟蹤的TCP鏈接數的限制
  • 3)使用支持高併發網絡I/O的編程技術

在Linux上編寫高併發TCP鏈接應用程序時,必須使用合適的網絡I/O技術和I/O事件分派機制。

可用的I/O技術有同步I/O,非阻塞式同步I/O(也稱反應式I/O),以及異步I/O。

在高TCP併發的情形下,若是使用同步I/O,這會嚴重阻塞程序的運轉,除非爲每一個TCP鏈接的I/O建立一個線程。可是,過多的線程又會因系統對線程的調度形成巨大開銷。所以,在高TCP併發的情形下使用同步 I/O是不可取的,這時能夠考慮使用非阻塞式同步I/O或異步I/O。非阻塞式同步I/O的技術包括使用select(),poll(),epoll等機制。異步I/O的技術就是使用AIO。

從I/O事件分派機制來看,使用select()是不合適的,由於它所支持的併發鏈接數有限(一般在1024個之內)。若是考慮性能,poll()也是不合適的,儘管它能夠支持的較高的TCP併發數,可是因爲其採用「輪詢」機制,當併發數較高時,其運行效率至關低,並可能存在I/O事件分派不均,致使部分TCP鏈接上的I/O出現「飢餓」現象。而若是使用epoll或AIO,則沒有上述問題(早期Linux內核的AIO技術實現是經過在內核中爲每一個 I/O請求建立一個線程來實現的,這種實現機制在高併發TCP鏈接的情形下使用其實也有嚴重的性能問題。但在最新的Linux內核中,AIO的實現已經獲得改進)。

綜上所述,在開發支持高併發TCP鏈接的Linux應用程序時,應儘可能使用epoll或AIO技術來實現併發的TCP鏈接上的I/O控制,這將爲提高程序對高併發TCP鏈接的支持提供有效的I/O保證。

5.2 [6]的錯誤在於服務端程序只偵聽一個端口,併發鏈接數量與server可用端口數無關

[6]末尾7樓評論也指出了這個錯誤。標識一條連接是五個部分組成的:[協議,本地ip,本地端口,遠端ip,遠端端口]。對於服務器而言只用一個端口就能夠承載多個連接

[10]對一個TCP服務程序能夠支持多少併發 TCP 鏈接進行了嚴密的討論,並給出程序測試。我認爲這個是正確答案。

一個進程同時打開的文件數目的最大值在實踐中是正確的,不符合原題意。

拋開操做系統層面,只考慮 TCP/IP 層面。創建一個 TCP 鏈接有開銷僅僅是三次握手過程當中發送一些報文而已。

TCP 鏈接是虛擬的鏈接,不是電路鏈接,維持 TCP 鏈接理論上不佔用網絡資源(會佔用兩頭程序的系統資源)。只要鏈接的雙方認爲 TCP 鏈接存在,而且能夠互相發送 IP packet,那麼 TCP 鏈接就一直存在。

因此單獨談論「TCP 併發鏈接數」是沒有意義的,由於鏈接數基本上是要多少有多少。更有意義的性能指標或許是:「每秒鐘收發多少條消息」、「每秒鐘收發多少字節的數據」、「支持多少個活動的併發客戶」等等。

該問題最終答案:

在只考慮 IPv4 的狀況下,併發數的理論上限是 2^48(客戶端 IP 的上限是 2^32 個,每一個客戶端IP發起鏈接的上限是 2^16)。考慮某些 IP 段被保留了,這個上界可適當縮小,但數量級不變。實際的限制是操做系統全局文件描述符的數量,以及內存大小。

6 RFID項目中對多個socket的處理

6.1 一個完整的TCP客戶-服務端程序須要的基本socket函數

在APUE中[7],C++版本socket函數以下:

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

domain: AF_INET/AF_INET6/AF-UNIX/AF_UPSPEC 協議域,又稱協議族

type: SOCK_DGRAM / SOCK_RAW / SOCK_SEQPACKET / SOCK_STREAM 規定數據的傳輸方式

protocol: IPPROTO_IP/IPPROTO_IPV6/IPPROTO_ICMP/IPPROTO_RAW/IPPROTO_TCP/IPPROTO_UDP 指定爲因特網域套接字定義的協議,一般爲0,自動選擇type類型對應的默認協議。

  • 2 int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

把本地協議地址賦給socket

  • 3 int listen(int sockfd, int backlog); //only called by server

backlog 指定最大容許接入的鏈接數量。

  • 4 int connect(int sockfd, const struct sockaddr *addr, socklen_t len); //only called by client
  • 5 int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len); //only called by server
  • 6 int close(int fg);
  • 7 int shutdown(int sockfd, int howto);

對於於[8]和[9]中的C#版函數以下:

  • 1 Socket(AddressFamily, SocketType, ProtocolType);
  • 2 socketobj.Bind(IPEndPoint);
  • 3 socketobj.Listen(int);
  • 4 socketobj.Connect(IPEndPoint);
  • 5 socketobj.Accept();
  • 6 關閉並釋放資源
socketobj.Shutdown(SocketShutdown.Both);
socketobj.Close();

6.2 [8]和[9]原理圖示

[8]是單用戶模式,server只能連一個client,當鏈接創建後,server便不在監聽。

雙方都須要兩個線程:一個用來接收報文,另外一個用來發送報文。咱們分別用Threc和Thsend表示

要改爲[9]中的多用戶模式只須要保持server的監聽狀態,接着對每一個新鏈接成功的client,存儲其配套的socket、Threc、Thsend。

假設有c一、c二、c3三個client鏈接到server,會創建三個socket:connSocket1,connSocket2,connSocket3

socket爲client和server提供endpoint到endpoint的通訊。其中endpoint格式爲:ip:port

三個socket在server端的endpoint相同,在client端的endpoint不一樣(ip相同,port不一樣)

因此咱們能夠用client端的endpoint來標識每一個socket。只須要把endpoint做爲key,socket、Threc、Thsend做爲value存在字典裏面就能夠靈活處理每一個每一個socket鏈接了。

References