当前位置: 首页 > news >正文

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的调用者会周期性地

调用TCPSendertick方法,看过了多少时间了。

TCPSender负责检查所有发出的TCPSenderMessages,

来决定最早发送的报文是不是太久没有确认了。

(这个报文段占据的所有序列号是否都得到了确认)。

如果不是,就需要重传这个报文了。

  • 每过若干msTCPSendertick函数就会被调用,它用来告诉你,自上一次调用到现在过了多久。使用这个方法需要维护一个标识,表示TCPSender累计过了多久。记住不要调用任何时间相关的函数,比如clock time等。
  • TCPSender创建时有一个初始的RTO重传时间,RTO会随着时间进行而改变,但初始值不变为initial_RTO_ms
  • 你需要实现一个重传定时器:在到达RTO时,定时器触发;这里的时间是调用TCPSendertick方法里的时间,而不是一天中的具体时间。
  • 每次包含数据(序列空间中长度非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);

TCPSenderBytestream里面获取子串来填充窗口;

从流中读取子串并尽可能多地发送TCPSenderMessage

只要Bytestream中有字节读且接收方窗口还有空间。

它们通过调用transmit函数来发送。

你需要保证每个发送的TCPSenderMessage全部都在接收方的窗口中。

要让每个独立的报文尽可能的大,但不要超过TCPConfig::MAX_PAYLOAD_SIZE

你可以使用TCPSenderMessage::sequence_length()来计算一个报文段

中占据了多少个序列号。

注意SYNFIN也会占据窗口中的一个序列号。

特别处理:

应该如何处理接收窗口为0的情况呢?

如果接收方声称它的窗口为0push函数中应该假装它的

窗口大小为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,它使用你写的TCPSenderTCPReceiver基于IPTCP与互联网进行通信。

我们同样给了你一个相似的使用linux TCPSocket的程序./build/apps/tcp_native

一个大的问题是: 你自己写的TCP可以和LinuxTCP互相之间进行通信吗?(tcp_ipv4tcp_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_ipv4tcp_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

在这里插入图片描述

http://www.xdnf.cn/news/1389583.html

相关文章:

  • 自动驾驶中的传感器技术36——Lidar(11)
  • 《生成式AI消费级应用Top 100——第五版》| a16z
  • uni-app 跨平台项目的 iOS 上架流程:多工具组合的高效协作方案
  • driver.js实现前端页面引导
  • 【Flask】测试平台开发,集成禅道
  • 渗透测试学习笔记
  • dm8_静默安装简单快速
  • 基于EB的K3XX_GPT定时器中断的实现方法
  • 音视频直播卡顿分析与优化:技术原理、实践案例与未来趋势
  • Java 流(Stream)、文件(File)和IO
  • 基于 Python asyncio 和币安 WebSocket 打造高频加密货币预警机器人
  • 【Spring Cloud Alibaba】前置知识
  • 订餐后台项目-day02数据库模型定义笔记
  • 从0开始学习Java+AI知识点总结-28.Linux部署
  • Java 8核心特性详解:从Lambda到Stream的革命性升级
  • lesson49:HTML基础标签全解析:从入门到精通的网页构建指南
  • SQL Server 查看备份计划
  • Cursor不能读取.env文件解决办法(**/.env、**/env.*)
  • 华为认证全解析:价值详解、含金量解读(2025最新版)
  • 安全月报 | 傲盾DDoS攻击防御2025年8月简报
  • CRYPT32!CryptMsgUpdate函数分析之CRYPT32!PkiAsn1Decode函数的作用是得到pci
  • 达梦数据库-归档日志(一)
  • JavaScript 入门教程
  • 《Linux 网络编程六:数据存储与SQLite应用指南》
  • TF-IDF:文本分析的“火眼金睛”
  • PCIe 6.0 TLP路由机制:解密高效数据传输的核心架构
  • 【微知】如何撤销一个git的commit?以及撤销的3种方式?
  • 在本地获取下载chrome,然后离线搬运到 ECS
  • 最小生成树——Kruskal
  • go 使用rabbitMQ