在 Windows 平台上進行網路程式開發時,Winsock(Windows Sockets)是核心 API。相較於面向連線的 TCP 協定,UDP(User Datagram Protocol)是一種無連線、不可靠的傳輸協定,但因其低延遲、低開銷的特性,廣泛應用於即時通訊、影音串流、線上遊戲等場景。本文將詳細解析基於 UDP 的 Winsock 程式設計要點,並提供完整的 Server/Client 範例程式碼。
雖然初始化與清理流程相同,但 UDP 省去了連線建立的步驟:
listen() 和 accept(),直接在 bind() 後進入資料收發迴圈。connect(),直接指定目標位址發送資料。資料收發函數改為 sendto() 和 recvfrom(),兩者都需要顯式指定目標/來源的 Socket 位址結構。
int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
sockaddr_in)。/* UDPServer.cpp */
#include <winsock2.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
int main() {
// 1. 初始化 Winsock 庫
WSADATA wsaData;
WORD wVersion = MAKEWORD(2, 2);
if (WSAStartup(wVersion, &wsaData) != 0) {
printf("Winsock 初始化失敗\n");
return 1;
}
// 2. 建立 UDP Socket (SOCK_DGRAM)
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) {
printf("Socket 建立失敗: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
// 3. 設定本地位址並綁定
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(7500); // 使用較高埠號避免衝突
serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(s, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("Bind 失敗: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
printf("UDP Server 已啟動,監聽埠 7500...\n");
// 4. 收發資料迴圈
sockaddr_in clientAddr{};
int clientAddrLen = sizeof(clientAddr);
char recvBuf[1024] = {0};
const char* sendMsg = "歡迎連線到 UDP Server!\r\n";
while (true) {
int recvLen = recvfrom(s, recvBuf, sizeof(recvBuf) - 1, 0,
(sockaddr*)&clientAddr, &clientAddrLen);
if (recvLen > 0) {
recvBuf[recvLen] = '\0';
printf("收到來自 %s:%d 的訊息: %s",
inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), recvBuf);
// 回覆 Client
sendto(s, sendMsg, (int)strlen(sendMsg), 0,
(sockaddr*)&clientAddr, clientAddrLen);
}
}
// 5. 清理資源 (實際上無限迴圈不會執行到這裡,需加入退出機制)
closesocket(s);
WSACleanup();
return 0;
}
/* UDPClient.cpp */
#include <winsock2.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "WS2_32.lib")
int main() {
WSADATA wsaData;
WORD wVersion = MAKEWORD(2, 2);
if (WSAStartup(wVersion, &wsaData) != 0) return 1;
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) {
WSACleanup();
return 1;
}
// 設定 Server 目標位址
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(7500);
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本機測試
const char* sendMsg = "Server 你好!\r\n";
char recvBuf[1024] = {0};
sockaddr_in fromAddr{};
int fromLen = sizeof(fromAddr);
printf("UDP Client 已啟動,送資料給 127.0.0.1:7500\n");
// 發送資料
if (sendto(s, sendMsg, (int)strlen(sendMsg), 0,
(sockaddr*)&serverAddr, sizeof(serverAddr)) != SOCKET_ERROR) {
printf("資料已發送,等待回應...\n");
// 接收回應
int recvLen = recvfrom(s, recvBuf, sizeof(recvBuf) - 1, 0,
(sockaddr*)&fromAddr, &fromLen);
if (recvLen > 0) {
recvBuf[recvLen] = '\0';
printf("Server 回應: %s", recvBuf);
}
}
closesocket(s);
WSACleanup();
return 0;
}
WSAGetLastError() 取得錯誤碼。htons() 轉網路位元組序,IP 字串轉整數用 inet_addr(),整數轉字串用 inet_ntoa()。select()、WSAAsyncSelect() 或 IOCP 實作非阻塞多工通訊。UDP 程式設計核心在於「無連線」與「訊息邊界保留」。掌握 sendto/recvfrom 與位址結構的操作,即可快速建立高效能的網路應用。建議讀者實際編譯執行上述程式碼,觀察封包在 Wireshark 中的傳輸細節,加深理解。