0. 前言
這個實驗做了挺久的,剛開始做的時候官方的代碼庫還是開著的。
調著調著代碼官方把倉庫給刪掉了,又去找別人的代碼倉庫調發現不
對都打算放棄了,過了幾天發現了一個start-code
的庫
再合進去簡直完美。這個實驗花的時間應該是前四個里面花的時間
最久的了,寫完這個實驗后準備重新去寫2021 sponge
版本的項目了。
到目前為止的實驗版本都是2025winter-minnow
版本。
1. 實驗構建
一開始用的別人寫好的倉庫sevetis/minnow
,先是make_parrallel.sh
的權限問題。
給了權限后,又出現找不到Makefile
。
后面找到一個starter-code
的庫,HT4w5/minnow-winter-2025
,
用這個庫的代碼合進去解決的問題。
2. TCPSender需求
TCPSender
需要負責做的事:
- 維護接收端的窗口(
TCPReceiverMessage
,ackno
,window size
) - 盡量的填充發送窗口,從
Bytestream
里面讀取,創建TCPSegment
(可能 包含SYN
,FIN
);除非窗口滿了或者bytestream
中沒有內容了。 - 跟蹤那些發送了但還沒有確認的報文,這些報文叫
Outstanding Segments
- 重傳那些
Outstanding Segment
,如果超時了
2.1 TCPSender怎么知道報文丟失?
TCPSender
會發送一串TCPSenderMessage
。
每個都是從Bytestream
里面讀取的子串(可能為空),用一個
序列號來標識在流中的位置。在流開頭時會加上SYN
,而在流
的結局會加上FIN
。
除了發送為些報文之外,TCPSender
還會跟蹤這些發送出去的報文所
占據的序列號有沒有被完全確認。TCPSender
的調用者會周期性地
調用TCPSender
的tick
方法,看過了多少時間了。
TCPSender
負責檢查所有發出的TCPSenderMessages
,
來決定最早發送的報文是不是太久沒有確認了。
(這個報文段占據的所有序列號是否都得到了確認)。
如果不是,就需要重傳這個報文了。
- 每過若干
ms
,TCPSender
的tick
函數就會被調用,它用來告訴你,自上一次調用到現在過了多久。使用這個方法需要維護一個標識,表示TCPSender
累計過了多久。記住不要調用任何時間相關的函數,比如clock time
等。 TCPSender
創建時有一個初始的RTO
重傳時間,RTO
會隨著時間進行而改變,但初始值不變為initial_RTO_ms
- 你需要實現一個重傳定時器:在到達
RTO
時,定時器觸發;這里的時間是調用TCPSender
的tick
方法里的時間,而不是一天中的具體時間。 - 每次包含數據(序列空間中長度非0)的報文段發送時,如果定時器沒有運行,那就啟動它,讓它在
RTO
時間到達時觸發。 - 所有發出的數據被確認后,關閉重傳定時器
- 如果
tick
調用且重傳定時器超時- 重傳最早未完全確認的報文。為了能重傳報文你需要選擇數據結構把報文給存下來。
- 如果窗口大小非
0
- 跟蹤連續重傳次數,每次連續重傳增加它。
TCPConnection
會根據這個信息判斷是不是該主動關閉連接了。 - 加倍
RTO
重傳時間。減少重傳包可能對網絡造成的影響。 - 重置重傳定時器,啟動它。注意更新
RTO
時間。
- 跟蹤連續重傳次數,每次連續重傳增加它。
- 當接收者通過
ackno
告訴我們,它們收到了新數據(新ackno
大于之前所有的ackno
)RTO
時間置為初始值initial_RTO
- 如果發送者還有未確認的報文段,重啟重傳定時器
- 將連續重傳數設為
0
如果你想把重傳定時器用一個單獨的類來寫,那么請加到已有的文件中(tcp_sender.hh
,tcp_sender.cc
)。
2.2 實現TCPSender
我們已經基本知道TCPSender
要做什么了。
從Bytestream
里讀取子串,將它們分成報文段發送給接收者。
如果在一段時間后還沒有收到確認消息,就重傳。
而且我們也已經討論了什么時候一個發送了的報文段需要重傳。
下面我們就看具體的接口類了。
void push(const TransmitFunction & transmit);
TCPSender
從Bytestream
里面獲取子串來填充窗口;
從流中讀取子串并盡可能多地發送TCPSenderMessage
,
只要Bytestream
中有字節讀且接收方窗口還有空間。
它們通過調用transmit
函數來發送。
你需要保證每個發送的TCPSenderMessage
全部都在接收方的窗口中。
要讓每個獨立的報文盡可能的大,但不要超過TCPConfig::MAX_PAYLOAD_SIZE
。
你可以使用TCPSenderMessage::sequence_length()
來計算一個報文段
中占據了多少個序列號。
注意SYN
和FIN
也會占據窗口中的一個序列號。
特別處理:
應該如何處理接收窗口為0
的情況呢?
如果接收方聲稱它的窗口為0
,push
函數中應該假裝它的
窗口大小為1
一樣。發送方可能以發送單個字節被接收方拒絕
而結束,也可能 接收方發送新的確認表示它的窗口有更多的空間了。
如果不這樣做的話,發送方永遠不知道什么時候允許開始發送了。
但要注意的是這只是你處理0
大小窗口的特殊情況。
TCPSender
實際上不需要記住錯誤的大小窗口為1
,這只是針對push
函數的特殊處理。同樣需要注意,窗口大小可能 為1 20 200
,
但窗口滿了。一個滿窗口和一個零窗口是兩個不同的概念。
void receive(const TCPReceiverMessage &msg);
msg
是從接收方接收的,
傳來了新的左端ackno
和右端ackno +window_size
。
TCPSender
需要檢查所有的outstanding segments
,
移除那些已經收到的。也就是小于ackno
的。
void tick(uint64_t ms_since_last_tick, const TransmitFunction & transmit);
一個計時函數,自上次發送過了多久。
發送方可能重傳outstanding segments
,
它們可以通過transmit
來發送。
(請不要使用clock
gettimeofday
這些函數,
唯一的對時間的引用就是ms_since_last_tick
)。
TCPSenderMessage make_empty_message() const;
TCPSende
應該可以正確的產生和發送一個序列號長度為0
的報文。
這對對端想要發送TCPReceiverMessage
,
并且需要產生TCPSenderMessage
是有用的。
2.3 FAQs和特殊情況
-
Q1: 在接收到對端通知之前,發送端默認的接收端窗口大小是多少?
A1: 1 -
Q2: 如果一個段的報文只有部分確認了,我需要剪切掉已經確認的部分嗎?
A2: 真正的TCP可以那樣做。但對于我們的學習來說沒必要,我們以一個完全的段確認來處理。 -
Q3: 如果我發送了三個獨立的段
"a" "b" "c"
,但都沒有收到確認,我可不可以在重傳的時候合成一個大的段"abc"?
或者是獨立的重傳?
A3: 和上一個問題類似,真正的TCP可以那樣做。但對我們的實驗來說沒必要,獨立跟蹤每個段,超時了就重傳最早的段。 -
Q4: 我需要存儲空的段嗎?并在必要時重傳。
A4: 沒有必要,只有包含數據的才需要,SYN、FIN、Payload
,除此之外無需重傳。
3. 實現思路及bug之路
這個實驗我感覺自己完全是在面向test-case
編程。
一開始我都不知道怎么發包!
后面去讀了TransmitFunction
的函數聲明才知道怎么發,原來
它帶的參數就是你構建發的包。。。
using TransmitFunction =
std::function<void( const TCPSenderMessage& )>;
一開始真的一點思路都沒有。
我是一步步摸索出來的。。。,先把重傳的部分實現完全放在一邊的。
先從序列號入手的,同樣需要一個流的標識stream_idx_
。
甚至忘記了絕對序列號和相對序列號轉化的邏輯,
又跑回去看tcp_receiver
的實現。
由于可能需要重傳發出去的包,因此選擇用queue<TCPSenderMessage>
去維護這一信息。
當然我們需要知道有沒有連接同步,用一個is_SYN_
維護這一信息。
剛開始寫的時候就忘記把syn
的包給推進隊列,
然后transmit(q.front())
就報錯
runtime_erro: load of value 190,which is not a valid value of type "bool"
在那里排查半天。。。
同樣不知道SYN FIN
要不要算在窗口大小里面,后面看函數發現是要算進去的。
還需要維護的是發送方自己的一個窗口,有哪些序列號是發送出去了的。
在寫代碼時,忘記更新這個窗口也錯了幾次。
make_empty_massage()
感覺完全是test-driven
寫出來的了。
再有一些bug
就是一些corner case
了,比如0
窗口的情況。
發的第一個包就是SYN+FIN
的情況,第一個包帶payload
的情況。
FIN
最后一個單獨發的情況。
還有處理零窗口的定時器的情況。
max_recv_
這個變量是根據接收方發來的信息而確定的接收方窗口的一
個最大大小。
還有RST
相關的代碼也感覺是test-driven
出來的。。。
代碼雖然是屎山,但它還是跑起來了。下面列下代碼吧。
還有個小插曲是,代碼case
全跑過后,自己手賤把is_FIN
初始化成
true
,在那里找了半天的錯誤。
- tcp_sender.hh
#pragma once#include "byte_stream.hh"
#include "tcp_receiver_message.hh"
#include "tcp_sender_message.hh"#include <functional>
#include <queue>class TCPSender
{
public:/* Construct TCP sender with given default Retransmission Timeout and possible ISN */TCPSender( ByteStream&& input, Wrap32 isn, uint64_t initial_RTO_ms ): input_( std::move( input ) ), isn_( isn ), initial_RTO_ms_( initial_RTO_ms ){}/* Generate an empty TCPSenderMessage */TCPSenderMessage make_empty_message() const;/* Receive and process a TCPReceiverMessage from the peer's receiver */void receive( const TCPReceiverMessage& msg );/* Type of the `transmit` function that the push and tick methods can use to send messages */using TransmitFunction = std::function<void( const TCPSenderMessage& )>;/* Push bytes from the outbound stream */void push( const TransmitFunction& transmit );/* Time has passed by the given # of milliseconds since the last time the tick() method was called */void tick( uint64_t ms_since_last_tick, const TransmitFunction& transmit );// Accessorsuint64_t sequence_numbers_in_flight() const; // For testing: how many sequence numbers are outstanding?uint64_t consecutive_retransmissions() const; // For testing: how many consecutive retransmissions have happened?const Writer& writer() const { return input_.writer(); }const Reader& reader() const { return input_.reader(); }Writer& writer() { return input_.writer(); }private:Reader& reader() { return input_.reader(); }void send_tcp_segment_and_update(const TransmitFunction &transmit, const TCPSenderMessage &msg);struct retr_timer {// void start(uint64_t abs_ms, uint64_t init_rto_);// void restart(uint64_t abs_ms);// bool expired(uint64_t abs_ms);void start(uint64_t abs_ms, uint64_t init_rto_) {is_started_ = true;init_ms_ = abs_ms; timeout_ = init_rto_;con_retr_cnts_ = 0;}bool expired(uint64_t abs_ms){return abs_ms >= init_ms_ + timeout_;}void restart(uint64_t abs_ms){con_retr_cnts_++;init_ms_ = abs_ms;timeout_ <<= 1;}void close(){is_started_ = false;}bool is_started_{};uint64_t con_retr_cnts_{};uint64_t timeout_{};uint64_t init_ms_{};};bool is_SYN_{};bool is_FIN_{};bool is_zero_recv_wnd_{};uint16_t max_recv_{ 1 };uint64_t snd_win_l{};uint64_t snd_win_r{};ByteStream input_;Wrap32 isn_;uint64_t initial_RTO_ms_;uint64_t abs_ms_passed_{};retr_timer timer_{};std::queue<TCPSenderMessage> outgoing_sndmsgs_{};
};
- tcp_sender.cc
#include "tcp_sender.hh"
#include "debug.hh"
#include "tcp_config.hh"using namespace std;// This function is for testing only; don't add extra state to support it.
uint64_t TCPSender::sequence_numbers_in_flight() const
{// debug( "unimplemented sequence_numbers_in_flight() called" );return snd_win_r - snd_win_l;
}// This function is for testing only; don't add extra state to support it.
uint64_t TCPSender::consecutive_retransmissions() const
{
// debug( "unimplemented consecutive_retransmissions() called" );return timer_.con_retr_cnts_;
}void TCPSender::push( const TransmitFunction& transmit )
{if (not is_SYN_) {TCPSenderMessage syn_msg{};syn_msg.SYN = true;syn_msg.seqno = isn_;if ( reader().bytes_buffered() != 0 && max_recv_ > 1) {read(reader(), std::min(static_cast<uint64_t>(TCPConfig::MAX_PAYLOAD_SIZE),static_cast<uint64_t>(max_recv_ - 1) ), syn_msg.payload);} if ( max_recv_ > syn_msg.sequence_length() && reader().is_finished()) {syn_msg.FIN = true;is_FIN_ = true;}send_tcp_segment_and_update(transmit, syn_msg);is_SYN_ = true;return ;}if ( is_zero_recv_wnd_ && outgoing_sndmsgs_.empty() ) {max_recv_ = 1;}if ( input_.has_error() && max_recv_ != 0) {TCPSenderMessage rst_msg{};rst_msg.RST = true;rst_msg.seqno = Wrap32::wrap( snd_win_r, isn_);send_tcp_segment_and_update( transmit, rst_msg);return ;}while ( reader().bytes_buffered() != 0 && max_recv_ != 0) {TCPSenderMessage snd_msg{};snd_msg.seqno = Wrap32::wrap( snd_win_r, isn_);read(reader(), std::min(static_cast<uint64_t>(TCPConfig::MAX_PAYLOAD_SIZE),static_cast<uint64_t>(max_recv_) ), snd_msg.payload);auto seq_len_no_fin = snd_msg.sequence_length();// debug("seq_len_no_fin: {}, recv_wnd_: {}", seq_len_no_fin, max_recv_);if (max_recv_ > seq_len_no_fin && reader().is_finished()) {snd_msg.FIN = true;is_FIN_ = true;}send_tcp_segment_and_update(transmit, snd_msg);// transmit(snd_msg);// outgoing_sndmsgs_.push(snd_msg);// snd_win_r += snd_msg.sequence_length();// recv_wnd_ -= snd_msg.payload.size();}// debug("recv_wnd_: {}, is_FIN_: {}, is_finished: {}", max_recv_, is_FIN_, reader().is_finished());if (not is_FIN_ && reader().is_finished() && max_recv_ > 0) {is_FIN_ = true;TCPSenderMessage snd_msg;snd_msg.seqno = Wrap32::wrap( snd_win_r, isn_);snd_msg.FIN = true;send_tcp_segment_and_update( transmit, snd_msg);// transmit(snd_msg);// outgoing_sndmsgs_.push(snd_msg);// snd_win_r += snd_msg.sequence_length();}
}TCPSenderMessage TCPSender::make_empty_message() const
{// debug( "unimplemented make_empty_message() called" );// return {};TCPSenderMessage msg{};msg.seqno = Wrap32::wrap( snd_win_r, isn_);if (input_.has_error())msg.RST = true;return msg;
}void TCPSender::receive( const TCPReceiverMessage& msg )
{// debug( "unimplemented receive() called" );// (void)msg;if ( msg.window_size == 0) {is_zero_recv_wnd_ = true;}else {is_zero_recv_wnd_ = false;}if ( msg.ackno.has_value()) {auto ack_stream_idx = msg.ackno.value().unwrap( this->isn_, snd_win_l);//debug("ack_stream_idx: {}, snd_win_l: {}, snd_win_r: {}\n", ack_stream_idx, snd_win_l, snd_win_r);if ( ack_stream_idx > snd_win_l && ack_stream_idx <= snd_win_r) {while (!outgoing_sndmsgs_.empty()) {auto hd = outgoing_sndmsgs_.front();auto sg_l = hd.seqno.unwrap( this->isn_, snd_win_l);auto sg_r = sg_l + hd.sequence_length();//debug("sg_l: {}, sg_r: {}\n", sg_l, sg_r);if (sg_r > ack_stream_idx) {snd_win_l = sg_l;break;}snd_win_l = sg_r;outgoing_sndmsgs_.pop();timer_.close();}if (!outgoing_sndmsgs_.empty()) {timer_.start(abs_ms_passed_, initial_RTO_ms_);}}if (ack_stream_idx + msg.window_size > snd_win_r)max_recv_ = ack_stream_idx + msg.window_size - snd_win_r;}else {max_recv_ = msg.window_size;}if ( msg.RST)input_.set_error();}void TCPSender::tick( uint64_t ms_since_last_tick, const TransmitFunction& transmit )
{// debug( "unimplemented tick({}, ...) called", ms_since_last_tick );// (void)transmit;abs_ms_passed_ += ms_since_last_tick;if ( timer_.expired(abs_ms_passed_) ) {if (timer_.con_retr_cnts_ <= TCPConfig::MAX_RETX_ATTEMPTS) {if (not is_zero_recv_wnd_)timer_.restart(abs_ms_passed_);else timer_.start(abs_ms_passed_,initial_RTO_ms_);if (!outgoing_sndmsgs_.empty()) {transmit(outgoing_sndmsgs_.front());}}}
}void TCPSender::send_tcp_segment_and_update(const TransmitFunction &transmit, const TCPSenderMessage &msg)
{auto seq_len = msg.sequence_length();max_recv_ -= seq_len;snd_win_r += seq_len;transmit( msg );// if (msg.FIN) {// debug("send packets contain FIN flag!");// }if (outgoing_sndmsgs_.empty() && not timer_.is_started_) {timer_.start( abs_ms_passed_, initial_RTO_ms_);}outgoing_sndmsgs_.emplace( msg );
}
4. 動手實踐
我們給你了一個客戶端程序./build/apps/tcp_ipv4
,它使用你寫的TCPSender
和TCPReceiver
基于IP
的TCP
與互聯網進行通信。
我們同樣給了你一個相似的使用linux TCPSocket
的程序./build/apps/tcp_native
。
一個大的問題是: 你自己寫的TCP
可以和Linux
的TCP
互相之間進行通信嗎?(tcp_ipv4
和tcp_native
)。
4.1 linux的tcp可以和自身通信嗎?
- 首先我們需要保證linux的tcp可以和自己通信,為此我們讓服務端啟動
./build/apps/tcp_native -l 0 9090
- 其次我們啟動linux的tcp服務端
./build/apps/tcp_native 169.255.144.1 9090
-
如果一切無誤服務端就會顯示
DEBUG: New connection from 169.254.144.1:36568
;
端會顯示類似DEBUG: Connecting to 169.254.144.1:9090... DEBUG: Successfully connected to 169.254.144.1:9090
-
這個時候在兩個窗口分別輸入一些字符,觀察對端有沒有正確的顯示呢?
-
可以通過輸入
CTRL+D
來關閉各自寫的Bytestream
,如果實現正確,你就會看到Outbound stream...finished
。如果是對端關閉,你將會看到Inbound stream...finished
。注意每方的流都是獨立的,如果自己方關閉了寫入,不會影響對端的接收。 -
現在關閉第二個方向的流。如果實現正確,兩個程序都會正常退出。
4.2 你的tcp實現可以與linux的通信嗎?
重復上面的操作,但使用你的tcp去連接linux的tcp。
首先運行
sudo ./scripts/tun.sh start 144
讓你的tcp
實現有權限在非root
情況下發送數據包。
每次重啟系統后都需要運行下這個命令。
回到實驗,你需要用tcp_ipv4
替換掉上面的一個程序(服務端或者客戶端)
。
連接如往常一樣建立連接了嗎?在窗口中打字對面能收到嗎?
如果可以的話,那真是恭喜你了。如果不行,那你可以開始debug
了。
你可以用下面的命令抓包進行分析。
sudo rm -f /tmp/cap.raw
sudo tcpdump -n -w /tmp/cap.raw -i tun144 --print --packet-bufferd
當你在每個方向輸入了字時,嘗試關閉一端并在另一端繼續輸入。
觀察有沒有顯示正確呢?
當兩端都CTRL+D
關閉流時,程序有沒有干凈地退出呢?
正確的實現應該是可以干凈地退出的,即使你可能看到tcp_ipv4
等了一會再退出,這主要是為了減少兩軍問題的發生。
什么時候需要等待呢?(是先close
還是后close
?)
4.3 嘗試通過1MB挑戰
一旦你完成了上面的基本通信,
嘗試在tcp_ipv4
和tcp_native
傳送一下文件。
你可以通過下面的命令創建一個大小為12345B
的文件/tmp/big.txt
如果是從服務端向客戶端發,可以使用下面的命令
- server
./build/apps/tcp_native -l 0 9090 < /tmp/big.txt
- client
</dev/null ./build/apps/tcp_ipv4 169.254.144.1 9090 > /tmp/big_r.txt
如果是客戶端向服務端發,可以使用下面的命令
- server
</dev/null ./build/apps/tcp_native -l 0 9090 > /tmp/big_r.txt
- client
./build/apps/tcp_ipv4 169.255.144.1 9090 < /tmp/big.txt
傳輸完成后,你可以通過sha-256的哈希值判斷這兩個文件是否相同
sha256sum /tmp/big.txt
sha256sum /tmp/big_r.txt
你可以嘗試不同的文件大小: 12B 65534B 65537B 200KB 1MB
如果都通過了,那么就恭喜你了。
如果沒有通過,繼續debug
吧!
寫了腳本,貼出來吧。。。
#!/bin/bash
# make a file size is k named /tmp/"test_"${k}".txt"
# correspond received file name is /tmp/"test_${k}_r".txtfunction cmp_two_file_shasum()
{[ $# -ne 2 ] && echo "usage: ./cmp_file_shasum <f1> <f2>" && exitif [ ! -f "$1" ]; thenecho "$1 not exists" && exit 1fiif [ ! -f "$2" ]; thenecho "$2 not exits " && exit 1fihash1=$(sha256sum $1 | awk ' {print $1}' )hash2=$(sha256sum $2 | awk ' {print $1}' )if [ "$hash1" = "$hash2" ]; thenreturn 0elsereturn 1fi}
function get_file_nm_and_create()
{local fn="/tmp/test_$1.txt"[ ! -f ${fn} ] && (dd if=/dev/random bs=$1 count=1 of=${fn})echo ${fn}
}
function get_file_rcv_nm()
{local fn="/tmp/test_$1r.txt"echo ${fn}
}function cln_test_files()
{rm -f /tmp/test_*.txt
}function test_conn()
{
file_sz=$1
is_native_server=$2
is_server_send=$3fn=$(get_file_nm_and_create ${file_sz})
rfn=$(get_file_rcv_nm ${file_sz})NATIVE_SOCKET="$(pwd)/build/apps/tcp_native"
MINNOW_SOCKET="$(pwd)/build/apps/tcp_ipv4"INPUT_FILE="< \"$fn\""
OUTPUT_FILE="> \"$rfn\""
INBOUND_CLOSED="</dev/null"if [ "$is_native_server" = "1" ]; thenSERVER_IP="169.254.144.1"SERVER_PORT="9090"srv_proc=$NATIVE_SOCKETcln_proc=$MINNOW_SOCKETmode_str="native_server"
elseSERVER_IP="169.254.144.2"SERVER_PORT="9090"srv_proc=$MINNOW_SOCKETcln_proc=$NATIVE_SOCKETmode_str="minnow_server"
fiif [ "$is_server_send" = "1" ]; thensrv_cmd="${srv_proc} -l ${SERVER_IP} ${SERVER_PORT} ${INPUT_FILE}"cln_cmd="${INBOUND_CLOSED} ${cln_proc} ${SERVER_IP} ${SERVER_PORT} ${OUTPUT_FILE}"send_str="ssend"
elsesrv_cmd="${INBOUND_CLOSED} ${srv_proc} -l ${SERVER_IP} ${SERVER_PORT} ${OUTPUT_FILE}"cln_cmd="${cln_proc} ${SERVER_IP} ${SERVER_PORT} ${INPUT_FILE}"send_str="csend"
fibash -c "${srv_cmd}" &
srv_pid=$!# make sure server is started!!!sleep 3bash -c "${cln_cmd}" &
cln_pid=$!wait $cln_pid
wait $srv_pidcmp_two_file_shasum "${fn}" "${rfn}"if [ $? -ne 0 ];thenecho "${file_sz}_${mode_str}_${send_str} test failed"
else echo "${file_sz}_${mode_str}_${send_str} test passed"
fi
}sz_arrs=(12 1000 65534 65537 200000 1000000)cln_test_filesfor check_sz in ${sz_arrs[@]};
do
native_server=1
minnow_server=0server_send=1
client_send=0test_conn $check_sz $native_server $client_sendwait $!test_conn $check_sz $minnow_server $client_send
wait $!test_conn $check_sz $native_server $server_send
wait $!test_conn $check_sz $minnow_server $server_send
wait $!
echo "----------"
done
# choose mode
# is native_server ?# true# server_cmd = ./build/app/tcp_native# client_cmd = ./build/app/tcp_ipv4#false# server_cmd = ./build/app/tcp_ipv4# client_cmd = ./build/app/tcp_native# server process start
# client process start# wait server_pid terminate
# wait client_pid terminate# compare f and rf
4.4 webget重寫
- 用
tcp_minnow_socket.hh
替換socket.hh
- 用
CS144TCPSocket
替換TCPSocket
- 在
getURL()
最后加上socket.wait_until_close()
這部分比較簡單,就不敘述更多了。
5. 成果
單獨貼下結果吧
check3
自己寫的腳本測試
12_native_server_csend test passed
12_minnow_server_csend test passed
12_native_server_ssend test passed
12_minnow_server_ssend test passed
----------
1000_native_server_csend test passed
1000_minnow_server_csend test passed
1000_native_server_ssend test passed
1000_minnow_server_ssend test passed
----------
65534_native_server_csend test passed
65534_minnow_server_csend test passed
65534_native_server_ssend test passed
65534_minnow_server_ssend test passed
----------
65537_native_server_csend test passed
65537_minnow_server_csend test passed
65537_native_server_ssend test passed
65537_minnow_server_ssend test passed
----------
200000_native_server_csend test passed
200000_minnow_server_csend test passed
200000_native_server_ssend test passed
200000_minnow_server_ssend test passed
----------
1000000_native_server_csend test passed
1000000_minnow_server_csend test passed
1000000_native_server_ssend test passed
1000000_minnow_server_ssend test passed
----------
check_webget
這個需要進入build
目錄,再make check