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.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;進行基本操…