在下面博客中,我介紹了利用UDP模擬TCP連接、按數據包發送文件的過程,并附上完整源碼。
socket編程UDP-文件傳輸&模擬TCP建立連接脫離連接(進階篇)_udp socket發送-CSDN博客
下面博客實現的是滑動窗口機制:
socket編程UDP-實現滑動窗口機制與累積確認GBN-CSDN博客
本篇博客,我將在此基礎上實現停等機制,完成客戶端發送的接收確認、超時重傳。
目錄
一、停等機制的協議設計
二、停等機制的代碼實現
1.實現思路
?2.核心源碼
3.可運行完整源碼
三、運行演示
?1.建立與斷開連接
2.接收確認(無丟包)
3.丟包處理&超時重傳
一、停等機制的協議設計
在設計中,客戶端為文件發送方、服務器端為文件接收方。
每次客戶端發送的數據包有唯一的序列號seq(隨著數據包的發送不斷遞增),?如果服務器端收到新的數據包會發送對應的ack.(比如收到seq1就會發送ack1,收到seq2就會發送ack2).
所謂停等機制,就是發送方每輪只發送一個數據包,直到收到期待的ack(即與序列號對應的ack),才會發送下一個數據包。
如果發送方在定時器時間內沒有收到期待的ack,將會重傳這一數據包。(正如圖中發送端重傳seq2)
二、停等機制的代碼實現
1.實現思路
接收確認和超時重傳機制主要通過 ‘waitForAck‘、‘receiveAck‘和 ‘sendFile‘函數來完成。以下是實現過程的描述:
-
在 ‘receiveAck‘方法中,服務器會不斷監聽 ACK 消息。收到任何數據包后,首先驗證其校
驗和和 ACK 序列號是否匹配。如果驗證成功,會將 ‘ackReceived‘設置為 ‘true‘,并通過條件變量通知 ‘waitForAck‘,使其能夠退出等待狀態。
-
‘sendFile‘方法負責逐個發送數據包,并在每次發送后調用‘waitForAck‘,等待接收 ACK 確認。每個數據包都包含一個序列號(‘seqNum‘),用于標識數據的順序和確認接收的正確性。發送數據包后, ‘ackReceived‘標志被設置為 ‘false‘,并記錄期望的 ACK 序列號。
-
‘waitForAck‘方法使用條件變量和超時機制,如果在設定的超時時間內未收到正確的 ACK 確認,便會返回 ‘false‘,觸發重傳邏輯;如果收到了正確ack,則會返回true.
?2.核心源碼
bool Sender::waitForAck(int seqNum) {std::unique_lock<std::mutex> lock(mtx);return cv.wait_for(lock, std::chrono::milliseconds(TIMEOUT), [this, seqNum]() { return ackReceived && expectedAck == seqNum; });
}
void Sender::receiveAck() {Datagram ackPacket(SERVER_PORT,ROUTER_PORT);socklen_t len = sizeof(routerAddr);while (true) {if (recvfrom(sock, reinterpret_cast<char*>(&ackPacket), sizeof(ackPacket), 0, (struct sockaddr*)&routerAddr, &len) > 0) {if (ackPacket.validateChecksum(clientAddr.sin_addr.S_un.S_addr, routerAddr.sin_addr.S_un.S_addr) && ackPacket.ack == expectedAck) {std::lock_guard<std::mutex> lock(mtx);std::cout<<"收到ACK,ack="<<ackPacket.ack<<std::endl;ackReceived = true;cv.notify_one();}}}
}
void Sender::sendFile(const std::string& filename) {//......int seqNum = 0;while (!file.eof()) {Datagram packet(CLIENT_PORT,ROUTER_PORT);packet.seq = seqNum;file.read(packet.data, BUFFER_SIZE);packet.dataSize = static_cast<int>(file.gcount());packet.flag = 0; // 數據包ackReceived = false;expectedAck = seqNum;//1.創建接收線程,避免第三次握手時ACK的丟包Datagram AckPacket(SERVER_PORT,ROUTER_PORT);if(seqNum<3){std::thread ackThread1(&Sender::receivePacket,this, std::ref(AckPacket));std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT)); //休眠等一會兒ackThread1.detach();//修改}while (true) {if(AckPacket.flag == 2&&seqNum<3&&AckPacket.validateChecksum(clientAddr.sin_addr.S_un.S_addr, routerAddr.sin_addr.S_un.S_addr))//2.如果此時又收到了SYN-ACK{std::cout << "重新收到SYN-ACK包\n";Datagram ackPacket(CLIENT_PORT,ROUTER_PORT);ackPacket.flag = 3; // ACKsendPacket(ackPacket);std::cout << "重新發送ACK包,連接建立成功\n";std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT)); //休眠等一會兒AckPacket.flag=1;}sendPacket(packet);std::cout << "發送數據包.SEQ=" << packet.seq <<",校驗碼="<< packet.checksum<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(5*TIMEOUT)); //休眠等一會兒if (waitForAck(seqNum)) {break; // 收到ACK,跳出重傳循環}std::cout << "ACK超時,重傳數據包,SEQ=" << packet.seq << std::endl;}seqNum++;}//......
}
3.可運行完整源碼
已上傳github:
https://github.com/yeyeyeyeye-zhang/Computer-Network/tree/main/lab3-1/codes
三、運行演示
在src目錄下輸入:
g++ -o cs main.cpp Datagram.cpp Sender.cpp Receiver.cpp -lws2_32
?1.建立與斷開連接
客戶端建立連接
服務器端建立連接
客戶端斷開連接
服務器端斷開連接?
2.接收確認(無丟包)
客戶端正常發送與接收
服務器端正常接收與發送
3.丟包處理&超時重傳
出現丟包后,超時,客戶端重傳數據包