CS144 lab3 tcp_sender
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