一、UDP協議核心特性
1. UDP vs TCP
?特性 ? | UDP | ?TCP |
---|---|---|
連接方式 | 無連接 | 面向連接(三次握手) |
可靠性 | 不保證數據到達或順序 | 可靠傳輸(超時重傳、順序控制) |
傳輸效率 | 低延遲,高吞吐 | 相對較低(因握手和確認機制) |
適用場景 | 實時音視頻、廣播、在線游戲 | 文件傳輸、Web請求、數據庫操作 |
2. UDP數據包結構
- **首部(8字節)**?:
| 源端口(2) | 目標端口(2) |
| 數據包長度(2) | 校驗和(2) |
- 數據載荷:最大長度受限于IPv4的MTU(通常1500字節)?。
3. 不同方式介紹
?單播(Unicast)?:1 對 1(普通 UDP/TCP 通信)。
?廣播(Broadcast)?:1 對同一子網內所有設備。
?組播(Multicast)?:1 對一組指定的設備(跨子網)。
二、UDP單播通信實現
1. UDP服務器與客戶端流程
服務器:socket() → bind() → recvfrom() → sendto()
客戶端:socket() → sendto() → recvfrom()
2. 完整代碼示例
?UDP服務器(接收并回復)?
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed!" << std::endl;return 1;}SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);sockaddr_in serverAddr{};serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(8080);bind(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));std::cout << "UDP Server listening on port 8080..." << std::endl;char buffer[1024];sockaddr_in clientAddr{};int clientAddrLen = sizeof(clientAddr);while (true) {int bytesReceived = recvfrom(sock, buffer, sizeof(buffer), 0,(sockaddr*)&clientAddr, &clientAddrLen);if (bytesReceived == SOCKET_ERROR) {std::cerr << "recvfrom failed: " << WSAGetLastError() << std::endl;continue;}char clientIp[INET_ADDRSTRLEN] = { 0 };if (InetNtopA(AF_INET, // IPv4&clientAddr.sin_addr, // 輸入地址結構體clientIp, // 輸出緩沖區sizeof(clientIp) // 緩沖區大小) == NULL) {std::cerr << "IP conversion failed: " << WSAGetLastError() << std::endl;strcpy_s(clientIp, "unknown"); // 錯誤時顯示 "unknown"}std::cout << "Received " << bytesReceived << " bytes from "<< clientIp << ":" // 使用轉換后的 IP 字符串<< ntohs(clientAddr.sin_port) << std::endl;// 原樣返回數據sendto(sock, buffer, bytesReceived, 0,(sockaddr*)&clientAddr, clientAddrLen);}closesocket(sock);WSACleanup();return 0;
}
UDP客戶端(發送消息)?
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed!" << std::endl;return 1;}SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);sockaddr_in serverAddr{};serverAddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr.s_addr));serverAddr.sin_port = htons(8080);const char* message = "Hello UDP Server!";sendto(sock, message, strlen(message), 0,(sockaddr*)&serverAddr, sizeof(serverAddr));char buffer[1024];int bytesReceived = recvfrom(sock, buffer, sizeof(buffer), 0, nullptr, nullptr);if (bytesReceived > 0) {std::cout << "Server echoed: " << std::string(buffer, bytesReceived) << std::endl;}closesocket(sock);WSACleanup();return 0;
}
測試結果
三、UDP廣播通信
1. 廣播地址與設置
- 廣播地址:255.255.255.255(全局廣播)或子網廣播地址(如192.168.1.255)。
- ?套接字選項:啟用
SO_BROADCAST
。
int enable = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&enable, sizeof(enable));
2. 廣播發送端代碼
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed!" << std::endl;return 1;}SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);// 啟用廣播int enable = 1;setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&enable, sizeof(enable));sockaddr_in broadcastAddr{};broadcastAddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &(broadcastAddr.sin_addr.s_addr));broadcastAddr.sin_port = htons(8080);const char* message = "Broadcast Message!";sendto(sock, message, strlen(message), 0,(sockaddr*)&broadcastAddr, sizeof(broadcastAddr));closesocket(sock);WSACleanup();return 0;
}
3. 服務器端修改部分
std::cout << "Received " << bytesReceived << " bytes from "<< clientIp << ":" // 使用轉換后的 IP 字符串<< ntohs(clientAddr.sin_port) << std::endl;std::cout << "Datas:" << std::string(buffer, bytesReceived) << std::endl;
4. 測試結果
四、UDP組播(Multicast)?
1. 組播地址范圍
- IPv4:224.0.0.0 ~ 239.255.255.255(如239.255.0.1)。
2. 接收端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed!" << std::endl;return 1;}// 創建 UDP 套接字SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock == INVALID_SOCKET) {std::cerr << "socket() failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 設置端口復用(允許其他進程綁定相同端口)int reuse = 1;if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,(char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {std::cerr << "setsockopt(SO_REUSEADDR) failed: " << WSAGetLastError() << std::endl;closesocket(sock);WSACleanup();return 1;}// 綁定到本地端口sockaddr_in localAddr{};localAddr.sin_family = AF_INET;localAddr.sin_addr.s_addr = htonl(INADDR_ANY);localAddr.sin_port = htons(8080);if (bind(sock, (sockaddr*)&localAddr, sizeof(localAddr)) == SOCKET_ERROR) {std::cerr << "bind() failed: " << WSAGetLastError() << std::endl;closesocket(sock);WSACleanup();return 1;}// 加入組播組ip_mreq mreq{};if (inet_pton(AF_INET, "239.255.255.250", &(mreq.imr_multiaddr.s_addr)) != 1) {std::cerr << "inet_pton() failed for multicast address" << std::endl;closesocket(sock);WSACleanup();return 1;}mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 使用默認網絡接口if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,(char*)&mreq, sizeof(mreq)) == SOCKET_ERROR) {std::cerr << "setsockopt(IP_ADD_MEMBERSHIP) failed: " << WSAGetLastError() << std::endl;closesocket(sock);WSACleanup();return 1;}std::cout << "Listening for multicast on 239.255.255.250:8080..." << std::endl;char buffer[1024];sockaddr_in senderAddr{};int senderAddrLen = sizeof(senderAddr);while (true) {// 接收數據int bytesReceived = recvfrom(sock, buffer, sizeof(buffer) - 1, 0,(sockaddr*)&senderAddr, &senderAddrLen);if (bytesReceived == SOCKET_ERROR) {std::cerr << "recvfrom() failed: " << WSAGetLastError() << std::endl;continue;}// 顯示來源信息char senderIp[INET_ADDRSTRLEN] = { 0 };inet_ntop(AF_INET, &senderAddr.sin_addr, senderIp, INET_ADDRSTRLEN);std::cout << "Received " << bytesReceived << " bytes from "<< senderIp << ":" << ntohs(senderAddr.sin_port) << std::endl;// 安全處理數據(防止緩沖區溢出)buffer[bytesReceived] = '\0'; // 添加字符串終止符std::cout << "Data: " << buffer << std::endl;}// 退出時清理(雖然 while(true) 會阻止執行到這里)closesocket(sock);WSACleanup();return 0;
}
3. 發送端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")int main() {// 初始化 WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl;return 1;}// 創建 UDP 套接字SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock == INVALID_SOCKET) {std::cerr << "socket() failed: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 設置組播 TTL(控制傳輸范圍)int ttl = 32;if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,(char*)&ttl, sizeof(ttl)) == SOCKET_ERROR) {std::cerr << "setsockopt(IP_MULTICAST_TTL) failed: " << WSAGetLastError() << std::endl;closesocket(sock);WSACleanup();return 1;}// 構建組播目標地址sockaddr_in multicastAddr{};multicastAddr.sin_family = AF_INET;multicastAddr.sin_port = htons(8080); // 目標端口// 轉換組播地址if (inet_pton(AF_INET, "239.255.255.250", &multicastAddr.sin_addr.s_addr) != 1) {std::cerr << "inet_pton() failed for multicast address" << std::endl;closesocket(sock);WSACleanup();return 1;}// 發送數據const char* msg = "Hello Multicast!";int msgLen = static_cast<int>(strlen(msg));int bytesSent = sendto(sock, msg, msgLen, 0,(sockaddr*)&multicastAddr, sizeof(multicastAddr));if (bytesSent == SOCKET_ERROR) {std::cerr << "sendto() failed: " << WSAGetLastError() << std::endl;}else if (bytesSent != msgLen) {std::cerr << "sendto() partial send: " << bytesSent << "/" << msgLen << std::endl;}else {std::cout << "Successfully sent " << bytesSent<< " bytes to multicast group 239.255.255.250:8080" << std::endl;}// 清理資源closesocket(sock);WSACleanup();return 0;
}