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.pswp.cn/pingmian/94734.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94734.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94734.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为HCIP数通学习与认证解析!

大家好&#xff0c;这里是G-LAB IT实验室。在信息技术飞速发展的今天&#xff0c;随着华为产品和服务的广泛应用&#xff0c;成为一名华为数通工程师无疑是许多年轻从业者的目标。然而&#xff0c;对于许多人来说&#xff0c;面对令人眼花缭乱的华为认证体系&#xff0c;不禁要…

深度学习入门Day10:深度强化学习原理与实战全解析

一、开篇&#xff1a;智能决策的科学与艺术在前九天的学习中&#xff0c;我们掌握了处理各种数据类型的深度学习方法&#xff0c;但这些都属于"被动学习"——模型从静态数据中学习模式。今天&#xff0c;我们将进入一个全新的领域&#xff1a;强化学习&#xff08;Re…

Jenkins Pipeline(二)-设置Docker Agent

设计流水线的目的是更方便地使用 Docker镜像作为单个 Stage或整个流水线的执行环境。 1.安装必要插件 在Jenkins服务器上已经安装了插件。 Docker PipelinePipeline Maven IntegrationPipeline Maven Plugin API 如果插件缺少什么&#xff0c;再次检查并安装即可。 2. 配…

神经网络|(十六)概率论基础知识-伽马函数·中

【1】引言 前序学习进程中&#xff0c;已经初步了解了伽马函数&#xff0c;认识到nnn的阶乘计算可以转化为&#xff1a; n!n!⋅limk→∞kn⋅k!(nk)!limk→∞kn⋅k!⋅n!(nk)!limk→∞kn⋅k!(n1)(n2)...(nk)n!n! \cdot lim_{k\rightarrow\infty}\frac{k^n\cdot k!}{(nk)!}\\lim_…

设计模式Books Reading

文章目录 设计模式 创建型设计模式 工厂方法 示例说明 工厂方法模式结构 案例伪代码 工厂方法模式适合应用 实现方式 工厂方法模式优缺点 与其他模式的关系 概念示例 抽象工厂 抽象工厂模式结构 抽象工厂模式适合应用场景 实现方式 抽象工厂模式优缺点 与其他模式的关系 代码示…

接吻数问题:从球体堆叠到高维空间的数学奥秘

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 1 接吻数问题概述 接吻数问题&#xff08;Kissing Number Problem&am…

深度学习③【卷积神经网络(CNN)详解:从卷积核到特征提取的视觉革命(概念篇)】

文章目录先言1. 卷积核&#xff1a;特征检测的魔法窗口1.1什么是卷积核&#xff1a;可学习的特征检测器1.2可视化理解&#xff1a;边缘检测、纹理提取、特征发现1.3代码实现&#xff1a;使用PyTorch定义和初始化卷积层2. 卷积运算的数学原理2.1.离散卷积计算&#xff1a;滑动窗…

当不想安装telnet或nc时,可使用 Linux 系统默认自带的bash原生网络功能或ping(辅助判断)测试连通性

1. 用bash原生/dev/tcp测试端口&#xff08;无需任何工具&#xff09;bashshell 内置了/dev/tcp虚拟设备&#xff0c;可直接通过脚本测试端口是否能连接&#xff0c;执行以下命令&#xff08;替换数据库 IP 和端口&#xff09;&#xff1a;# 格式&#xff1a;echo > /dev/tc…

【STM32外设】ADC

声明&#xff1a;上图是STM32产品型号各字段含义&#xff0c;本文基于STM32F103 1、ADC的一些概念 常规通道(常规组)和注入通道(注入组)&#xff08;regular channels and injected channels&#xff09;ADC支持的外部通道总共16个&#xff08;且被3个ADC共享&#xff0c;ADC12…

Aha Moment——啊哈时刻!

1. 理解面试官的意图面试官问你“Aha moment”&#xff0c;其实是想知道&#xff1a;你是否真正理解这个概念&#xff1a;不只是背定义&#xff0c;而是理解其为什么重要。你如何发现它&#xff1a;考察你的数据分析方法论和技术能力&#xff08;用了哪些数据、指标、模型&…

RAG教程5:多表示索引和ColBERT

文章目录 导入依赖包 多表示索引 ColBERT 导入依赖包 %pip install youtube-transcript-api pytube多表示索引 from langchain_community.document_loaders import WebBaseLoader from langchain_text_splitters import RecursiveCharacterTextSplitterloader = WebBaseL

来自火山引擎的 MCP 安全授权新范式

资料来源&#xff1a;火山引擎-开发者社区 本文旨在深入剖析火山引擎 Model Context Protocol (MCP) 开放生态下的 OAuth 授权安全挑战&#xff0c;并系统阐述火山引擎为此构建的多层次、纵深防御安全方案。面对由 OAuth 2.0 动态客户端注册带来的灵活性与潜在风险&#xff0c;…

瑞芯微RK3506开发板PWM输入捕获驱动调试记录

本文演示PWM输入信号采集&#xff0c;基于触觉智能RK3506开发板。配置为&#xff1a; 3核Cortex-A7Cortex-M0多核异构处理器 主要接口&#xff1a;2路CAN FD&#xff0c;5路串口&#xff0c;RGB、MIPI、音频、USB2.0 OTG等、板载双百兆网口4G星闪SLEWiFi6BLE5.2。 PWM信号简…

PHP的header()函数分析

PHP的header()函数是HTTP协议交互的核心工具&#xff0c;它通过直接操纵响应头实现服务器与客户端之间的元数据通信。作为PHP原生函数&#xff0c;其设计初衷是处理HTTP协议层的关键操作&#xff0c;包括状态码设置、内容类型声明和缓存控制等基础功能。在Web开发中&#xff0c…

根据并发和响应延迟,实现语音识别接口自动切换需求

根据并发和响应延迟&#xff0c;语音识别接口自动 切换需求 需求描述&#xff1a; 当请求的语音识别的请求数量大于3或者请求语音识别接口3秒不可达无响应&#xff0c;切换备用语音识别接口 科大讯飞语音识别作为备用接口 科大讯飞的API文档: 进入讯飞开放平台的网页&#…

程序员之电工基础-CV程序解决目标检测

一、背景 兴趣爱好来了&#xff0c;决定研发一个产品。涉及到电工和机械等知识&#xff0c;所以记录一下相关的基础知识。今天的内容又回到了我的主营板块&#xff01;&#xff01;哈哈&#xff01;&#xff01;为后续整体集成做准备&#xff0c;先测试目标检测部分的能力。 二…

B树的概述以及插入逻辑

一&#xff0c;B树的概述1.B树(B-树)又称多路平衡查找树&#xff0c;B树所有节点中孩子数量的最大值n称为B树的阶&#xff0c;通常用m表示比如当m为2就是常见的二叉树一颗m阶的B树定义如下&#xff1a;1)每个结点最多有m-1个关键字&#xff1b;2)根节点最少可以只有1个关键字;3…

如何用 Kotlin 在 Android 手机开发一个小闹钟、计时器、秒表

以下是在 Android 上用 Kotlin 开发小闹钟、计时器和秒表的方法&#xff0c;分为核心功能实现和界面设计两部分&#xff1a; 闹钟功能实现 AlarmManager 和 BroadcastReceiver // 设置闹钟 val alarmManager getSystemService(Context.ALARM_SERVICE) as AlarmManager val i…

LeetCode Hot 100 第8天

1. 73 矩阵置零&#xff08;记录标识&#xff09; 链接&#xff1a;题目链接 题解&#xff1a; 题解 时间复杂度O(n*m)&#xff1a; 方案1(空间复杂度O(n m))&#xff1a;matrix[i][j] 0&#xff0c;意味着 第i行、第j列所有元素都要置为0&#xff1b;维护能置为0行、列的集…

Python OpenCV图像处理与深度学习:Python OpenCV开发环境搭建与入门

Python OpenCV入门&#xff1a;环境设置 学习目标 通过本课程&#xff0c;学员们将学习在Windows、macOS和Linux操作系统上安装Python和OpenCV&#xff0c;配置开发环境&#xff0c;以及如何使用Jupyter Notebook和PyCharm等集成开发环境&#xff08;IDE&#xff09;进行基本操…