日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LWIP之TCP协议

發布時間:2025/3/15 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LWIP之TCP协议 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

IP協議提供了在各個主機之間傳送數據報的功能,但是數據的最終目的地是主機上的特定應用程序。傳輸層協議就承擔了這樣的責任,典型的傳輸層協議有UDP和TCP兩種。

UDP只為應用程序提供了一種無連接的、不可靠的傳輸服務。

TCP適用于可靠性要求很高的場合。TCP將所有數據看作數據流按照編號的順序組織起來,采用正面確認以及重傳等機制,保證數據流能全部正確到達,才把數據遞交給應用層。許多著名的上層協議都是基于TCP實現的,如DNS、HTTP、FTP、SMTP、TELNET等。

?

?

?

?

報文格式

源端口號和目的端口號:用于標識發送端和接收端應用進程

序號:從發送端到接收端的數據的第一個字節編號。新連接建立時(SYN為1),發送方隨機一個初始序號ISN

確認序號:ACK為1時有效,表示上次已成功收到數據字節序號加1

首部長度:TCP首部長度,以4字節為長度。如果沒有任何選項字段,首部長度應該為5(20字節)

6個標志比特:

窗口大小:通知發送方接收緩沖區大小,用于實現流量控制

檢驗和:保證數據正確性

緊急指針:URG為1時有效,表示報文中包含緊急數據。緊急數據始終放在數據區的開始,緊急指針定義了緊急數據結束處

?

數據流編號

在IP協議中,會對每個數據報進行編號,而在TCP中,沒有報文編號的概念,因為它的目標是數據流傳輸,數據流由連續的字節流組成,盡管在上層可能用各種各樣的數據結構和格式來描述數據,但在TCP看來,數據都是字節流。TCP把一個連接中的所有數據字節都進行編號,當然兩個方向上的編號是彼此獨立的,編號的初始值由發送數據的一方隨機選取,編號的取值范圍在0~2^32-1。

?

緊急數據

TCP是一個面向數據流的協議,應用程序需要發送的所有數據將被組織成字節流,每個字節的數據都在流中占用一個位置,并且依次發送。但是在某些特殊情況下,發送方應用程序需要發送一些緊急數據,它并不期望這些緊急數據和普通數據一樣被放在流中,而是期望接收方能優先讀取這些數據。

使用URG位置1的報文段,這種類型的報文段將告訴接收方:這里面的數據是緊急的,可以直接讀取,不必把它們放在接收緩沖里面。在包含緊急數據的報文段中,緊急數據總是放在數據區域的起始處,且報文首部中的緊急指針表明了緊急數據的最后一個字節。

?

強迫數據交互

TCP采用了緩沖機制來保證協議的高效性,在數據發送時,軟件將延遲小分組數據的交付,它將等待足夠長的時間,以期待接收更多的應用數據,最后再一起發送;在接收數據時,TCP首先是將數據放在接收緩沖中,只有在應用程序準備就緒或者TCP協議認為時機恰當的時候,數據才會交付給應用程序。

緩沖機制是出于對網絡性能提升的考慮,但是這種機制也降低了應用程序的實時性。為了解決這個問題,發送方應用程序向TCP傳遞數據時,請求推送操作,這時TCP協議不會等待發送緩沖區被填滿,而是直接將報文發送出去。同時,被推送出去的報文首部中推送位(PSH)將被置1,接收端在收到這類的報文時。會盡快將數據遞交給應用程序,而不必緩沖更多的數據再遞交。

?

LWIP對于TCP首部的定義如下

TCP首部結構體 struct tcp_hdr {PACK_STRUCT_FIELD(u16_t src); //源端口號PACK_STRUCT_FIELD(u16_t dest); //目的端口號PACK_STRUCT_FIELD(u32_t seqno); //序號PACK_STRUCT_FIELD(u32_t ackno); //確認序號PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags); //首部長度+保留位+標志位PACK_STRUCT_FIELD(u16_t wnd); //窗口大小PACK_STRUCT_FIELD(u16_t chksum); //校驗和PACK_STRUCT_FIELD(u16_t urgp); //緊急指針 } PACK_STRUCT_STRUCT;/* 標志位 */ #define TCP_FIN 0x01U //終止連接 #define TCP_SYN 0x02U //發起連接,同步序號 #define TCP_RST 0x04U //復位連接 #define TCP_PSH 0x08U //推送數據 #define TCP_ACK 0x10U //確認序號有效 #define TCP_URG 0x20U //緊急指針有效 #define TCP_ECE 0x40U //暫時LwIP不支持 #define TCP_CWR 0x80U //暫時LwIP不支持 #define TCP_FLAGS 0x3fU //TCP首部標志域

?

?

?

?

控制塊

LwIP中定義了兩種類型的TCP控制塊,一種專門用于描述處于LISTEN狀態的連接,另一種用于描述處于其他狀態的連接。

/* TCP控制塊共有字段 */ #define TCP_PCB_COMMON(type) \type *next; \ //用于將所有TCP控制塊連接成鏈表enum tcp_state state; \ //TCP控制塊狀態u8_t prio; \ //TCP控制塊優先級void *callback_arg; \ //用戶自定義數據指針u16_t local_port; \ //本地端口號DEF_ACCEPT_CALLBACK /* listen狀態TCP控制塊 */ struct tcp_pcb_listen { /* IP相關字段 */IP_PCB;/* TCP相關字段 */TCP_PCB_COMMON(struct tcp_pcb_listen); }; /* TCP控制塊 */ struct tcp_pcb {/* IP相關字段 */IP_PCB;/* TCP相關字段 */TCP_PCB_COMMON(struct tcp_pcb);/* 遠程端口號 */u16_t remote_port;/* 標志位 */u8_t flags; #define TF_ACK_DELAY ((u8_t)0x01U) //當前有ACK被延遲 #define TF_ACK_NOW ((u8_t)0x02U) //要求立即發送ACK #define TF_INFR ((u8_t)0x04U) //處于快速重傳模式 #define TF_TIMESTAMP ((u8_t)0x08U) //時間戳選項報文段 #define TF_FIN ((u8_t)0x20U) //斷開連接報文段 #define TF_NODELAY ((u8_t)0x40U) //不允許延遲發送ACK #define TF_NAGLEMEMERR ((u8_t)0x80U) //內存不足,必須盡快發送報文段騰出空間/* 下一個期望接收的編號 */u32_t rcv_nxt;/* 接收窗口大小 */u16_t rcv_wnd;/* 通告窗口大小 */u16_t rcv_ann_wnd;/* 接收通告窗口右邊界值(每次發送數據后更新) */u32_t rcv_ann_right_edge;/* 某些狀態時間 */u32_t tmr;/* poll計數器和間隔時間(polltmr超過pollinterval調用poll回調函數) */u8_t polltmr, pollinterval;/* 重傳定時器,500ms加一 */s16_t rtime; //-1表示關閉/* 最大報文段長度 */u16_t mss;/* RTT計時器,500ms加一 */u32_t rttest; //0表示關閉/* 正在RTT估算的報文段序號 */u32_t rtseq;/* RTT估算的中間值 */s16_t sa, sv;/* 報文段超時間隔 */s16_t rto;/* 已經重傳次數 */u8_t nrtx;/* 被接收方確認的最高編號(發送窗口前一個字節編號) */u32_t lastack;/* 重復確認次數 */u8_t dupacks;/* 阻塞窗口大小 */u16_t cwnd; /* 擁塞避免啟動門限 */u16_t ssthresh;/* 下一個將要發送的數據編號 */u32_t snd_nxt;/* 發送窗口大小 */u16_t snd_wnd;/* 上一次窗口更新時收到的數據序號和確認序號 */u32_t snd_wl1, snd_wl2;/* 下一個可以被應用程序緩存的編號 */u32_t snd_lbb;/* 上一次成功發送的字節數 */u16_t acked;/* 可使用的發送緩沖區大小 */u16_t snd_buf;#define TCP_SNDQUEUELEN_OVERFLOW (0xffff-3)/* 緩沖數據已占用pbuf個數 */u16_t snd_queuelen;/* 待發送報文段隊列 */struct tcp_seg *unsent;/* 待確認報文段隊列 */struct tcp_seg *unacked;/* 接收無序報文段隊列 */struct tcp_seg *ooseq;/* 指向上次成功接收但未被應用層取用的數據 */struct pbuf *refused_data;/* 發送成功回調函數 */err_t (*sent)(void *arg, struct tcp_pcb *pcb, u16_t space);/* 接收到出具回調函數 */err_t (*recv)(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);/* 連接成功回調函數 */err_t (*connected)(void *arg, struct tcp_pcb *pcb, err_t err);/* 該函數被內核周期調用 */err_t (*poll)(void *arg, struct tcp_pcb *pcb);/* 連接發生錯誤回調函數 */void (*errf)(void *arg, err_t err);/* 多久之后進行保活探測 */u32_t keep_idle;/* 保活時間間隔 */u32_t keep_intvl;/* 保活最大報文數 */u32_t keep_cnt;/* 堅持定時器計數 */u32_t persist_cnt;/* 已發送零探測報文個數 */u8_t persist_backoff;/* 已發送的保活報文數 */u8_t keep_cnt_sent; };

為了組織和描述系統內的所有TCP控制塊,內核定義了四條鏈表來連接處于不同狀態下的控制塊。

/* 已綁定本地端口號的TCP控制塊鏈表 */ struct tcp_pcb *tcp_bound_pcbs; /* 偵聽狀態的TCP控制塊鏈表 */ union tcp_listen_pcbs_t tcp_listen_pcbs; /* 其它狀態的TCP控制塊鏈表 */ struct tcp_pcb *tcp_active_pcbs; /* TIME-WAIT狀態的TCP控制塊鏈表 */ struct tcp_pcb *tcp_tw_pcbs;

?

?

?

?

連接機制

TCP協議為通信雙方提供了完善的連接建立機制和連接斷開機制。

客戶端和服務器建立連接的過程稱為三次握手過程

  • 客戶端發送一個SYN標志置1的TCP報文段,報文段首部指明自己的端口號及期望連接的服務器端口號,通常服務器端口號為一個熟知端口號,客戶端選擇的端口號通常為一個短暫端口號,可以由一TCP軟件自動分配。同時在報文段中,客戶端需要通告自己的初始序列號ISN。除此之外,這個報文中還可以攜帶一些選項字段,例如前面所說的最大報文段大小、窗口擴大因子,選項將客戶端的一些連接屬性通告給服務器。
  • 當服務器接收到該報文并解析后,返回一個SYN和ACK標志置1的報文段作為應答。ACK為1表示該報文包含了有效的確認號,這個值應該是客戶端初始序列號加1(即A+1)。另一方面,SYN標志表明服務器響應連接,并在回應報文中包含服務器自身選定的初始序號ISN(這里假設為B)。服務器可以在這個報文段中加上選項字段,告訴客戶端自己的連接屬性,同時報文首部中的窗口大小也有效,它向客戶端指明自己的接收窗口大小。
  • 當客戶端接收到服務器的SYN應答報文后,會再次產生ACK置位的報文段,該報文段包含了對服務器SYN報文段的有效確認號,該值為服務器發送的ISN加1(即B+1)。同時,該報文段中還包含了有效的窗口大小,用來向服務器指明客戶端的接收窗口大小。
  • ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    客戶端和服務器斷開連接的過程稱為四次握手過程

  • 當客戶端應用程序主動執行關閉操作時,客戶端會向服務器發送一個FIN標志置1的報文段,用來關閉從客戶端到服務器的數據傳送,假如該報文段序號字段值為C。
  • 當服務器收到這個FIN報文段后,它返回一個ACK報文,確認序號為收到的序號加1(即C+1)。和SYN一樣,一個FIN將占用一個序號。當客戶端接收到這個ACK后,從客戶端到服務器方向的連接就斷開了。
  • 服務器TCP向其上層應用程序通告客戶端的斷開操作,這會導致服務器程序關閉它的連接,同樣,此時一個FIN標志置1的報文段將被發送到客戶端,假設序號為D。
  • 客戶端也會返回一個ACK報文段(D+1)作為響應,以完成該方向連接的斷開操作。
  • ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

    客戶端和服務器復位連接

    在正常情況下,通信雙方通過使用關閉連接的握手過程來結束一個連接,但是在連接出現異常的情況下(例如TCP軟件在檢測到一個SYN報文段請求連接的端口在本地并不存在時),TCP可以直接中斷這個連接,這就是連接的復位。復位連接時,發送方將發送一個RST標志被置1的報文段,接收方對復位報文段的處理是直接終止該連接上的數據傳送,釋放發送、接收緩沖,并向應用程序通知連接復位。

    ?

    TCP為每個連接定義了11中狀態,在LwIP中,這11中狀態的定義如下

    /* TCP控制塊狀態 */ enum tcp_state {CLOSED = 0, //連接斷開LISTEN = 1, //服務器偵聽中SYN_SENT = 2, //客戶端請求連接,等待對方同意并請求連接SYN_RCVD = 3, //服務器接受連接請求,發送同意請求并請求連接,等待對方同意請求ESTABLISHED = 4, //對方同意連接請求,連接已建立FIN_WAIT_1 = 5, //主動方請求斷開,等待被動方斷開響應FIN_WAIT_2 = 6, //主動方收到斷開響應,等待被動方斷開請求CLOSE_WAIT = 7, //被動方收到斷開請求,發送斷開響應,等待應用程序關閉CLOSING = 8, //雙方同時收到斷開請求,發送斷開響應,等待斷開響應LAST_ACK = 9, //被動方收到關閉指令,發送斷開請求,等待主動方斷開響應TIME_WAIT = 10 //主動方收到斷開請求,發送斷開響應,等待2MSL };

    ?

    ?

    ?

    差錯控制(確認與重傳)

    確認與重傳,接收方通過確認的機制來告訴發送方數據的接收狀況,這是通過向發送方返回一個確認號來完成的,確認號標識出自己期望接收到的下一個字節的編號。如果接收方只收到報文段1和3,那么返回的確認號仍然是320。

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    發送方為每個報文啟動一個定時器,在指定的時間內沒有收到確認,會認為報文發送失敗,并重傳報文。超時重傳,相關的字段包括rtime、rttest、rtseq、sa、sv、rto和nrtx。rtime表示重傳定時器,它的值每500ms被內核加1,當該值超過rto時,報文段重傳將發生;rttest用于對某個報文段計時,測算該報文段在兩臺主機之間的往返時間,不同的時間點、不同的網絡狀況下,往返時間的估計都會各有差異,TCP會根據往返時間的估計值動態設置各個報文段的超時間隔rto;rtseq表示了當前正在進行往返時間估計的報文段序號;sa、sv是與超時時間rto設置密切相關的兩個字段;rto表示超時時間間隔;nrtx表示報文段被重傳的次數,也是與rto的設置密切相關。

    怎樣決定超時間隔(RTO)是提高TCP性能的關鍵,而這些都與往返時間(RTT)估計密切相關。發送方可能在某段時間內連續發送多個報文段,但只能選擇一個報文段作為基準,啟動一個定時器以測量其RTT值。TCP/IP協議利用一些優化算法平滑RTT的值,使得這些報文段測量出來的RTT值更具有效性,而在往返時間變化起伏很大時,基于均值和方差來計算RTO能提供更好的效果。

    ? ? ? ? ? ?? ?

    上面各式中,M表示某次測量的RTT值;A表示已測得的RTT平均值;D值為RTT估計的方差;g和h都為常數,一般g取1/8,h取1/4。在算法初始時,RTO取值為6,即3s;A值為0,D值為6。

    注:sa和sv是計算中間量,其中sa = 8A、sv = 4D。

    當重發后仍然收不到確認,很可能是網絡不通或者網絡阻塞。如果還使用原來的RTO重發數據包,勢必使問題越來越嚴重。參照TCP/IP標準中的算法,LwIP的做法是:如果重發的報文超時,則接下來的重發必須將RTO的值設置為前一次的兩倍,且當重發次數超過上限后,停止重發。

    注:重發報文期間不進行RTT估計。

    ?

    ?

    緩沖機制

    在發送方,上層應用程序可能會將各種各樣、大小各異的數據流交給TCP發送,TCP提供了完整的緩沖機制來提高傳送效率并減少網絡中的通信量,在少量數據發送時,協議通常會短暫延遲數據的發送時間,以緩沖到更多的用戶數據,以組成一個最佳大小的報文段發送出去;對于每個發送出去的報文,TCP不會馬上刪除它們,而是將它們保存在內部緩沖中,以便需要的時候重傳,只有等到對應的確認到達后,報文才會在緩沖區中被刪除;同理,接收方也必須維護良好的緩沖機制,因為底層的報文可能是無序到達的,這里需要把各個無序報文組織為有序數據流,重復的報文會被刪除。

    ?

    ?

    流量控制(滑動窗口)

    發送方發送報文速度很快,而接收方處理報文的速度很慢,這時在接收方會發生數據丟失的情況,丟失最終導致發送方的重傳,這使得整個連接的通信效率降低;另一方面,如果發生每次只發送很少的數據,然后等待確認的返回,而接收方在處理完數據并返回確認后,又繼續等待接收后續的數據,這樣不管是接收方還是發送方在很多時候都會處于等待狀態,連接的效率也無法得到提升。TCP中引進了滑動窗口的概念來進行流量的控制,接收數據的一方可以向發送方通告自己接收窗口的大小,告訴發送方自己的接收能力,而發送方可以根據這個窗口的大小來發送盡可能多的數據,同時又保證了接收方能夠處理這些數據。

    接收窗口,相關的字段包括rcv_nxt、rcv_wnd、rcv_ann_wnd和rcv_ann_right_edge。rcv_nxt是自己期望接收到的下一個數據字編號;rcv_wnd表示接收窗口的大小,收到數據會減小,上層取走數據則增大;rcv_ann_wnd表示將向對方通告的窗口大小值,當rcv_wnd改變時,內核會計算出一個合理的通告窗口值rcv_ann_wnd;rcv_ann_right_edge記錄了上一次窗口通告時窗口右邊界取值。

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

    發送窗口,相關的字段包括lastack、snd_nxt、snd_wnd、snd_lbb、snd_wl1和snd_wl2。lastack記錄了被接收方確認的最高序列號;snd_nxt表示自己將要發送的下一個數據的起始編號;snd_wnd記錄了當前的發送窗口大小,它常被設置為接收方通告的接收窗口值;snd_lbb記錄了下一個將被應用程序緩存的數據的起始編號;snd_wl1記錄上一次窗口更新時收到的數據序號;snd_wl2記錄上一次窗口更新時收到的確認序號。

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

    ?

    ?

    擁塞控制(慢啟動與擁塞避免)

    擁塞控制考慮的是網絡傳輸狀況。在路由器發送擁塞時,會丟棄不能處理的數據報,這將導致發送方因接收不到確認而重傳,重傳的數據同樣不會成功,且重傳會使得路由器中擁塞更為嚴重。擁塞發生時報文被丟棄,但是發送方不會得到任何報文丟失的信息,因此,發送方必須實現一種自適應機制,及時檢測網絡中的擁塞狀況,自動調節數據的發送速度,這樣才能提高數據發送的成功率。

    擁塞控制需要考慮兩種情況:

    1.在連接剛建立時,無法得知合理的報文發送速率。

    2.在產生擁塞之后,必須減小報文發送速率。

    慢啟動算法,為發送方增加了阻塞窗口,相關字段為cwnd。阻塞窗口被初始化為1。當發送方檢測到擁塞時(超時或收到重復確認),ssthresh被設置為有效發送窗口的一半,cwnd被設置為1個報文段大小。發送方取阻塞窗口與通告窗口兩者中的最小值(有效發送窗口大小)作為發送上限。每收到一個ACK,阻塞窗口增加一個報文段大小。一旦發現報文丟失,發送方就把阻塞窗口減半,直至減少至最小的窗口(至少一個報文段)。

    擁塞避免算法,相關字段為ssthresh,表示擁塞避免啟動門限。在LwIP中,客戶端初始值被設置為10個報文段大小,服務器初始值被設置為對方通告的接收窗口大小。當發送方檢測到擁塞時(超時或收到重復確認),ssthresh被設置為有效發送窗口的一半,cwnd被設置為1個報文段大小。每次收到一個確認時將cwnd增加1/cwnd,當整個窗口內的數據被確認,cwnd值只增加了1。

    如果cwnd小于或等于ssthresh,則處于慢啟動階段,否則處于擁塞避免階段。

    ?

    ?

    快速重傳與快速恢復

    兩種情況可能使發送方得到重復的ACK(能夠不斷收到重復ACK,說明網絡不存在擁塞)

  • 序號靠前的報文段丟失。
  • 各個報文在網絡中獨立路由,序號靠前的報文段較其他報文段晚到達接收端。
  • 如果網絡環境不穩定導致報文段丟失,需要重傳報文。但是TCP存在超時重傳機制,這帶來了一些新的問題

  • 當一個報文段丟失時,會等待一定的超時周期然后才重傳分組,增加了端到端的時延。
  • 當一個報文段丟失時,在等待超時的過程中,其后的報文段已經被接收端接收但卻遲遲得不到確認,發送端會認為也丟失了,從而引起不必要的重傳,既浪費資源也浪費時間。
  • 快速重傳算法,當發送機接收到三個重復確認時,TCP假定網絡環境不穩定導致報文段丟失。此時,發送方無需等待超時定時器溢出就進行重傳,TCP進入快速重傳模式。ssthresh設置為有效發送窗口的一半,cwnd設置為ssthresh + 3個報文段大小,此后每收到一個重復確認cwnd增加一個報文段大小。

    快速恢復算法,若收到了新報文段的確認,將cwnd設置為ssthresh,即后續直接執行擁塞避免算法。

    ?

    ?

    糊涂窗口與避免

    糊涂窗口綜合征(SWS):可能是由發送方引起的,也可能是由接收方引起的,最終導致網絡上產生很多的小報文段。小報文段中IP首部和TCP首部這些字段占了大部分空間,而真正的TCP數據卻很少,小報文段的傳輸浪費了網絡的大量帶寬。

    1、發送方引起的糊涂窗口綜合征

    發送端產生數據很慢,一次將一個字節的數據寫入發送端的TCP緩存。如果發送端的TCP沒有特定的指令,它就產生一個字節數據的報文段,結果有很多41字節的IP數據報就在互連網中傳來傳去。

    解決方法是:強迫發送端的TCP收集數據,然后用一個更大的數據塊來發送。如果發送端的TCP等待時間過長,就會降低實時性;等待時間不夠長,就可能發送較小的報文段。為了解決這個問題,Nagle發明了Nagle算法。

    Nagle算法(LWIP的實現方式):

    (1)如果不存在已發送未確認的報文段,則允許發送

    (2)如果處于快速重傳模式,則允許發送;

    (3)設置了TCP_NODELAY選項,則允許發送;

    (4)未發送報文段大于等于兩個,則允許發送;

    (5)第一個未發送的報文段長度達到MSS,則允許發送;

    2、接收方引起的糊涂窗口綜合征

    接收端消耗數據很慢,緩存滿了,一次消耗一個字節。接收端的TCP通告其窗口大小為1字節,發送端就發送一個字節數據的報文段,結果有很多41字節的IP數據報就在互連網中傳來傳去。

    解決辦法有兩種:

    (1)只要有數據到達就發送確認,但宣布的窗口大小為零,直到或者緩存空間已能放入具有最大長度的報文段,或者緩存空間的一半已經空了。

    (2)當數據到達時并不立即確認,直到入緩存有足夠的空間。遲延的確認還有另一個優點,它減少了通信量。但是如果延時過長,就會導致重傳;如果延時不夠長,就可能發送較小的報文段。需要用協議來平衡,如確認延遲不超過500毫秒。

    ?

    ?

    零窗口探查

    如果發送方接收到的通告窗口大小為0,則會停止報文段發送,直到接收方通告非0的窗口。但是通常這個非0窗口通告在一個不含任何數據的ACK報文中發送,并且接收方不會對ACK報文段進行確認。如果這個非0窗口通告丟失了,則雙方可能陷入互相等待。為了防止這種死鎖情況的發生,發送方使用一個堅持定時器來周期性地向接收方查詢,以便發現窗口是否已經增大。

    堅持定時器,相關的字段包括persist_cnt和persist_backoff。persist_cnt用于堅持定時器計數,當計時超過某個上限時,則發送窗口探測報文;persist_backoff是計時上限數組的索引。

    當發送窗口太小以至于不能發送下一個報文時,啟動窗口于探測。計時上限數組為1.5S、3S、6S、12S、24S、48S、60S,當persist_cnt大于數組元素個數時,時間間隔變為60S。

    若檢測到一個非0窗口,則停止窗口探查。

    ?

    ?

    保活機制

    當TCP連接已處于穩定狀態,而雙方沒有數據需要發送,則在這個連接之間不會再有任何信息交互。如果其中一方崩潰或重啟,那么原來的有效連接將變得無效。因此TCP提供一種保活機制,來進行檢測。

    1.如果雙方沒有任何數據交互,服務器默認將每兩個小時檢測一次。當然,一旦服務器收到任何數據,將重新進行計時。

    2.如果客戶端沒有響應,服務器還會默認發送9個探查報文進行檢測,每個報文默認間隔75s。

    3.如果服務器一個響應都沒有收到,就會任何客戶端已經關閉。

    注:如果客戶端崩潰并重啟,則客戶端收到探查報文后會發送復位報文,服務器收到后結束該連接。

    保活定時器,相關的字段包括keep_idle、keep_intvl、keep_cnt、keep_cnt_sent。keep_idle記錄了多久之后進行保活探測,默認2小時;keep_cnt_sent表示已經發送保活探查報文個數;keep_intvl表示保活時間間隔,默認75s;keep_cnt表示保活最大報文數,默認9次。

    ?

    ?

    ?

    TCP代碼

    /* tcp定時器節拍(周期500ms) */ u32_t tcp_ticks;/* 指數避讓數組,數據包重發后扔收不到確認,RTO值按數組值進行指數擴大 */ const u8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7}; /* 堅持定時器計數上限值 */ const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 };/* 已綁定本地端口的TCP控制塊鏈表 */ struct tcp_pcb *tcp_bound_pcbs; /* 偵聽狀態的TCP控制塊鏈表 */ union tcp_listen_pcbs_t tcp_listen_pcbs; /* 活躍(正在交互)的TCP控制塊鏈表 */ struct tcp_pcb *tcp_active_pcbs; /* 等待2MSL狀態的TCP控制塊鏈表 */ struct tcp_pcb *tcp_tw_pcbs;/* TCP控制塊臨時指針 */ struct tcp_pcb *tcp_tmp_pcb;static u8_t tcp_timer; static u16_t tcp_new_port(void);/* TCP定時器回調函數(周期250ms) */ void tcp_tmr(void) {/* 快定時器回調函數 */tcp_fasttmr();/* 慢定時器回調函數 */if (++tcp_timer & 1) {tcp_slowtmr();} }/* 關閉連接 */ err_t tcp_close(struct tcp_pcb *pcb) {err_t err;/* 根據不同的狀態做不同的處理 */switch (pcb->state) {/* CLOSED態(連接斷開) */case CLOSED:err = ERR_OK;/* 將控制塊從已綁定本地端口的TCP控制塊鏈表上移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);pcb = NULL;break;/* LISTEN態(服務器偵聽中) */case LISTEN:err = ERR_OK;/* 將控制塊從偵聽狀態的TCP控制塊鏈表刪除,釋放所有緩沖區數據 */tcp_pcb_remove((struct tcp_pcb **)&tcp_listen_pcbs.pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB_LISTEN, pcb);pcb = NULL;break;/* SYN_SENT態(客戶端請求連接,等待對方同意并請求連接) */case SYN_SENT:err = ERR_OK;/* 將控制塊從活躍(正在交互)的TCP控制塊鏈表刪除,釋放所有緩沖區數據 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);pcb = NULL;break;/* SYN_RCVD態(服務器接受連接請求,發送同意請求并請求連接,等待對方同意請求) */case SYN_RCVD:/* 構造TCP結束報文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 設置PCB為FIN_WAIT_1態(主動方請求斷開,等待被動方斷開響應) */pcb->state = FIN_WAIT_1;}break;/* ESTABLISHED態(對方同意連接請求,連接已建立) */case ESTABLISHED:/* 構造TCP結束報文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 設置PCB為FIN_WAIT_1態(主動方請求斷開,等待被動方斷開響應) */pcb->state = FIN_WAIT_1;}break;/* CLOSE_WAIT態(被動方收到斷開請求,發送斷開響應,等待應用程序關閉) */case CLOSE_WAIT:/* 構造TCP結束報文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 設置PCB為LAST_ACK態(被動方收到關閉指令,發送斷開請求,等待主動方斷開響應) */pcb->state = LAST_ACK;}break;/* 其它狀態返回錯誤 */default:err = ERR_OK;pcb = NULL;break;}/* 輸出報文段 */if (pcb != NULL && err == ERR_OK) {tcp_output(pcb);}return err; }/* 刪除TCP控制塊,可選則發送TCP復位報文 */ void tcp_abandon(struct tcp_pcb *pcb, int reset) {u32_t seqno, ackno;u16_t remote_port, local_port;struct ip_addr remote_ip, local_ip;void (* errf)(void *arg, err_t err);void *errf_arg;/* 控制塊處于TIME_WAIT狀態(主動方收到斷開請求,發送斷開響應,等待2MSL) */if (pcb->state == TIME_WAIT) {/* 將控制塊從等待2MSL狀態的TCP控制塊鏈表刪除,釋放所有緩沖區數據 */tcp_pcb_remove(&tcp_tw_pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);} /* 其它狀態下 */else {/* 下一個將要發送的數據編號 */seqno = pcb->snd_nxt;/* 下一個期望收到的數據確認號 */ackno = pcb->rcv_nxt;/* 本地IP和遠端IP */ip_addr_set(&local_ip, &(pcb->local_ip));ip_addr_set(&remote_ip, &(pcb->remote_ip));/* 本地端口號和遠端端口號 */local_port = pcb->local_port;remote_port = pcb->remote_port;/* 錯誤回調函數 */errf = pcb->errf;/* 錯誤回調函數參數 */errf_arg = pcb->callback_arg;/* 將控制塊從活躍(正在交互)的TCP控制塊鏈表中移除 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 釋放待確認隊列上的所有報文段 */if (pcb->unacked != NULL) {tcp_segs_free(pcb->unacked);}/* 釋放待發送隊列上的所有報文段 */if (pcb->unsent != NULL) {tcp_segs_free(pcb->unsent);}/* 釋放接收失序隊列上的所有報文段 */ if (pcb->ooseq != NULL) {tcp_segs_free(pcb->ooseq);}/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);/* 調用錯誤回調函數 */TCP_EVENT_ERR(errf, errf_arg, ERR_ABRT);/* 發送TCP復位報文 */if (reset) {tcp_rst(seqno, ackno, &local_ip, &remote_ip, local_port, remote_port);}} }/* 綁定本地端口(端口號最好大于0x8000,0表示自動分配端口號) */ err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port) {struct tcp_pcb *cpcb;/* 如果沒有指定端口號,自動分配一個沒有被使用的端口號 */if (port == 0) {port = tcp_new_port();}/* 遍歷偵聽狀態的TCP控制塊鏈表 */for(cpcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口號相同 */if (cpcb->local_port == port) {/* IP地址存在重復 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 該端口已經被使用,返回錯誤 */return ERR_USE;}}}/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */for(cpcb = tcp_active_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口號相同 */if (cpcb->local_port == port) {/* IP地址存在重復 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 該端口已經被使用,返回錯誤 */return ERR_USE;}}}/* 遍歷已綁定本地端口的TCP控制塊鏈表 */for(cpcb = tcp_bound_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口號相同 */if (cpcb->local_port == port) {/* IP地址存在重復 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 該端口已經被使用,返回錯誤 */return ERR_USE;}}}/* 遍歷等待2MSL狀態的TCP控制塊鏈表(進入該狀態后,等待2MSL才能釋放控制塊,端口方可重新使用) */for(cpcb = tcp_tw_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口號相同 */if (cpcb->local_port == port) {/* IP地址相同 */if (ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 該端口已經被使用,返回錯誤 */return ERR_USE;}}}/* 綁定IP地址和端口號 */if (!ip_addr_isany(ipaddr)) {pcb->local_ip = *ipaddr;}pcb->local_port = port;/* 將TCP控制塊插入已綁定本地端口的TCP控制塊鏈表 */TCP_REG(&tcp_bound_pcbs, pcb);return ERR_OK; }static err_t tcp_accept_null(void *arg, struct tcp_pcb *pcb, err_t err) {return ERR_ABRT; }/* 服務器開始偵聽 */ struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog) {struct tcp_pcb_listen *lpcb;/* 控制塊已經處于LISTEN態(服務器偵聽中),不做處理 */if (pcb->state == LISTEN) {return pcb;}/* 申請偵聽狀態PCB空間 */lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN);if (lpcb == NULL) {return NULL;}/* 將參數從原來的PCB中拷貝到偵聽狀態PCB中 *//* 用戶自定義數據指針 */lpcb->callback_arg = pcb->callback_arg;/* 本地端口號 */lpcb->local_port = pcb->local_port;/* 控制塊狀態設置為LISTEN態(服務器偵聽中) */lpcb->state = LISTEN;/* 套接字選項 */lpcb->so_options = pcb->so_options;lpcb->so_options |= SOF_ACCEPTCONN;/* 生命周期 */lpcb->ttl = pcb->ttl;/* IP服務選項 */lpcb->tos = pcb->tos;/* 本地IP */ip_addr_set(&lpcb->local_ip, &pcb->local_ip);/* 將控制塊從已綁定本地端口的TCP控制塊鏈表中移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 釋放原控制塊內存空間 */memp_free(MEMP_TCP_PCB, pcb);/* 默認接受連接回調函數 */lpcb->accept = tcp_accept_null;/* 將控制塊插入偵聽狀態的TCP控制塊鏈表 */TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb);/* 返回偵聽控制塊指針 */return (struct tcp_pcb *)lpcb; }/* 更新接收通告窗口大小 */ u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb) {/* 真實的接收窗口右邊界值 */u32_t new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd;/* 真實的窗口右邊界值,比通告窗口右邊界值大超過1個mss */if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) {/* 接收窗口通告值直接設為真實接收窗口值 */pcb->rcv_ann_wnd = pcb->rcv_wnd;/* 返回接收窗口右邊界值增加量 */return new_right_edge - pcb->rcv_ann_right_edge;} /* 真實的窗口右邊界值,比通告窗口右邊界值大不超過1個mss *//* 不使用真實接收窗口值 */else {/* 剛剛收到的數據比通告窗口還大 */if (TCP_SEQ_GT(pcb->rcv_nxt, pcb->rcv_ann_right_edge)) {/* 接收通告窗口大小設為0 */pcb->rcv_ann_wnd = 0;}/* 剛剛收到的數據沒有通告窗口大 */else {/* 縮小接收通告窗口值 */pcb->rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt;}return 0;} }/* 應用層接收對方傳來的數據 */ void tcp_recved(struct tcp_pcb *pcb, u16_t len) {int wnd_inflation;/* 更新接收窗口 */pcb->rcv_wnd += len;if (pcb->rcv_wnd > TCP_WND)pcb->rcv_wnd = TCP_WND;/* 更新接收通告窗口 */wnd_inflation = tcp_update_rcv_ann_wnd(pcb);/* 發送響應 */if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) tcp_ack_now(pcb); }/* 分配一個沒有被使用的端口號 */ static u16_t tcp_new_port(void) {struct tcp_pcb *pcb; #define TCP_LOCAL_PORT_RANGE_START 4096 #define TCP_LOCAL_PORT_RANGE_END 0x7fffstatic u16_t port = TCP_LOCAL_PORT_RANGE_START;again:/* 獲取一個端口號 */if (++port > TCP_LOCAL_PORT_RANGE_END) {port = TCP_LOCAL_PORT_RANGE_START;}/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 該端口號如果被使用,則重新獲取一個端口號 */if (pcb->local_port == port) {goto again;}}/* 遍歷等待2MSL狀態的TCP控制塊鏈表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 該端口號如果被使用,則重新獲取一個端口號 */if (pcb->local_port == port) {goto again;}}/* 遍歷偵聽狀態的TCP控制塊鏈表 */for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {/* 該端口號如果被使用,則重新獲取一個端口號 */if (pcb->local_port == port) {goto again;}}/* 返回沒有被使用的端口號 */return port; }/* 連接遠程端口 */ err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port, err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err)) {err_t ret;u32_t iss;/* 設置遠程IP和端口號 */if (ipaddr != NULL) {pcb->remote_ip = *ipaddr;} else {return ERR_VAL;}pcb->remote_port = port;/* 如果沒有綁定本地端口,則自動分配一個端口號 */if (pcb->local_port == 0) {pcb->local_port = tcp_new_port();}/* 獲得一個初始數據編號 */iss = tcp_next_iss();/* 下一個期望接收的編號,收到同步報文后會重新設置 */pcb->rcv_nxt = 0;/* 下一個將要發送的數據編號 */pcb->snd_nxt = iss;/* 被確認的最高數據編號,相當于iss前面的數據都已確認 */pcb->lastack = iss - 1;/* 下一個可以被應用程序緩存的數據編號 */pcb->snd_lbb = iss - 1;/* 接收窗口大小 */pcb->rcv_wnd = TCP_WND;/* 接收通告窗口大小 */pcb->rcv_ann_wnd = TCP_WND;/* 接收窗口右邊界值 */pcb->rcv_ann_right_edge = pcb->rcv_nxt;/* 發送窗口大小,通常設為對方的通告窗口大小 */pcb->snd_wnd = TCP_WND;/* 最大報文段大小(不超過536) */pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;/* 根據IP地址所在網絡接口最大允許數據包長度,重新計算TCP最大報文段長度 */pcb->mss = tcp_eff_send_mss(pcb->mss, ipaddr);/* 客戶端阻塞窗口,1表示大小不確定 */pcb->cwnd = 1;/* 客戶端擁塞避免啟動門限初始化為10mss */pcb->ssthresh = pcb->mss * 10;/* 控制塊狀態設為SYN_SENT態(客戶端請求連接,等待對方同意并請求連接) */pcb->state = SYN_SENT;/* 連接成功回調函數 */pcb->connected = connected;/* 將控制塊已綁定本地端口的TCP控制塊鏈表中移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 將控制塊插入活躍(正在交互)的TCP控制塊鏈表 */TCP_REG(&tcp_active_pcbs, pcb);/* 組建TCP同步報文 */ret = tcp_enqueue(pcb, NULL, 0, TCP_SYN, 0, TF_SEG_OPTS_MSS);if (ret == ERR_OK) {/* 發送報文 */tcp_output(pcb);}return ret; } /* TCP慢定時器函數(周期500ms) */ void tcp_slowtmr(void) {struct tcp_pcb *pcb, *pcb2, *prev;u16_t eff_wnd;u8_t pcb_remove;u8_t pcb_reset;err_t err;err = ERR_OK;/* TCP定時器節拍加一 */++tcp_ticks;/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */prev = NULL;pcb = tcp_active_pcbs;while (pcb != NULL) {pcb_remove = 0;pcb_reset = 0;/* 該控制塊處于SYN_SENT態(客戶端請求連接,等待對方同意并請求連接),并且同步報文重傳次數達到最大 */if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {/* 需要刪除該控制塊 */++pcb_remove;}/* 控制塊報文重傳次數達到最大 */else if (pcb->nrtx == TCP_MAXRTX) {/* 需要刪除該控制塊 */++pcb_remove;} /* 報文重傳次數沒有達到最大 */else {/* 堅持定時器已啟動 */if (pcb->persist_backoff > 0) {/* 堅持定時器計數加一 */pcb->persist_cnt++;/* 堅持定時器計數超時 */if (pcb->persist_cnt >= tcp_persist_backoff[pcb->persist_backoff - 1]) {/* 重新計數 */pcb->persist_cnt = 0;/* 零窗口探測次數加一 */if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {pcb->persist_backoff++;}/* 發送零窗口探測報文 */tcp_zero_window_probe(pcb);}} /* 堅持定時器未啟動 */else {/* 重傳定時器計數值加一 */if(pcb->rtime >= 0)++pcb->rtime;/* 有數據未確認且超時發生 */if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {/* SYN_SENT態(客戶端請求連接,等待對方同意并請求連接)不進行指數避讓 */if (pcb->state != SYN_SENT) {/* 動態計算RTO值 */pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];}/* 重傳定時器清零 */pcb->rtime = 0;/* 計算有效發送窗口大小 */eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);/* 將擁塞避免啟動門限設置為有效窗口大小的一半 */pcb->ssthresh = eff_wnd >> 1;/* 擁塞避免啟動門限至少為2mss */if (pcb->ssthresh < pcb->mss) {pcb->ssthresh = pcb->mss * 2;}/* 設置阻塞窗口大小為1mss */pcb->cwnd = pcb->mss;/* 重傳報文段 */tcp_rexmit_rto(pcb);}}}/* FIN_WAIT_2態(主動方收到斷開響應,等待被動方斷開請求),等待被動方斷開請求超時 */if (pcb->state == FIN_WAIT_2) {if ((u32_t)(tcp_ticks - pcb->tmr) > TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {/* 需要刪除該控制塊 */++pcb_remove;}}/* 啟用了保活功能并且控制塊處于ESTABLISHED態(對方同意連接請求,連接已建立),或處于CLOSE_WAIT態(被動方收到斷開請求,發送斷開響應,等待應用程序關閉) */if((pcb->so_options & SOF_KEEPALIVE) && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {/* 保活探測超時(2小時+9*75秒) */if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + (pcb->keep_cnt * pcb->keep_intvl)) / TCP_SLOW_INTERVAL){/* 需要刪除該控制塊 */++pcb_remove;/* 需要復位該連接 */++pcb_reset;}/* 觸發保活探測 */else if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + pcb->keep_cnt_sent * pcb->keep_intvl) / TCP_SLOW_INTERVAL){/* 發送保活報文 */tcp_keepalive(pcb);/* 保活報文次數加一 */pcb->keep_cnt_sent++;}}/* 失序重組超時 */if (pcb->ooseq != NULL && (u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {/* 刪除失序報文 */tcp_segs_free(pcb->ooseq);pcb->ooseq = NULL;}/* SYN_RCVD態(服務器接受連接請求,發送同意請求并請求連接,等待對方同意請求),等待客戶端同意連接超時 */if (pcb->state == SYN_RCVD) {if ((u32_t)(tcp_ticks - pcb->tmr) > TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {/* 需要刪除該控制塊 */++pcb_remove;}}/* LAST_ACK態(被動方收到關閉指令,發送斷開請求,等待主動方斷開響應) */if (pcb->state == LAST_ACK) {/* 主動方同意斷開連接響應,超過2msl */if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {/* 需要刪除該控制塊 */++pcb_remove;}}/* 需要刪除該控制塊 */if (pcb_remove) {/* 清空控制塊上各種緩沖區數據 */tcp_pcb_purge(pcb);/* 將控制塊從活躍(正在交互)的TCP控制塊鏈表移除 */if (prev != NULL) {prev->next = pcb->next;} else {tcp_active_pcbs = pcb->next;}/* 調用連接錯誤回調函數 */TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_ABRT);/* 需要發送TCP復位報文 */if (pcb_reset) {/* 發送TCP復位報文 */tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip, pcb->local_port, pcb->remote_port);}pcb2 = pcb->next;/* 釋放控制塊結構體 */memp_free(MEMP_TCP_PCB, pcb);pcb = pcb2;}/* 不需要刪除該控制塊 */else {/* 觸發poll回調函數 */++pcb->polltmr;if (pcb->polltmr >= pcb->pollinterval) {pcb->polltmr = 0;/* 調用poll回調函數 */TCP_EVENT_POLL(pcb, err);if (err == ERR_OK) {/* 輸出報文段 */tcp_output(pcb);}}prev = pcb;pcb = pcb->next;}}/* 遍歷等待2MSL狀態的TCP控制塊鏈表 */prev = NULL; pcb = tcp_tw_pcbs;while (pcb != NULL) {pcb_remove = 0;/* 在該狀態下持續時間超過2msl */if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {/* 需要刪除該控制塊 */++pcb_remove;}/* 需要刪除該控制塊 */if (pcb_remove) {/* 清空控制塊上各種緩沖區數據 */tcp_pcb_purge(pcb); /* 將控制塊從等待2MSL狀態的TCP控制塊鏈表移除 */if (prev != NULL) {prev->next = pcb->next;} else {tcp_tw_pcbs = pcb->next;}pcb2 = pcb->next;memp_free(MEMP_TCP_PCB, pcb);pcb = pcb2;} /* 不需要刪除該控制塊 */else {prev = pcb;pcb = pcb->next;}} }/* TCP塊定時器函數(周期250ms) */ void tcp_fasttmr(void) {struct tcp_pcb *pcb;/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 該控制塊有接收到的數據未被應用層取用 */if (pcb->refused_data != NULL) {err_t err;/* 調用接收回調函數 */TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);if (err == ERR_OK) {pcb->refused_data = NULL;}}/* 延遲確認最多250ms(未解決糊涂窗口綜合征) */ if (pcb->flags & TF_ACK_DELAY) {/* 立即發送響應 */tcp_ack_now(pcb);/* 清空延遲確認標志和立即確認標志 */pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);}} }/* 釋放隊列上的所有報文段 */ u8_t tcp_segs_free(struct tcp_seg *seg) {u8_t count = 0;struct tcp_seg *next;/* 遍歷整個隊列 */while (seg != NULL) {next = seg->next;/* 釋放該報文段空間 */count += tcp_seg_free(seg);seg = next;}/* 返回釋放的pbuf個數 */return count; }/* 釋放報文段空間 */ u8_t tcp_seg_free(struct tcp_seg *seg) {u8_t count = 0;/* 釋放報文段數據 */if (seg != NULL) {if (seg->p != NULL) {count = pbuf_free(seg->p);}/* 釋放報文段結構 */memp_free(MEMP_TCP_SEG, seg);}/* 返回釋放的pbuf個數 */return count; }/* 設置優先級 */ void tcp_setprio(struct tcp_pcb *pcb, u8_t prio) {pcb->prio = prio; }/* 拷貝一個新的報文段 */ struct tcp_seg *tcp_seg_copy(struct tcp_seg *seg) {struct tcp_seg *cseg;/* 為新的報文段申請空間 */cseg = memp_malloc(MEMP_TCP_SEG);if (cseg == NULL) {return NULL;}/* 將原報文段結構體拷貝到新報文段結構體中 */SMEMCPY((u8_t *)cseg, (const u8_t *)seg, sizeof(struct tcp_seg)); /* 報文段數據pbuf引用加一 */pbuf_ref(cseg->p);return cseg; }/* 默認接收回調函數 */ err_t tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {/* 有數據 */if (p != NULL) {/* 接收數據,并釋放數據包空間 */tcp_recved(pcb, p->tot_len);pbuf_free(p);} /* 沒有數據 */else if (err == ERR_OK) {/* 對方請求斷開,本方也要發送關閉連接進行握手 */return tcp_close(pcb);}return ERR_OK; }/* 釋放最低優先級(小于指定優先級)中最老的控制塊 */ static void tcp_kill_prio(u8_t prio) {struct tcp_pcb *pcb, *inactive;u32_t inactivity;u8_t mprio;mprio = TCP_PRIO_MAX;inactivity = 0;inactive = NULL;/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 找出最低優先級(小于指定優先級)中最老的控制塊 */if (pcb->prio <= prio && pcb->prio <= mprio && (u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {inactivity = tcp_ticks - pcb->tmr;inactive = pcb;mprio = pcb->prio;}}/* 回收該控制塊 */if (inactive != NULL) {tcp_abort(inactive);} }/* 回收最老的TIME-WAIT狀態的TCP控制塊 */ static void tcp_kill_timewait(void) {struct tcp_pcb *pcb, *inactive;u32_t inactivity;inactivity = 0;inactive = NULL;/* 遍歷TIME-WAIT狀態的TCP控制塊鏈表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 找出最老的TIME-WAIT狀態的TCP控制塊 */if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {inactivity = tcp_ticks - pcb->tmr;inactive = pcb;}}/* 刪除TCP控制塊,并發送TCP復位報文 */if (inactive != NULL) {tcp_abort(inactive);} }/* 分配一個TCP控制塊 */ struct tcp_pcb *tcp_alloc(u8_t prio) {struct tcp_pcb *pcb;u32_t iss;/* 為TCP控制塊申請內存 */pcb = memp_malloc(MEMP_TCP_PCB);/* 申請失敗 */if (pcb == NULL) {/* 回收最老的TIME-WAIT狀態的TCP控制塊 */tcp_kill_timewait();/* 再次為TCP控制塊申請內存 */pcb = memp_malloc(MEMP_TCP_PCB);/* 申請失敗 */if (pcb == NULL) {/* 釋放最低優先級(小于指定優先級)中最老的控制塊 */tcp_kill_prio(prio);/* 再次為TCP控制塊申請內存 */pcb = memp_malloc(MEMP_TCP_PCB);}}/* 申請成功 */if (pcb != NULL) {memset(pcb, 0, sizeof(struct tcp_pcb));/* 優先級 */pcb->prio = TCP_PRIO_NORMAL;/* 發送緩沖區大小 */pcb->snd_buf = TCP_SND_BUF;/* 發送緩沖區數據已占用pbuf個數 */pcb->snd_queuelen = 0;/* 接收窗口大小 */pcb->rcv_wnd = TCP_WND;/* 接收通告窗口大小 */pcb->rcv_ann_wnd = TCP_WND;/* IP服務類型 */pcb->tos = 0;/* 生存周期 */pcb->ttl = TCP_TTL;/* 最大報文段不得小于536 */pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;/* 報文超時重傳時間 */pcb->rto = 3000 / TCP_SLOW_INTERVAL;/* RTO計算中間變量 */pcb->sa = 0;pcb->sv = 3000 / TCP_SLOW_INTERVAL;/* 重傳定時器 */pcb->rtime = -1;/* 阻塞窗口,1表示大小不確定 */pcb->cwnd = 1;/* 初始數據編號 */iss = tcp_next_iss();/* 上一次窗口更新收到的數據確認號 */pcb->snd_wl2 = iss;/* 下一個將要發送的數據編號 */pcb->snd_nxt = iss;/* 被接收方確認的最高數據編號 */pcb->lastack = iss;/* 下一個可以被應用程序緩存的數據編號 */pcb->snd_lbb = iss;/* TCP進入某些狀態的時間點 */pcb->tmr = tcp_ticks;/* poll計數器,超過pollinterval調用poll回調函數 */pcb->polltmr = 0;/* 默認接收回調函數 */pcb->recv = tcp_recv_null;/* 空閑多久進行保活探測 */pcb->keep_idle = TCP_KEEPIDLE_DEFAULT;/* 保活探測報文間隔時間 */pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;/* 保活探測報文最多發送次數 */pcb->keep_cnt = TCP_KEEPCNT_DEFAULT;/* 已發送保活探測報文次數 */pcb->keep_cnt_sent = 0;}/* 返回控制塊指針 */return pcb; }/* 新建TCP控制塊 */ struct tcp_pcb *tcp_new(void) {return tcp_alloc(TCP_PRIO_NORMAL); }/* 設置用戶自定義參數 */ void tcp_arg(struct tcp_pcb *pcb, void *arg) { pcb->callback_arg = arg; }/* 設置接收回調函數 */ void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)) {pcb->recv = recv; }/* 設置發送成功回調函數 */ void tcp_sent(struct tcp_pcb *pcb, err_t (* sent)(void *arg, struct tcp_pcb *tpcb, u16_t len)) {pcb->sent = sent; }/* 設置連接錯誤回調函數 */ void tcp_err(struct tcp_pcb *pcb, void (* errf)(void *arg, err_t err)) {pcb->errf = errf; }/* 設置接受連接回調函數 */ void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err)) {pcb->accept = accept; }/* 設置poll回調函數和周期 */ void tcp_poll(struct tcp_pcb *pcb, err_t (* poll)(void *arg, struct tcp_pcb *tpcb), u8_t interval) {pcb->poll = poll;pcb->pollinterval = interval; }/* 清空控制塊上各種緩沖區數據 */ void tcp_pcb_purge(struct tcp_pcb *pcb) {if (pcb->state != CLOSED && pcb->state != TIME_WAIT && pcb->state != LISTEN) {/* 釋放已接收但未被應用層取用的數據 */if (pcb->refused_data != NULL) {pbuf_free(pcb->refused_data);pcb->refused_data = NULL;}/* 關閉重傳定時器 */pcb->rtime = -1;/* 釋放接收失序重組報文段 */tcp_segs_free(pcb->ooseq);pcb->ooseq = NULL;/* 釋放待發送報文段 */tcp_segs_free(pcb->unsent);/* 釋放待確認報文段 */tcp_segs_free(pcb->unacked);pcb->unacked = pcb->unsent = NULL;} }/* 將控制塊從鏈表刪除,釋放所有緩沖區數據,并將控制塊設為CLOSE態 */ void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb) {/* 將控制塊從鏈表中移除 */TCP_RMV(pcblist, pcb);/* 清空控制塊上各種緩沖區數據 */tcp_pcb_purge(pcb);/* 延遲發送的響應,發送出去 */if (pcb->state != TIME_WAIT && pcb->state != LISTEN && pcb->flags & TF_ACK_DELAY) {pcb->flags |= TF_ACK_NOW;tcp_output(pcb);}/* 將狀態設置為關閉態 */pcb->state = CLOSED; }/* 獲得一個初始數據編號 */ u32_t tcp_next_iss(void) {static u32_t iss = 6510;iss += tcp_ticks;return iss; }/* 根據IP地址所在網絡接口最大允許數據包長度,重新計算TCP最大報文段長度 */ u16_t tcp_eff_send_mss(u16_t sendmss, struct ip_addr *addr) {u16_t mss_s;struct netif *outif;/* 根據IP地址選擇一個合適(和目的主機處于同一子網)的網絡接口 */outif = ip_route(addr);/* 根據網絡接口最大允許數據包長度,重新計算mss */if ((outif != NULL) && (outif->mtu != 0)) {mss_s = outif->mtu - IP_HLEN - TCP_HLEN;sendmss = LWIP_MIN(sendmss, mss_s);}return sendmss; }

    ?

    ?

    輸出處理

    /* 設置TCP頭部 */ static struct tcp_hdr *tcp_output_set_header(struct tcp_pcb *pcb, struct pbuf *p, int optlen, u32_t seqno_be) {/* TCP頭部指針 */struct tcp_hdr *tcphdr = p->payload;/* 源端口號 */tcphdr->src = htons(pcb->local_port);/* 目的端口號 */tcphdr->dest = htons(pcb->remote_port);/* 序號 */tcphdr->seqno = seqno_be;/* 確認序號為下一次期望收到的數據編號 */tcphdr->ackno = htonl(pcb->rcv_nxt);/* 設置ACK有效標志位 */TCPH_FLAGS_SET(tcphdr, TCP_ACK);/* 接收通告窗口 */tcphdr->wnd = htons(pcb->rcv_ann_wnd);/* 緊急指針無效 */tcphdr->urgp = 0;/* 首部長度 */TCPH_HDRLEN_SET(tcphdr, (5 + optlen / 4));/* 校驗和初始化為0 */tcphdr->chksum = 0;/* 每次發送數據后,更新接收窗口右邊界值 */pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;return tcphdr; }/* 構造只帶標志位的報文(無數據) */ err_t tcp_send_ctrl(struct tcp_pcb *pcb, u8_t flags) {return tcp_enqueue(pcb, NULL, 0, flags, TCP_WRITE_FLAG_COPY, 0); }/* 發送數據 */ err_t tcp_write(struct tcp_pcb *pcb, const void *data, u16_t len, u8_t apiflags) {/* 部分狀態允許發送數據 */if (pcb->state == ESTABLISHED || pcb->state == CLOSE_WAIT || pcb->state == SYN_SENT || pcb->state == SYN_RCVD) {if (len > 0) {/* 構建報文段 */return tcp_enqueue(pcb, (void *)data, len, 0, apiflags, 0);}return ERR_OK;} else {return ERR_CONN;} }/* 構建TCP報文段 */ err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len, u8_t flags, u8_t apiflags, u8_t optflags) {struct pbuf *p;struct tcp_seg *seg, *useg, *queue;u32_t seqno;u16_t left, seglen;void *ptr;u16_t queuelen;u8_t optlen;/* 發送緩沖區大小不足以發送指定數據 */if (len > pcb->snd_buf) {pcb->flags |= TF_NAGLEMEMERR;return ERR_MEM;}/* 剩余數據長度 */left = len;/* 數據指針 */ptr = arg;optlen = LWIP_TCP_OPT_LENGTH(optflags);/* 下一個可以被應用程序緩存的數據編號 */seqno = pcb->snd_lbb;/* 發送緩沖區pbuf超過規定上限 */queuelen = pcb->snd_queuelen;if ((queuelen >= TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {pcb->flags |= TF_NAGLEMEMERR;return ERR_MEM;}/* 將所有數據組織成pbuf報文段 */useg = queue = seg = NULL;seglen = 0;while (queue == NULL || left > 0) {/* 計算合理報文段長度 */seglen = left > (pcb->mss - optlen) ? (pcb->mss - optlen) : left;/* 申請TCP報文段結構體 */seg = memp_malloc(MEMP_TCP_SEG);if (seg == NULL) {goto memerr;}seg->next = NULL;seg->p = NULL;/* 第一個報文段 */if (queue == NULL) {queue = seg;}/* 將該報文段插入報文段序列 */else {useg->next = seg;}/* 記錄最后一個報文段 */useg = seg;/* 要求將數據拷貝出來 */if (apiflags & TCP_WRITE_FLAG_COPY) {/* 為數據申請內存空間 */if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen + optlen, PBUF_RAM)) == NULL) {goto memerr;}/* 更新發送緩沖區已用pbuf個數 */queuelen += pbuf_clen(seg->p);/* 將數據拷貝出來 */if (arg != NULL) {MEMCPY((char *)seg->p->payload + optlen, ptr, seglen);}seg->dataptr = seg->p->payload;}/* 不需要拷貝 */else {/* 為可選項字段申請空間 */if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {goto memerr;}/* 更新發送緩沖區已用pbuf個數 */queuelen += pbuf_clen(seg->p);/* 為報文段申請pbuf空間 */if (left > 0) {if ((p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {pbuf_free(seg->p);seg->p = NULL;goto memerr;}/* 更新發送緩沖區已用pbuf個數 */++queuelen;/* 設置數據指針 */p->payload = ptr;seg->dataptr = ptr;/* 將可選字段pbuf和數據pbuf拼接起來 */pbuf_cat(seg->p, p);p = NULL;}}/* 發送緩沖區pbuf超過規定上限 */if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {goto memerr;}/* 報文段長度 */seg->len = seglen;/* 向前調整出TCP首部 */if (pbuf_header(seg->p, TCP_HLEN)) {goto memerr;}/* TCP首部指針 */seg->tcphdr = seg->p->payload;/* 源端口號 */seg->tcphdr->src = htons(pcb->local_port);/* 目的端口號 */seg->tcphdr->dest = htons(pcb->remote_port);/* 序號 */seg->tcphdr->seqno = htonl(seqno);/* 緊急指針 */seg->tcphdr->urgp = 0;/* 標志位 */TCPH_FLAGS_SET(seg->tcphdr, flags);/* 報文段選項屬性 */seg->flags = optflags;/* 首部長度 */TCPH_HDRLEN_SET(seg->tcphdr, (5 + optlen / 4));/* 更新剩余數據長度、序號、數據指針 */left -= seglen;seqno += seglen;ptr = (void *)((u8_t *)ptr + seglen);}/* 找出最后一個待發送報文段 */if (pcb->unsent == NULL) {useg = NULL;}else {for (useg = pcb->unsent; useg->next != NULL; useg = useg->next);}/* 待發送隊列最后一個報文段和組建的第一個報文段能夠合并(說明新組建的報文段只有一個) */if (useg != NULL && TCP_TCPLEN(useg) != 0 && !(TCPH_FLAGS(useg->tcphdr) & (TCP_SYN | TCP_FIN)) && (!(flags & (TCP_SYN | TCP_FIN)) || (flags == TCP_FIN)) && (useg->len + queue->len <= pcb->mss) && (useg->flags == queue->flags) && (ntohl(useg->tcphdr->seqno) + useg->len == ntohl(queue->tcphdr->seqno)) ) {/* 向后剝離TCP首部 */if(pbuf_header(queue->p, -(TCP_HLEN + optlen))) {goto memerr;}/* 第一個報文段沒有數據,直接釋放 */if (queue->p->len == 0) {struct pbuf *old_q = queue->p;queue->p = queue->p->next;old_q->next = NULL;queuelen--;pbuf_free(old_q);}/* 要求發送中斷連接報文 */if (flags & TCP_FIN) {/* 設置中斷連接標志位 */TCPH_SET_FLAG(useg->tcphdr, TCP_FIN);} /* 不要求發送中斷連接報文 */else {/* 將兩個報文段合并 */pbuf_cat(useg->p, queue->p);useg->len += queue->len;useg->next = queue->next;}if (seg == queue) {seg = useg;seglen = useg->len;}/* 釋放報文段結構 */memp_free(MEMP_TCP_SEG, queue);}/* 待發送隊列最后一個報文段不能和新組建的報文段合并 */else {/* 將報文段掛接到待發送鏈表尾部 */if (useg == NULL) {pcb->unsent = queue;}else {useg->next = queue;}}/* 同步報文或中止報文都占一個序號 */if ((flags & TCP_SYN) || (flags & TCP_FIN)) {++len;}if (flags & TCP_FIN) {pcb->flags |= TF_FIN;}pcb->snd_lbb += len;pcb->snd_buf -= len;/* 設置發送緩沖區已用pbuf個數 */pcb->snd_queuelen = queuelen;/* 要求推送報文 */if (seg != NULL && seglen > 0 && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);}return ERR_OK; memerr:pcb->flags |= TF_NAGLEMEMERR;if (queue != NULL) {tcp_segs_free(queue);}return ERR_MEM; }/* 發送ACK報文段(不包含任何數據) */ err_t tcp_send_empty_ack(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;u8_t optlen = 0;/* 向前調整出TCP頭部 */p = pbuf_alloc(PBUF_IP, TCP_HLEN + optlen, PBUF_RAM);if (p == NULL) {return ERR_BUF;}/* 清空延遲響應和立即響應標志位 */pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);/* 設置TCP頭部 */tcphdr = tcp_output_set_header(pcb, p, optlen, htonl(pcb->snd_nxt));/* 計算校驗和 */tcphdr->chksum = inet_chksum_pseudo(p, &(pcb->local_ip), &(pcb->remote_ip), IP_PROTO_TCP, p->tot_len);/* 調用IP輸出函數發送數據包 */ip_output(p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,IP_PROTO_TCP);/* 釋放報文段 */pbuf_free(p);return ERR_OK; }/* 輸出報文段 */ err_t tcp_output(struct tcp_pcb *pcb) {struct tcp_seg *seg, *useg;u32_t wnd, snd_nxt;/* 當前控制塊正有數據被處理,不輸出直接返回 */if (tcp_input_pcb == pcb) {return ERR_OK;}/* 有效發送窗口值設置為發送窗口和阻塞窗口較小值 */wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);/* 獲取第一個待發送報文段 */seg = pcb->unsent;/* 沒有待發送報文段,但是要求發送響應 */if (pcb->flags & TF_ACK_NOW && (seg == NULL || ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {/* 發送ACK報文段(不包含任何數據) */return tcp_send_empty_ack(pcb);}/* 找出最后一個待發送報文段 */useg = pcb->unacked;if (useg != NULL) {for (; useg->next != NULL; useg = useg->next);}/* 數據在有效發送窗口內 */while (seg != NULL && ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {/* 使用nagle判斷是否要推遲發送數據包,當產生內存不足或請求斷開連接的數據報時算法失效 */if((tcp_do_output_nagle(pcb) == 0) && ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)){break;}/* 從隊列中刪除該報文段 */pcb->unsent = seg->next;/* 設置ACK標志,清除延遲發送和立即發送標志位 */if (pcb->state != SYN_SENT) {TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);}/* 發送該報文段 */tcp_output_segment(seg, pcb);/* 發送完有效數據之后,更新下一個將要發送的數據編號 */snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {pcb->snd_nxt = snd_nxt;}/* 數據長度大于0,說明需要響應 */if (TCP_TCPLEN(seg) > 0) {/* 將報文段插入待確認隊列 */seg->next = NULL;if (pcb->unacked == NULL) {pcb->unacked = seg;useg = seg;} else {if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))){struct tcp_seg **cur_seg = &(pcb->unacked);while (*cur_seg && TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {cur_seg = &((*cur_seg)->next );}seg->next = (*cur_seg);(*cur_seg) = seg;} else {useg->next = seg;useg = useg->next;}}}/* 不需要響應,直接釋放 */else {tcp_seg_free(seg);}seg = pcb->unsent;}/* 待發送隊列不為空,并且發送窗口太小不足以發送下一個報文,并且堅持定時器未啟動 */if (seg != NULL && pcb->persist_backoff == 0 && ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > pcb->snd_wnd) {/* 啟動堅持定時器 */pcb->persist_cnt = 0;pcb->persist_backoff = 1;}pcb->flags &= ~TF_NAGLEMEMERR;return ERR_OK; }/* 輸出一個報文段 */ static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb) {u16_t len;struct netif *netif;u32_t *opts;/* 確認號為下一個期望收到的數據編號 */seg->tcphdr->ackno = htonl(pcb->rcv_nxt);/* 通告窗口大小 */seg->tcphdr->wnd = htons(pcb->rcv_ann_wnd);/* 每次發送數據后,更新控制塊接收窗口右邊界 */pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;/* 可選字段,只支持MSS選項 */opts = (u32_t *)(seg->tcphdr + 1);if (seg->flags & TF_SEG_OPTS_MSS) {TCP_BUILD_MSS_OPTION(*opts);opts += 1;}/* 未明確本地IP的,通過路由算法明確本地IP */if (ip_addr_isany(&(pcb->local_ip))) {netif = ip_route(&(pcb->remote_ip));if (netif == NULL) {return;}ip_addr_set(&(pcb->local_ip), &(netif->ip_addr));}/* 如果該控制塊的重傳定時器是關閉的,則打開 */if(pcb->rtime == -1)pcb->rtime = 0; //定時器從0開始計數/* RTT估算定時器是關閉的 */if (pcb->rttest == 0) {pcb->rttest = tcp_ticks; //打開RTT估算,記錄當前時間pcb->rtseq = ntohl(seg->tcphdr->seqno); //記錄被估算的報文編號}/* 向前調整payload,包含TCP頭部 */len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);seg->p->len -= len;seg->p->tot_len -= len;seg->p->payload = seg->tcphdr;/* 計算校驗和 */seg->tcphdr->chksum = 0;seg->tcphdr->chksum = inet_chksum_pseudo(seg->p, &(pcb->local_ip), &(pcb->remote_ip), IP_PROTO_TCP, seg->p->tot_len);/* 調用IP數據包輸出函數發送報文段 */ip_output(seg->p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos, IP_PROTO_TCP); }/* 發送TCP復位報文 */ void tcp_rst(u32_t seqno, u32_t ackno, struct ip_addr *local_ip, struct ip_addr *remote_ip, u16_t local_port, u16_t remote_port) {struct pbuf *p;struct tcp_hdr *tcphdr;/* 申請內存 */p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);if (p == NULL) {return;}/* 填寫TCP首部 */tcphdr = p->payload;tcphdr->src = htons(local_port);tcphdr->dest = htons(remote_port);tcphdr->seqno = htonl(seqno);tcphdr->ackno = htonl(ackno);TCPH_FLAGS_SET(tcphdr, TCP_RST | TCP_ACK);tcphdr->wnd = htons(TCP_WND);tcphdr->urgp = 0;TCPH_HDRLEN_SET(tcphdr, 5);tcphdr->chksum = 0;tcphdr->chksum = inet_chksum_pseudo(p, local_ip, remote_ip, IP_PROTO_TCP, p->tot_len);/* 調用IP數據包輸出函數發送報文段 */ip_output(p, local_ip, remote_ip, TCP_TTL, 0, IP_PROTO_TCP);/* 釋放數據包 */pbuf_free(p); }/* 重傳報文段 */ void tcp_rexmit_rto(struct tcp_pcb *pcb) {struct tcp_seg *seg;if (pcb->unacked == NULL) {return;}/* 將未確認數據包全部掛接到未發送數據包鏈表的頭部 */for (seg = pcb->unacked; seg->next != NULL; seg = seg->next);seg->next = pcb->unsent;pcb->unsent = pcb->unacked;pcb->unacked = NULL;/* 重發次數加一 */++pcb->nrtx;/* 重發報文期間不進行RTT估算 */pcb->rttest = 0;/* 輸出報文段 */tcp_output(pcb); }/* 重傳第一個待確認報文段 */ void tcp_rexmit(struct tcp_pcb *pcb) {struct tcp_seg *seg;struct tcp_seg **cur_seg;/* 待確認報文不為空 */if (pcb->unacked == NULL) {return;}/* 將第一個待確認報文從鏈表中移除 */seg = pcb->unacked;pcb->unacked = seg->next;/* 將待確認報文按照數據編號從小到大插入待發送報文鏈表中 */cur_seg = &(pcb->unsent);while (*cur_seg && TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {cur_seg = &((*cur_seg)->next );}seg->next = *cur_seg;*cur_seg = seg;/* 重傳次數加一 */++pcb->nrtx;/* 打開重傳計時器 */pcb->rttest = 0; }/* 啟動快速重傳機制 */ void tcp_rexmit_fast(struct tcp_pcb *pcb) {/* 有待確認數據,且當前不處于快速重傳模式 */if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) {/* 重傳第一個待確認報文段 */tcp_rexmit(pcb);/* 進入快速重傳模式,將擁塞避免啟動門限設置為有效窗口的一半 */if (pcb->cwnd > pcb->snd_wnd)pcb->ssthresh = pcb->snd_wnd / 2;elsepcb->ssthresh = pcb->cwnd / 2;/* 擁塞避免啟動門限不能小于2mss */if (pcb->ssthresh < 2*pcb->mss) {pcb->ssthresh = 2 * pcb->mss;}/* 執行慢啟動算法,已經收到3個重復ACK */pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;/* 設置快速重傳模式標志位 */pcb->flags |= TF_INFR;} }/* 發送保活探測報文 */ void tcp_keepalive(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;/* 申請內存 */p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);if(p == NULL) {return;}/* 設置TCP報文頭部(保活探測報文:序號為上一個報文序號減一) */tcphdr = tcp_output_set_header(pcb, p, 0, htonl(pcb->snd_nxt - 1));/* 檢驗和 */tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip, IP_PROTO_TCP, p->tot_len);/* 調用IP數據包輸出函數發送報文段 */ip_output(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);/* 釋放數據包 */pbuf_free(p); }/* 發送保活零窗口探測報文 */ void tcp_zero_window_probe(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;struct tcp_seg *seg;u16_t len;u8_t is_fin;/* 沒有待確認或待發送數據時,不發送零窗口探測 */seg = pcb->unacked;if(seg == NULL)seg = pcb->unsent;if(seg == NULL)return;/* TCP結束報文且不包含有效數據,則不需要進行探測,否則需要進行探測 */is_fin = ((TCPH_FLAGS(seg->tcphdr) & TCP_FIN) != 0) && (seg->len == 0);len = is_fin ? TCP_HLEN : TCP_HLEN + 1;/* 申請內存 */p = pbuf_alloc(PBUF_IP, len, PBUF_RAM);if(p == NULL) {return;}/* 設置TCP報文頭部 */tcphdr = tcp_output_set_header(pcb, p, 0, seg->tcphdr->seqno);/* TCP結束報文,設置斷開連接標志位 */if (is_fin) {TCPH_FLAGS_SET(tcphdr, TCP_ACK | TCP_FIN);}/* 零窗口探測報文,從報文段中拷貝一個字節進來 */else {*((char *)p->payload + sizeof(struct tcp_hdr)) = *(char *)seg->dataptr;}/* 計算校驗和 */tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip, IP_PROTO_TCP, p->tot_len);/* 調用IP數據包輸出函數發送報文段 */ip_output(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);/* 釋放數據包 */pbuf_free(p); }

    ?

    ?

    輸入處理

    /* 當前報文段結構體 */ static struct tcp_seg inseg; /* 當前報文段TCP首部 */ static struct tcp_hdr *tcphdr; /* 當前報文段IP首部 */ static struct ip_hdr *iphdr; /* 當前報文段序號和確認序號 */ static u32_t seqno, ackno; /* 當前報文段標志位 */ static u8_t flags; /* 當前報文段TCP長度 */ static u16_t tcplen; /* 接收標志位,存放處理結果 */ static u8_t recv_flags; /* 已經接收的數據,用于遞交給應用層 */ static struct pbuf *recv_data; /* 當前報文所屬PCB */ struct tcp_pcb *tcp_input_pcb;/* 處理報文段 */ static err_t tcp_process(struct tcp_pcb *pcb); /* 接收函數處理數據 */ static void tcp_receive(struct tcp_pcb *pcb); /* 處理報文段中選項字段 */ static void tcp_parseopt(struct tcp_pcb *pcb); /* LISTEN控制塊接收函數 */ static err_t tcp_listen_input(struct tcp_pcb_listen *pcb); /* TIME-WAIT控制塊接收函數 */ static err_t tcp_timewait_input(struct tcp_pcb *pcb);/* TCP報文輸入處理函數 */ void tcp_input(struct pbuf *p, struct netif *inp) {struct tcp_pcb *pcb, *prev;struct tcp_pcb_listen *lpcb;u8_t hdrlen;err_t err;/* IP首部指針 */iphdr = p->payload;/* TCP首部指針 */tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4);/* 向后剝離IP首部 */if (pbuf_header(p, -((s16_t)(IPH_HL(iphdr) * 4))) || (p->tot_len < sizeof(struct tcp_hdr))) {pbuf_free(p);return;}/* TCP不處理廣播包和組播包 */if (ip_addr_isbroadcast(&(iphdr->dest), inp) || ip_addr_ismulticast(&(iphdr->dest))) {pbuf_free(p);return;}/* 驗證TCP校驗和 */if (inet_chksum_pseudo(p, (struct ip_addr *)&(iphdr->src), (struct ip_addr *)&(iphdr->dest), IP_PROTO_TCP, p->tot_len) != 0) {pbuf_free(p);return;}/* 向后調整剝離TCP首部 */hdrlen = TCPH_HDRLEN(tcphdr);if(pbuf_header(p, -(hdrlen * 4))){pbuf_free(p);return;}/* 將TCP首部各字段大端轉小端 *//* 源端口號 */tcphdr->src = ntohs(tcphdr->src);/* 目的端口號 */tcphdr->dest = ntohs(tcphdr->dest);/* 數據編號 */seqno = tcphdr->seqno = ntohl(tcphdr->seqno);/* 確認號 */ackno = tcphdr->ackno = ntohl(tcphdr->ackno);/* 通告窗口大小 */tcphdr->wnd = ntohs(tcphdr->wnd);/* 標志位 */flags = TCPH_FLAGS(tcphdr);/* TCP數據長度 */tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);/* 遍歷活躍(正在交互)的TCP控制塊鏈表 */prev = NULL;for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 匹配本地和遠程端口號和IP地址,判斷該報文段是不是發送給該控制塊 */if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {if (prev != NULL) {prev->next = pcb->next;pcb->next = tcp_active_pcbs;tcp_active_pcbs = pcb;}break;}prev = pcb;}/* 沒有匹配到活躍(正在交互)的TCP控制塊 */if (pcb == NULL) {/* 遍歷等待2MSL狀態的TCP控制塊鏈表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 匹配本地和遠程端口號和IP地址,判斷該報文段是不是發送給該控制塊 */if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {/* 調用TIME-WAIT態接收函數 */tcp_timewait_input(pcb);pbuf_free(p);return;}}/* 遍歷偵聽狀態的TCP控制塊鏈表 */prev = NULL;for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {/* 匹配本地端口號和IP地址,判斷該報文段是不是發送給該控制塊 */if ((ip_addr_isany(&(lpcb->local_ip)) || ip_addr_cmp(&(lpcb->local_ip), &(iphdr->dest))) && lpcb->local_port == tcphdr->dest) {/* 將該控制塊放到鏈表首部(最近可能要經常交互數據,可以增加效率) */if (prev != NULL) {((struct tcp_pcb_listen *)prev)->next = lpcb->next;lpcb->next = tcp_listen_pcbs.listen_pcbs;tcp_listen_pcbs.listen_pcbs = lpcb;}/* 調用LISTEN態接收函數 */tcp_listen_input(lpcb);pbuf_free(p);return;}prev = (struct tcp_pcb *)lpcb;}}/* 匹配到活躍(正在交互)的TCP控制塊 */if (pcb != NULL) {/* 將數據組織成報文段 */inseg.next = NULL;inseg.len = p->tot_len;inseg.dataptr = p->payload;inseg.p = p;inseg.tcphdr = tcphdr;/* 清空當前報文段數據指針和接收處理標志 */recv_data = NULL;recv_flags = 0;/* 有數據已經接收但是未被應用層取用 */if (pcb->refused_data != NULL) {/* 調用接收回調函數 */TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);if (err == ERR_OK) {pcb->refused_data = NULL;}/* 應用程序依然沒有取用,則釋放當前接收到的數據包 */else {pbuf_free(p);return;}}/* 當前報文段所屬PCB */tcp_input_pcb = pcb;/* 處理報文段 */err = tcp_process(pcb);if (err != ERR_ABRT) {/* 收到對方復位報文 */if (recv_flags & TF_RESET) {/* 調用連接錯誤回調函數 */TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);/* 將控制塊從鏈表刪除,釋放所有緩沖區數據 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);} /* 連接成功斷開 */else if (recv_flags & TF_CLOSED) {/* 將控制塊從鏈表刪除,釋放所有緩沖區數據 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 釋放控制塊 */memp_free(MEMP_TCP_PCB, pcb);} /* 有數據被確認,調用發送回調函數 */else {err = ERR_OK;if (pcb->acked > 0) {TCP_EVENT_SENT(pcb, pcb->acked, err);}/* 有數要推送,調用接收回調函數 */if (recv_data != NULL) {if(flags & TCP_PSH) {recv_data->flags |= PBUF_FLAG_PUSH;}TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);/* 上層沒有讀取數據,將數據掛接到refused_data隊列 */if (err != ERR_OK) {pcb->refused_data = recv_data;}}/* 對方請求關閉,調用接收回調函數 */if (recv_flags & TF_GOT_FIN) {TCP_EVENT_RECV(pcb, NULL, ERR_OK, err);}tcp_input_pcb = NULL;/* 嘗試輸出 */tcp_output(pcb);}}tcp_input_pcb = NULL;/* 釋放該報文段 */if (inseg.p != NULL){pbuf_free(inseg.p);inseg.p = NULL;}}/* 沒有匹配到控制塊 */else {/* 不是rst報文 */if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) {/* 發送RST報文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}/* 釋放空間 */pbuf_free(p);} }/* LISTEN控制塊接收函數 */ static err_t tcp_listen_input(struct tcp_pcb_listen *pcb) {struct tcp_pcb *npcb;err_t rc;/* 收到ACK報文 */if (flags & TCP_ACK) {/* 發送TCP復位報文 */tcp_rst(ackno + 1, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);} /* 收到SYN報文 */else if (flags & TCP_SYN) {/* 分配一個TCP控制塊 */npcb = tcp_alloc(pcb->prio);if (npcb == NULL) {return ERR_MEM;}/* 設置控制塊的本地IP、本地端口號、遠程IP、遠程端口號 */ip_addr_set(&(npcb->local_ip), &(iphdr->dest));npcb->local_port = pcb->local_port;ip_addr_set(&(npcb->remote_ip), &(iphdr->src));npcb->remote_port = tcphdr->src;/* 設置控制塊為SYN_RCVD態(服務器接受連接請求,發送同意請求并請求連接,等待對方同意請求) */npcb->state = SYN_RCVD;/* 下一個期望收到的序號(SYN+1) */npcb->rcv_nxt = seqno + 1;/* 接收窗口右邊界值(SYN+ACK發送后會被更新) */npcb->rcv_ann_right_edge = npcb->rcv_nxt;/* 將發送窗口大小設置為對方通告窗口值 */npcb->snd_wnd = tcphdr->wnd;/* 將擁塞避免啟動門限設置為對方通告窗口值 */npcb->ssthresh = npcb->snd_wnd;/* 上一次窗口更新時收到的序號(每次snd_wnd更新后) */npcb->snd_wl1 = seqno - 1;/* 用戶數據指針 */npcb->callback_arg = pcb->callback_arg;/* 接受連接回調函數 */npcb->accept = pcb->accept;/* 套接字選項 */npcb->so_options = pcb->so_options & (SOF_DEBUG|SOF_DONTROUTE|SOF_KEEPALIVE|SOF_OOBINLINE|SOF_LINGER);/* 將控制塊加入活躍(正在交互)的TCP控制塊鏈表 */TCP_REG(&tcp_active_pcbs, npcb);/* 處理SYN中的選項字段 */tcp_parseopt(npcb);/* 根據IP地址所在網絡接口最大允許數據包長度,計算TCP最大報文段長度 */npcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip));/* 構建SYN|ACK報文 */rc = tcp_enqueue(npcb, NULL, 0, TCP_SYN | TCP_ACK, 0, TF_SEG_OPTS_MSS);if (rc != ERR_OK) {tcp_abandon(npcb, 0);return rc;}/* 發送出去 */return tcp_output(npcb);}return ERR_OK; }/* TIME-WAIT控制塊接收函數 */ static err_t tcp_timewait_input(struct tcp_pcb *pcb) {/* 收到復位報文,不處理直接退出 */if (flags & TCP_RST) {return ERR_OK;}/* 收到同步報文 */if (flags & TCP_SYN) {/* 報文段在接收窗口內 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {/* 發送TCP復位報文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);return ERR_OK;}} /* 收到斷開連接報文 */else if (flags & TCP_FIN) {/* 重新進行2MSL計時 */pcb->tmr = tcp_ticks;}/* 收到有數據的報文或者FIN或者窗口之外的SYN,立即發送響應 */if ((tcplen > 0)) {pcb->flags |= TF_ACK_NOW;return tcp_output(pcb);}return ERR_OK; }/* 處理報文段 */ static err_t tcp_process(struct tcp_pcb *pcb) {struct tcp_seg *rseg;u8_t acceptable = 0;err_t err;err = ERR_OK;/* 報文段包含復位標志 */if (flags & TCP_RST) {/* 先確定是否允許處理RST報文 *//* SYN_SENT態(客戶端請求連接,等待對方同意并請求連接) */if (pcb->state == SYN_SENT) {/* 確認序號等于下一個要發送的序號(即SYN+1) */if (ackno == pcb->snd_nxt) {/* 允許處理 */acceptable = 1;}}/* 其它狀態 */else {/* 數據編號在接收窗口內 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {/* 允許處理 */acceptable = 1;}}/* 允許處理復位報文 */if (acceptable) {/* 處理結果為對方請求復位 */recv_flags |= TF_RESET;pcb->flags &= ~TF_ACK_DELAY;return ERR_RST;} /* 不允許處理復位報文,直接退出 */else {return ERR_OK;}}/* 數據段包含同步標志,且當前不在同步握手階段,說明可能是一個重復的同步報文 */if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) { /* 立即響應 */tcp_ack_now(pcb);return ERR_OK;}/* 收到報文后,更新狀態定時器 */pcb->tmr = tcp_ticks;/* 清空已發送的保活探測報文次數 */pcb->keep_cnt_sent = 0;/* 處理可選項 */tcp_parseopt(pcb);/* 判斷PCB狀態,做不同處理 */switch (pcb->state) {/* SYN_SEN態(客戶端請求連接,等待對方同意并請求連接) */case SYN_SENT:/* SYN+ACK報文,且確認號為SYN+1,握手第2步完成 */if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {/* SYN被確認,發送緩沖區長度加一 */pcb->snd_buf++;/* 收到對方同步報文后,初始化下一個期望接收到的序號 */pcb->rcv_nxt = seqno + 1;/* 接收窗口右邊界值(ACK發送后會被更新) */pcb->rcv_ann_right_edge = pcb->rcv_nxt;/* 接收的最大確認號 */pcb->lastack = ackno;/* 將對方通告窗口大小設為發送窗口大小 */pcb->snd_wnd = tcphdr->wnd;/* 上一次窗口更新時收到的數據序號(每次變更snd_wnd時,記錄一下) */pcb->snd_wl1 = seqno - 1;/* 進入ESTABLISHED態(對方同意連接請求,連接已建立) */pcb->state = ESTABLISHED;/* 根據IP地址所在網絡接口最大允許數據包長度,重新計算TCP最大報文段長度 */pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));/* 根據新的mss重新設置擁塞避免啟動門限為10mss */pcb->ssthresh = pcb->mss * 10;/* 根據新的mss重新設置阻塞窗口為2mss */pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);/* SYN被確認,pbuf個數減一 */--pcb->snd_queuelen;/* 從未確認隊列中刪除同步報文段(隊列中第一個報文肯定是同步報文) */rseg = pcb->unacked;pcb->unacked = rseg->next;/* 未確認隊列為空,停止重傳定時器 */if(pcb->unacked == NULL)pcb->rtime = -1;/* 不為空,啟動重傳定時器 */else {pcb->rtime = 0;pcb->nrtx = 0;}/* 釋放SYN報文段空間 */tcp_seg_free(rseg);/* 調用連接成功回調函數 */TCP_EVENT_CONNECTED(pcb, ERR_OK, err);/* 立即響應,三次握手結束 */tcp_ack_now(pcb);}/* ACK報文,沒有SYN */else if (flags & TCP_ACK) {/* 直接發送復位報文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}break;/* SYN_RCVD態(服務器接受連接請求,發送同意請求并請求連接,等待對方同意請求) */case SYN_RCVD:/* ACK報文 */if (flags & TCP_ACK) {/* 確認號在已發送的窗口內 */if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {u16_t old_cwnd;/* 進入ESTABLISHED態(對方同意連接請求,連接已建立) */pcb->state = ESTABLISHED;/* 調用接受連接回調函數 */TCP_EVENT_ACCEPT(pcb, ERR_OK, err);if (err != ERR_OK) {tcp_abort(pcb);return ERR_ABRT;}/* 記錄老的阻塞窗口 */old_cwnd = pcb->cwnd;/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 確認號SYN占了一個編號,但是不占字節數,所以要調整之前計算出的成功發送的字節數 */if (pcb->acked != 0) {pcb->acked--;}/* 重新設置阻塞窗口 */pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);/* 對方請求關閉連接 */if (recv_flags & TF_GOT_FIN) {/* 立即響應 */tcp_ack_now(pcb);/* 進入CLOSE_WAIT態(被動方收到斷開請求,發送斷開響應,等待應用程序關閉) */pcb->state = CLOSE_WAIT;}}/* 錯誤確認號 */else {/* 直接發送復位報文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}}/* 收到對方重復SYN,說明對方沒有收到SYN+ACK */else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {/* 重傳SYN+ACK */tcp_rexmit(pcb);}break;/* CLOSE_WAIT(被動方收到斷開請求,發送斷開響應,等待應用程序關閉)/ESTABLISHED(對方同意連接請求,連接已建立)態 */case CLOSE_WAIT:case ESTABLISHED:/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 對方請求斷開連接 */if (recv_flags & TF_GOT_FIN) {/* 立即發送響應 */tcp_ack_now(pcb);/* 進入CLOSE_WAIT態(被動方收到斷開請求,發送斷開響應,等待應用程序關閉) */pcb->state = CLOSE_WAIT;}break;/* FIN_WAIT_1態(主動方請求斷開,等待被動方斷開響應) */case FIN_WAIT_1:/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 對方也請求斷開連接 */if (recv_flags & TF_GOT_FIN) {/* 也收到對方響應,說明斷開連接握手第3步已完成 */if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {/* 立即響應 */tcp_ack_now(pcb);/* 清空控制塊上各種緩沖區數據,并將控制塊設為CLOSE態 */tcp_pcb_purge(pcb);/* 將控制塊從tcp_active_pcbs鏈表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 進入TIME_WAIT態(主動方收到斷開請求,發送斷開響應,等待2MSL) */pcb->state = TIME_WAIT;/* 將控制塊插入tcp_tw_pcbs鏈表 */TCP_REG(&tcp_tw_pcbs, pcb);}/* 沒有收到響應,說明雙方同時執行關閉 */else {/* 立即響應 */tcp_ack_now(pcb);/* 進入CLOSING態(雙方同時收到斷開請求,發送斷開響應,等待斷開響應) */pcb->state = CLOSING;}}/* 收到ACK握手,說明斷開連接握手第2步完成 */else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {/* 進入FIN_WAIT_2態(主動方收到斷開響應,等待被動方斷開請求) */pcb->state = FIN_WAIT_2;}break;/* FIN_WAIT_2態(主動方收到斷開響應,等待被動方斷開請求) */case FIN_WAIT_2:/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 收到對方FIN握手,說明斷開連接握手第3步已完成 */if (recv_flags & TF_GOT_FIN) {/* 立即響應 */tcp_ack_now(pcb);/* 清空控制塊上各種緩沖區數據,并將控制塊設為CLOSE態 */tcp_pcb_purge(pcb);/* 將控制塊從tcp_active_pcbs鏈表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 進入TIME_WAIT態(主動方收到斷開請求,發送斷開響應,等待2MSL) */pcb->state = TIME_WAIT;/* 將控制塊插入tcp_tw_pcbs鏈表 */TCP_REG(&tcp_tw_pcbs, pcb);}break;/* CLOSING態(雙方同時收到斷開請求,發送斷開響應,等待斷開響應) */case CLOSING:/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 收到ACK握手,斷開連接 */if (flags & TCP_ACK && ackno == pcb->snd_nxt) {/* 清空控制塊上各種緩沖區數據,并將控制塊設為CLOSE態 */tcp_pcb_purge(pcb);/* 將控制塊從tcp_active_pcbs鏈表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 進入TIME_WAIT態(主動方收到斷開請求,發送斷開響應,等待2MSL) */pcb->state = TIME_WAIT;/* 將控制塊插入tcp_tw_pcbs鏈表 */TCP_REG(&tcp_tw_pcbs, pcb);}break;/* LAST_ACK態(被動方收到關閉指令,發送斷開請求,等待主動方斷開響應) */case LAST_ACK:/* 調用函數處理報文中的數據 */tcp_receive(pcb);/* 收到ACK握手 */if (flags & TCP_ACK && ackno == pcb->snd_nxt) {/* 連接關閉成功 */recv_flags |= TF_CLOSED;}break;default:break;}return ERR_OK; }/* 將報文段插入失序序列 */ static void tcp_oos_insert_segment(struct tcp_seg *cseg, struct tcp_seg *next) {struct tcp_seg *old_seg;if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {tcp_segs_free(next);next = NULL;}else {while (next && TCP_SEQ_GEQ((seqno + cseg->len), (next->tcphdr->seqno + next->len))) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {TCPH_FLAGS_SET(cseg->tcphdr, TCPH_FLAGS(cseg->tcphdr) | TCP_FIN);}old_seg = next;next = next->next;tcp_seg_free(old_seg);}if (next && TCP_SEQ_GT(seqno + cseg->len, next->tcphdr->seqno)) {cseg->len = (u16_t)(next->tcphdr->seqno - seqno);pbuf_realloc(cseg->p, cseg->len);}}cseg->next = next; }/* 接收函數處理數據 */ static void tcp_receive(struct tcp_pcb *pcb) {struct tcp_seg *next;struct tcp_seg *prev, *cseg;struct pbuf *p;s32_t off;s16_t m;u32_t right_wnd_edge;u16_t new_tot_len;int found_dupack = 0;/* 攜帶ACK字段 */if (flags & TCP_ACK) {/* 對方原接收窗口右邊界 */right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;/* 對方窗口發生變化 */if (TCP_SEQ_LT(pcb->snd_wl1, seqno) || (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) || (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd)) {/* 更新發送窗口 */pcb->snd_wnd = tcphdr->wnd;pcb->snd_wl1 = seqno;pcb->snd_wl2 = ackno;/* 窗口通告非0,停止堅持定時器 */if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0) {pcb->persist_backoff = 0;}}/* 報文段確認號在發送窗口前,可能是重復ACK */if (TCP_SEQ_LEQ(ackno, pcb->lastack)) {pcb->acked = 0;/* 報文長度為0,ACK報文不攜帶數據也不包含SYN、FIN */if (tcplen == 0) {/* 對方接收窗口右邊界不變 */if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){/* 仍有數據待確認 */if (pcb->rtime >= 0) {/* 確認號在發送窗口左邊界 */if (pcb->lastack == ackno) {/* 發現重復確認 */found_dupack = 1;/* 重復確認次數加1 */if (pcb->dupacks + 1 > pcb->dupacks)++pcb->dupacks;/* 處于快速重傳階段 */if (pcb->dupacks > 3) {/* 快速重傳階段使用慢啟動算法 */if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {pcb->cwnd += pcb->mss;}} /* 收到3次重復確認,啟動快速重傳機制 */else if (pcb->dupacks == 3) {/* 啟動快速重傳機制 */tcp_rexmit_fast(pcb);}}}}}/* 不是重復ACK */if (!found_dupack) {pcb->dupacks = 0;}}/* 新的確認,確認編號在待確認窗口中 */else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){/* 控制塊處于快重傳階段 */if (pcb->flags & TF_INFR) {/* 快速恢復,將阻塞窗口設置為擁塞避免啟動門限大小 */pcb->flags &= ~TF_INFR;pcb->cwnd = pcb->ssthresh;}/* 重發次數清零 */pcb->nrtx = 0;/* 恢復RTO值 */pcb->rto = (pcb->sa >> 3) + pcb->sv;/* 計算上次成功發送的字節數 */pcb->acked = (u16_t)(ackno - pcb->lastack);/* 擴大發送緩沖區大小 */pcb->snd_buf += pcb->acked;/* 重復確認次數清零 */pcb->dupacks = 0;/* 更新最高確認號 */pcb->lastack = ackno;/* 控制塊狀態處于連接已經建立之后 */if (pcb->state >= ESTABLISHED) {/* 慢啟動階段 */if (pcb->cwnd < pcb->ssthresh) {/* 沒收到一個響應增加一個mss */if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {pcb->cwnd += pcb->mss;}}/* 擁塞避免階段 */else {/* 沒收到一個響應增加1/cwnd個mss */u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);if (new_cwnd > pcb->cwnd) {pcb->cwnd = new_cwnd;}}}/* 移除待確認隊列上已確認的報文段 */while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno)) {next = pcb->unacked;pcb->unacked = pcb->unacked->next;/* 調整成功發送的字節數(因為FIN占一個字節) */if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {pcb->acked--;}/* 調整發送緩沖區pbuf數 */pcb->snd_queuelen -= pbuf_clen(next->p);/* 釋放已確認報文段空間 */tcp_seg_free(next);}/* 沒有數據等待確認 */if(pcb->unacked == NULL)pcb->rtime = -1; //關閉重傳定時器/* 依然有數據等待確認 */elsepcb->rtime = 0; //重置重傳定時器pcb->polltmr = 0;}/* 確認不在窗口內 */else {pcb->acked = 0;}/* 有部分重發報文段被掛在待發送隊列,確認號如果高于這些報文段序號,需要釋放 */while (pcb->unsent != NULL && TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)) {next = pcb->unsent;pcb->unsent = pcb->unsent->next;if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {pcb->acked--;}pcb->snd_queuelen -= pbuf_clen(next->p);tcp_seg_free(next);}/* RTT估算正在進行且該報文段被確認 */if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {/* 計算rto值 */m = (s16_t)(tcp_ticks - pcb->rttest);m = m - (pcb->sa >> 3);pcb->sa += m;if (m < 0) {m = -m;}m = m - (pcb->sv >> 2);pcb->sv += m;pcb->rto = (pcb->sa >> 3) + pcb->sv;/* 停止RTT估算 */pcb->rttest = 0;}}/* 數據包包含數據或FIN、SYN */if (tcplen > 0) {/* 數據頭部不在接收窗口左側,但是部分數據在窗口內 */if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)){/* 在窗口內的數據偏移量 */off = pcb->rcv_nxt - seqno;p = inseg.p;/* 剝離前面不在窗口內的數據 */if (inseg.p->len < off) {new_tot_len = (u16_t)(inseg.p->tot_len - off);while (p->len < off) {off -= p->len;p->tot_len = new_tot_len;p->len = 0;p = p->next;}if(pbuf_header(p, (s16_t)-off)) {}} else {if(pbuf_header(inseg.p, (s16_t)-off)) {}}/* 更新報文段相關數據 */inseg.dataptr = p->payload;inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;}/* 不是(數據頭部不在接收窗口內,尾部在窗口內) */else {/* 收到重復數據,直接響應 */if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){tcp_ack_now(pcb);}}/* 數據頭部在接收窗口內 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)){/* 剛好在起始處,說明這是連續報文 */if (pcb->rcv_nxt == seqno) {/* 當前報文段TCP長度 */tcplen = TCP_TCPLEN(&inseg);/* 數據尾部在窗口外 */if (tcplen > pcb->rcv_wnd) {/* 先清除FIN標志 */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);}/* 剝離窗口外的尾部數據 */inseg.len = pcb->rcv_wnd;if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);tcplen = TCP_TCPLEN(&inseg);}/* 失序報文段不為空 */if (pcb->ooseq != NULL) {/* 收到FIN */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {/* 釋放所有失序報文段 */while (pcb->ooseq != NULL) {struct tcp_seg *old_ooseq = pcb->ooseq;pcb->ooseq = pcb->ooseq->next;tcp_seg_free(old_ooseq);} } /* 沒收到FIN */else {struct tcp_seg *next = pcb->ooseq;struct tcp_seg *old_seg;/* 將報文段插入失序報文段 */while (next && TCP_SEQ_GEQ(seqno + tcplen, next->tcphdr->seqno + next->len)) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN && (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0) {TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) | TCP_FIN);tcplen = TCP_TCPLEN(&inseg);}old_seg = next;next = next->next;tcp_seg_free(old_seg);}if (next && TCP_SEQ_GT(seqno + tcplen, next->tcphdr->seqno)) {inseg.len = (u16_t)(pcb->ooseq->tcphdr->seqno - seqno);if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);tcplen = TCP_TCPLEN(&inseg);}pcb->ooseq = next;}}/* 更新下一個期望接收序號 */pcb->rcv_nxt = seqno + tcplen;/* 更新接收窗口大小 */pcb->rcv_wnd -= tcplen;/* 更新通告窗口大小 */tcp_update_rcv_ann_wnd(pcb);/* 記錄數據,用于提供給應用程序 */if (inseg.p->tot_len > 0) {recv_data = inseg.p;inseg.p = NULL;}/* 收到了FIN報文,需要發送響應包 */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {recv_flags |= TF_GOT_FIN;}/* 將后續有序的報文段都掛接到recv_data上 */while (pcb->ooseq != NULL && pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt) {cseg = pcb->ooseq;seqno = pcb->ooseq->tcphdr->seqno;pcb->rcv_nxt += TCP_TCPLEN(cseg);pcb->rcv_wnd -= TCP_TCPLEN(cseg);/* 更新通告窗口大小 */tcp_update_rcv_ann_wnd(pcb);if (cseg->p->tot_len > 0) {if (recv_data) {pbuf_cat(recv_data, cseg->p);} else {recv_data = cseg->p;}cseg->p = NULL;}/* 收到了FIN報文,需要發送響應包 */if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {recv_flags |= TF_GOT_FIN;/* 已經建立的連接要進入CLOSE_WAIT */if (pcb->state == ESTABLISHED) {pcb->state = CLOSE_WAIT;} }pcb->ooseq = cseg->next;tcp_seg_free(cseg);}/* 發送響應 */tcp_ack(pcb);}/* 頭部不是窗口起始處,說明這是失序報文 */else {/* 發送ACK報文段(不包含任何數據) */tcp_send_empty_ack(pcb);/* 將報文段插入失序隊列 */if (pcb->ooseq == NULL) {pcb->ooseq = tcp_seg_copy(&inseg);} else {prev = NULL;for(next = pcb->ooseq; next != NULL; next = next->next) {if (seqno == next->tcphdr->seqno) {if (inseg.len > next->len) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {if (prev != NULL) {prev->next = cseg;} else {pcb->ooseq = cseg;}tcp_oos_insert_segment(cseg, next);}break;} else {break;}} else {if (prev == NULL) {if (TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {pcb->ooseq = cseg;tcp_oos_insert_segment(cseg, next);}break;}} else {if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1)) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno)) {prev->len = (u16_t)(seqno - prev->tcphdr->seqno);pbuf_realloc(prev->p, prev->len);}prev->next = cseg;tcp_oos_insert_segment(cseg, next);}break;}}if (next->next == NULL && TCP_SEQ_GT(seqno, next->tcphdr->seqno)) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {break;}next->next = tcp_seg_copy(&inseg);if (next->next != NULL) {if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno)) {next->len = (u16_t)(seqno - next->tcphdr->seqno);pbuf_realloc(next->p, next->len);}}break;}}prev = next;}}}}/* 數據頭部不在接收窗口內 */else {/* 不在窗口內的報文段 */tcp_send_empty_ack(pcb);}}/* 數據包不包含數據 */else {/* 不在窗口內的報文 */if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1)){/* 返回確認 */tcp_ack_now(pcb);}} }/* 處理報文段中選項字段 */ static void tcp_parseopt(struct tcp_pcb *pcb) {u16_t c, max_c;u16_t mss;u8_t *opts, opt;/* 選項字段指針 */opts = (u8_t *)tcphdr + TCP_HLEN;/* 首部長度大于20字節 */if(TCPH_HDRLEN(tcphdr) > 0x5) {/* 選項字段長度(單位字節) */max_c = (TCPH_HDRLEN(tcphdr) - 5) << 2;/* 一個一個選項進行解析 */for (c = 0; c < max_c; ) {opt = opts[c];switch (opt) {/* 結束選項字段 */case 0x00:return;/* 空操作 */case 0x01:++c;break;/* 最大報文段大小 */case 0x02:if (opts[c + 1] != 0x04 || c + 0x04 > max_c) {return;}mss = (opts[c + 2] << 8) | opts[c + 3];pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;c += 0x04;break;/* 不支持其它選項 */default:if (opts[c + 1] == 0) {return;}/* 跳過選項長度 */c += opts[c + 1];}}} }

    ?

    總結

    以上是生活随笔為你收集整理的LWIP之TCP协议的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 亚洲一区二区三区在线视频 | 欧美一区二区人人喊爽 | ,午夜性刺激免费看视频 | 久久国产99 | 国产视频在线观看一区二区 | 欧美午夜精品一区二区三区电影 | 成人精品在线看 | 男女在线视频 | 天天看视频 | 美女在线观看视频 | 国产精品大屁股白浆一区 | 青青草视频国产 | 九色国产精品 | 美腿丝袜一区二区三区 | 白丝校花扒腿让我c | 日韩在线 中文字幕 | 国产一级美女 | 国产精品第6页 | 国产欧美日韩三区 | 中文字幕第十一页 | 免费在线观看黄色av | 动漫同人高h啪啪爽文 | 字幕网在线观看 | 精品无码黑人又粗又大又长 | 玖玖爱在线观看 | 欧美日韩在线视频一区 | 婷婷综合五月 | 亚洲精品一区久久久久久 | 亚洲av成人片色在线观看高潮 | 毛片黄色片 | 在线观看免费高清在线观看 | 完全免费在线视频 | 91成年视频 | 国产乱淫视频 | 欧美做受高潮动漫 | 宅男午夜在线 | 日韩av在线第一页 | 欧美中文字幕在线播放 | 在线免费小电影 | 中文字幕在线观看91 | 韩日av| 国产夫妻性生活 | 国产操女人 | se日韩| 制服丝袜一区 | www.av成人 | 欧美伊人影院 | 成人免费黄色片 | 久久av一区二区三区 | 最新av网址在线观看 | 日韩一区二区视频在线播放 | 欧美视频免费看欧美视频 | 免费毛片网 | 姑娘第5集在线观看免费好剧 | 色偷偷91 | 波多野结衣中文在线 | 男男野外做爰全过程69 | 五月丁香啪啪 | 人妻换人妻仑乱 | 嫩草视屏 | 人人射 | 97视频国产 | 视频在线国产 | 免费成人一级片 | 精品人成 | 国产一区二区三区四区在线观看 | 在线观看av中文字幕 | 免费精品一区 | 91娇羞白丝网站 | 国产麻豆a毛片 | 成人免费午夜视频 | 中文字幕在线一区二区三区 | 成人三级黄色片 | av第一福利大全导航 | 性色视频 | 色呦呦网站入口 | 久久免费视频1 | 欧美熟妇另类久久久久久不卡 | 国产无人区码熟妇毛片多 | 性免费网站 | 日本视频一区二区三区 | 在线视频福利 | 国产xxxx做受性欧美88 | 久热一区 | 久久成人在线视频 | 中文字幕在线观看1 | 色婷婷av一区| 国产精品1234区 | jizzjizz在线 | 黑人干亚洲女 | 国产三级在线观看 | 免费亚洲网站 | 蜜桃久久av | 精品人妻一区二区三区四区在线 | 中文字幕在线观看二区 | 4438亚洲 | 国产免费视频一区二区三区 | 日韩午夜在线观看 | 欧美成人精品一区二区免费看片 |