經(jīng)過前面幾個實(shí)驗(yàn)的鋪墊,終于到了將他們組合起來的時候了。Lab4 將實(shí)現(xiàn) TCP Connection 功能,內(nèi)部含有 TCPReceiver
和 TCPSender
,可以與 TCP 連接的另一個端點(diǎn)進(jìn)行數(shù)據(jù)交換。
(資料圖)
簡單來說,這次實(shí)驗(yàn)就是要在 TCPConnection
類中實(shí)現(xiàn)下圖所示的有限狀態(tài)機(jī):
這些狀態(tài)對應(yīng) TCPState
的內(nèi)部枚舉類 State
:
//! \brief Official state names from the [TCP](\ref rfc::rfc793) specificationenum class State { LISTEN = 0, //!< Listening for a peer to connect SYN_RCVD, //!< Got the peer"s SYN SYN_SENT, //!< Sent a SYN to initiate a connection ESTABLISHED, //!< Three-way handshake complete CLOSE_WAIT, //!< Remote side has sent a FIN, connection is half-open LAST_ACK, //!< Local side sent a FIN from CLOSE_WAIT, waiting for ACK FIN_WAIT_1, //!< Sent a FIN to the remote side, not yet ACK"d FIN_WAIT_2, //!< Received an ACK for previously-sent FIN CLOSING, //!< Received a FIN just after we sent one TIME_WAIT, //!< Both sides have sent FIN and ACK"d, waiting for 2 MSL CLOSED, //!< A connection that has terminated normally RESET, //!< A connection that terminated abnormally};
除了三次握手和四次揮手外,我們還得處理報(bào)文段首部 RST
標(biāo)志被置位的情況,這時候應(yīng)該將斷開連接,并將內(nèi)部的輸入流和輸入流標(biāo)記為 error
,此時的 TCPState
應(yīng)該是 RESET
。
先在類聲明里面加上一些成員:
class TCPConnection { private: TCPConfig _cfg; TCPReceiver _receiver{_cfg.recv_capacity}; TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn}; //! outbound queue of segments that the TCPConnection wants sent std::queue _segments_out{}; //! Should the TCPConnection stay active (and keep ACKing) //! for 10 * _cfg.rt_timeout milliseconds after both streams have ended, //! in case the remote TCPConnection doesn"t know we"ve received its whole stream? bool _linger_after_streams_finish{true}; bool _is_active{true}; size_t _last_segment_time{0}; /** * @brief 發(fā)送報(bào)文段 * @param fill_window 是否填滿發(fā)送窗口 */ void send_segments(bool fill_window = false); // 發(fā)送 RST 報(bào)文段 void send_rst_segment(); // 中止連接 void abort(); public: // 省略其余成員}
接著實(shí)現(xiàn)幾個最簡單的成員函數(shù):
size_t TCPConnection::remaining_outbound_capacity() const { return _sender.stream_in().remaining_capacity(); }size_t TCPConnection::bytes_in_flight() const { return _sender.bytes_in_flight(); }size_t TCPConnection::unassembled_bytes() const { return _receiver.unassembled_bytes(); }size_t TCPConnection::time_since_last_segment_received() const { return _last_segment_time; }bool TCPConnection::active() const { return _is_active; }
主動連接客戶端可以調(diào)用 TCPConnection::connect
函數(shù)發(fā)送 SYN
報(bào)文段請求與服務(wù)端建立連接,由于 Lab3 中實(shí)現(xiàn)的 TCPSender::fill_window()
函數(shù)會根據(jù)發(fā)送方的狀態(tài)選擇要發(fā)送的報(bào)文段類型,在還沒建立連接的情況下,這里直接調(diào)用 fill_window()
就會將一個 SYN
報(bào)文段放在隊(duì)列中,我們只需將其取出放到 TCPConnection
的 _segments_out
隊(duì)列中即可:
void TCPConnection::connect() { // 發(fā)送 SYN send_segments(true);}void TCPConnection::send_segments(bool fill_window) { if (fill_window) _sender.fill_window(); auto &segments = _sender.segments_out(); while (!segments.empty()) { auto seg = segments.front(); // 設(shè)置 ACK、確認(rèn)應(yīng)答號和接收窗口大小 if (_receiver.ackno()) { seg.header().ackno = _receiver.ackno().value(); seg.header().win = _receiver.window_size(); seg.header().ack = true; } _segments_out.push(seg); segments.pop(); }}
主動關(guān)閉當(dāng)上層程序沒有更多數(shù)據(jù)需要發(fā)送時,將會調(diào)用 TCPConnection::end_input_stream()
結(jié)束輸入,這時候需要發(fā)送 FIN
報(bào)文段給服務(wù)端,告訴他自己沒有更多數(shù)據(jù)要發(fā)送了,但是可以繼續(xù)接收服務(wù)端發(fā)來的數(shù)據(jù)??蛻舳说臓顟B(tài)由 ESTABLISHED
轉(zhuǎn)移到 FIN_WAIT_1
,服務(wù)端收到 FIN
之后變成 CLOSE_WAIT
狀態(tài),并回復(fù) ACK
給客戶端,客戶端收到之后接著轉(zhuǎn)移到 FIN_WAIT_2
狀態(tài)。
如果服務(wù)端數(shù)據(jù)傳輸完成了,會發(fā)送 FIN
報(bào)文段給客戶端,轉(zhuǎn)移到 LAST_ACK
狀態(tài),此時客戶端會回復(fù)最后一個 ACK
給服務(wù)端并進(jìn)入 TIME_WAIT
超時等待狀態(tài),如果這個等待時間內(nèi)沒有收到服務(wù)端重傳的 FIN
,就說明 ACK
順利到達(dá)了服務(wù)端且服務(wù)端已經(jīng)變成 CLOSED
狀態(tài)了,此時客戶端也能斷開連接變成 CLOSED
了。
void TCPConnection::end_input_stream() { // 發(fā)送 FIN _sender.stream_in().end_input(); send_segments(true);}
在上述情景中,客戶端是主動關(guān)閉(Active Close)的一方,服務(wù)端是被動關(guān)閉(Passive Close)的一方。
主動重置連接有兩種情況會導(dǎo)致發(fā)送 RST
報(bào)文段來主動重置連接:
TCPSender
超時重傳的次數(shù)過多時,表明通信鏈路存在故障;TCPConnect
對象被釋放但是 TCP 仍然處于連接狀態(tài)的時候;和 Lab3 中類似,TCPConnection
通過外部定期調(diào)用 tick()
函數(shù)來得知過了多長時間,在 tick()
函數(shù)里還得處理超時等待的情況:
//! \param[in] ms_since_last_tick number of milliseconds since the last call to this methodvoid TCPConnection::tick(const size_t ms_since_last_tick) { _sender.tick(ms_since_last_tick); // 重傳次數(shù)太多時需要斷開連接 if (_sender.consecutive_retransmissions() > _cfg.MAX_RETX_ATTEMPTS) { return send_rst_segment(); } // 重傳數(shù)據(jù)包 send_segments(); _last_segment_time += ms_since_last_tick; // TIME_WAIT 超時等待狀態(tài)轉(zhuǎn)移到 CLOSED 狀態(tài) if (TCPState::state_summary(_receiver) == TCPReceiverStateSummary::FIN_RECV && TCPState::state_summary(_sender) == TCPSenderStateSummary::FIN_ACKED && _last_segment_time >= 10 * _cfg.rt_timeout) { _linger_after_streams_finish = false; _is_active = false; }}TCPConnection::~TCPConnection() { try { if (active()) { cerr << "Warning: Unclean shutdown of TCPConnection\n"; // Your code here: need to send a RST segment to the peer send_rst_segment(); } } catch (const exception &e) { std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl; }}void TCPConnection::send_rst_segment() { abort(); TCPSegment seg; seg.header().rst = true; _segments_out.push(seg);}void TCPConnection::abort() { _is_active = false; _sender.stream_in().set_error(); _receiver.stream_out().set_error();}
接收報(bào)文段外部通過 TCPConnection::segment_received()
將接收到的報(bào)文段傳給它,在這個函數(shù)內(nèi)部,需要將確認(rèn)應(yīng)答號和接收窗口大小告訴 TCPSender
,好讓他接著填滿發(fā)送窗口。接著還需要把報(bào)文段傳給 TCPReceiver
來重組數(shù)據(jù),并更新確認(rèn)應(yīng)答號和自己的接收窗口大小。然后 TCPSender
需要根據(jù)收到的包類型進(jìn)行狀態(tài)轉(zhuǎn)移,并決定發(fā)送含有有效數(shù)據(jù)的報(bào)文段還是空 ACK
給對方。
為什么即使沒有新的數(shù)據(jù)要發(fā)送也要回復(fù)一個空 ACK
呢?因?yàn)槿绻贿@么做,對方會以為剛剛發(fā)的包丟掉了而一直重傳。
void TCPConnection::segment_received(const TCPSegment &seg) { if (!active()) return; _last_segment_time = 0; // 是否需要發(fā)送空包回復(fù) ACK,比如沒有數(shù)據(jù)的時候收到 SYN/ACK 也要回一個 ACK bool need_empty_ack = seg.length_in_sequence_space(); auto &header = seg.header(); // 處理 RST 標(biāo)志位 if (header.rst) return abort(); // 將包交給發(fā)送者 if (header.ack) { need_empty_ack |= !_sender.ack_received(header.ackno, header.win); // 隊(duì)列中已經(jīng)有數(shù)據(jù)報(bào)文段了就不需要專門的空包回復(fù) ACK if (!_sender.segments_out().empty()) need_empty_ack = false; } // 將包交給接受者 need_empty_ack |= !_receiver.segment_received(seg); // 被動連接 if (TCPState::state_summary(_receiver) == TCPReceiverStateSummary::SYN_RECV && TCPState::state_summary(_sender) == TCPSenderStateSummary::CLOSED) return connect(); // 被動關(guān)閉 if (TCPState::state_summary(_receiver) == TCPReceiverStateSummary::FIN_RECV && TCPState::state_summary(_sender) == TCPSenderStateSummary::SYN_ACKED) _linger_after_streams_finish = false; // LAST_ACK 狀態(tài)轉(zhuǎn)移到 CLOSED if (TCPState::state_summary(_receiver) == TCPReceiverStateSummary::FIN_RECV && TCPState::state_summary(_sender) == TCPSenderStateSummary::FIN_ACKED && !_linger_after_streams_finish) { _is_active = false; return; } if (need_empty_ack && TCPState::state_summary(_receiver) != TCPReceiverStateSummary::LISTEN) _sender.send_empty_segment(); // 發(fā)送其余報(bào)文段 send_segments();}
測試在終端中輸入 make check_lab4
就能運(yùn)行所有測試用例,測試結(jié)果如下:
發(fā)現(xiàn)有幾個 txrx.sh
的測試用例失敗了,但是單獨(dú)運(yùn)行這些測試用例卻又可以通過,就很奇怪:
接著測試一下吞吐量(請確保構(gòu)建類型是 Release 而不是 Debug),感覺還行, 0.71Gbit/s,超過了實(shí)驗(yàn)指導(dǎo)書要求的 0.1Gbit/s。但是實(shí)際上還可以優(yōu)化一下 ByteStream
類,將內(nèi)部數(shù)據(jù)類型換成 BufferList
,這樣在寫入數(shù)據(jù)的時候就不用一個字符一個字符插入隊(duì)列了,可以大大提高效率。
最后將 Lab0 中 webget
使用的 TCPSocket
換成 CS144TCPSocket
,重新編譯并運(yùn)行 webegt
,發(fā)現(xiàn)能夠正確得到響應(yīng)結(jié)果,說明我們實(shí)現(xiàn)的這個 CS144TCPSocket
已經(jīng)能和別的操作系統(tǒng)實(shí)現(xiàn)的 Socket
進(jìn)行交流了:
至此,CS144 的 TCP 實(shí)驗(yàn)部分已全部完成,可以說是比較有挑戰(zhàn)性的一次實(shí)驗(yàn)了,尤其是 Lab4 部分,各種奇奇怪怪的 bug,編碼一晚上,調(diào)試時長兩天半(約等于一坤天),調(diào)試的時候斷點(diǎn)還總是失效,最后發(fā)現(xiàn)是優(yōu)化搞的鬼,需要將 etc/cflags.cmake
第 18 行改為 set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -O0")
才行。以上~~
關(guān)鍵詞:
免責(zé)聲明:本網(wǎng)站所有信息,并不代表本站贊同其觀點(diǎn)和對其真實(shí)性負(fù)責(zé),投資者據(jù)此操作,風(fēng)險(xiǎn)請自擔(dān)。
上一篇: 倍思口袋快充移動電源,隨時充電。
下一篇:最后一頁
前言經(jīng)過前面幾個實(shí)驗(yàn)的鋪墊,終于到了將他們組合起來的時候了。Lab4將實(shí)現(xiàn)TCPConnection功能,內(nèi)部含有TCP
縱觀市場上不少的移動電源產(chǎn)品,不難發(fā)現(xiàn)隨著移動電源的容量增大移動電源的體積也在增大,主流市場的20000m
【廣西:柳州北部、梧州南部發(fā)生山洪災(zāi)害可能性很大】廣西水利廳和廣西氣象局于5月6日16時聯(lián)合發(fā)布山洪災(zāi)害
【昆侖萬維:旗下Opera推出適配AI的瀏覽器OperaOne】據(jù)昆侖萬維集團(tuán)官微消息,近日,旗下海外信息分發(fā)及元
其官網(wǎng)顯示,該場發(fā)布會將有國民大作版本計(jì)劃、新品首曝等環(huán)節(jié)
星火成炬|飛揚(yáng)吧青春,主流媒體,山西門戶。山西新聞網(wǎng)是經(jīng)國務(wù)院新聞辦審核批準(zhǔn),由山西日報(bào)報(bào)業(yè)集團(tuán)主管、
第133屆廣交會閉幕“綠色”理念貫穿廣交會各環(huán)節(jié),主流媒體,山西門戶。山西新聞網(wǎng)是經(jīng)國務(wù)院新聞辦審核批準(zhǔn)
“呃……果然是又昏倒了嗎,我的身體到底是怎么了……”金發(fā)少年蘇醒,現(xiàn)在所處的地方是自己房間旁的浴室并
中鋼網(wǎng)其他新聞資訊頻道提供鋼材行業(yè)其他新聞資訊,中鋼網(wǎng)-免保證金、免手續(xù)費(fèi)、零風(fēng)險(xiǎn)、零成本鋼材現(xiàn)貨交
中鋼網(wǎng)其他新聞資訊頻道提供鋼材行業(yè)其他新聞資訊,中鋼網(wǎng)-免保證金、免手續(xù)費(fèi)、零風(fēng)險(xiǎn)、零成本鋼材現(xiàn)貨交
中鋼網(wǎng)其他新聞資訊頻道提供鋼材行業(yè)其他新聞資訊,中鋼網(wǎng)-免保證金、免手續(xù)費(fèi)、零風(fēng)險(xiǎn)、零成本鋼材現(xiàn)貨交
中鋼網(wǎng)其他新聞資訊頻道提供鋼材行業(yè)其他新聞資訊,中鋼網(wǎng)-免保證金、免手續(xù)費(fèi)、零風(fēng)險(xiǎn)、零成本鋼材現(xiàn)貨交
中鋼網(wǎng)其他新聞資訊頻道提供鋼材行業(yè)其他新聞資訊,中鋼網(wǎng)-免保證金、免手續(xù)費(fèi)、零風(fēng)險(xiǎn)、零成本鋼材現(xiàn)貨交
近兩年,重卡行業(yè)內(nèi)卷出了新高度,大馬力車輛越來越多。為了滿足市場用戶的迫切需求,中國重汽推出了多款爆
5月5日,譚旭光在濰坊濰柴工業(yè)園與清華大學(xué)中國經(jīng)濟(jì)思想與實(shí)踐研究院院長李稻葵一行進(jìn)行工作座談,共同探討
App5月6日消息,國家發(fā)改委黨組書記、主任鄭柵潔在旗幟網(wǎng)刊發(fā)署名文章指出,積極擴(kuò)大國內(nèi)有效需求。把恢復(fù)
【俄羅斯同古巴簽署協(xié)議,促進(jìn)對古能源、采礦和民航等領(lǐng)域投資】據(jù)古巴《格拉瑪報(bào)》和塔斯社5月5日消息,俄
工業(yè)窯爐的工作原理是什么?電阻爐消耗電能轉(zhuǎn)換來的熱能 一部分由電爐構(gòu)筑材料及傳熱的各種因素而散失到空間...
工業(yè)爐窯有哪些應(yīng)用?完成溫度曲線要求。完成氣氛曲線要求。壓力曲線要求,也可以把以上三點(diǎn)稱為窯爐的三要素...
膨化設(shè)備的工作原理是什么?膨化是利用相變和氣體的熱壓效應(yīng)原理,使被加工物料內(nèi)部的液體迅速升溫汽化、增壓...