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: 1Q2: 如果一个段的报文只有部分确认了,我需要剪切掉已经确认的部分吗?
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 );
// Accessors
uint64_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".txt
function cmp_two_file_shasum()
{
[ $# -ne 2 ] && echo "usage: ./cmp_file_shasum <f1> <f2>" && exit
if [ ! -f "$1" ]; then
echo "$1 not exists" && exit 1
fi
if [ ! -f "$2" ]; then
echo "$2 not exits " && exit 1
fi
hash1=$(sha256sum $1 | awk ' {print $1}' )
hash2=$(sha256sum $2 | awk ' {print $1}' )
if [ "$hash1" = "$hash2" ]; then
return 0
else
return 1
fi
}
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=$3
fn=$(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" ]; then
SERVER_IP="169.254.144.1"
SERVER_PORT="9090"
srv_proc=$NATIVE_SOCKET
cln_proc=$MINNOW_SOCKET
mode_str="native_server"
else
SERVER_IP="169.254.144.2"
SERVER_PORT="9090"
srv_proc=$MINNOW_SOCKET
cln_proc=$NATIVE_SOCKET
mode_str="minnow_server"
fi
if [ "$is_server_send" = "1" ]; then
srv_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"
else
srv_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"
fi
bash -c "${srv_cmd}" &
srv_pid=$!
# make sure server is started!!!
sleep 3
bash -c "${cln_cmd}" &
cln_pid=$!
wait $cln_pid
wait $srv_pid
cmp_two_file_shasum "${fn}" "${rfn}"
if [ $? -ne 0 ];then
echo "${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_files
for check_sz in ${sz_arrs[@]};
do
native_server=1
minnow_server=0
server_send=1
client_send=0
test_conn $check_sz $native_server $client_send
wait $!
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