ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen); // 輸入值
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen); // 輸出指針
| 維度 | sendto (發送) | recvfrom (接收) |
|---|---|---|
| addrlen 含義 | **告訴內核**目標地址結構體大小 | **由內核填寫**來源地址實際大小 |
| 傳遞方式 | 值傳遞 socklen_t addrlen | 指針傳遞 socklen_t *addrlen |
| 調用方責任 | 預先設置正確大小(如 sizeof(struct sockaddr_in)) | 分配變量並傳遞地址,調用後讀取值 |
| 內核行為 | 只讀,用於邊界檢查 | 寫入實際地址長度(IPv4=16, IPv6=28) |
sendto 傳值足夠,recvfrom 為了通用性(未來可能擴展)預留指針。sockaddr_in6 28 字節,sockaddr_storage 128 字節。地址長度不再固定,**接收端必須知道實際長度**以正確解析,故 recvfrom 必須回填長度。dest_addr 時明確知道類型(IPv4/IPv6),直接傳 sizeof(...) 即可,無需內核回告。struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.1.100", &dest.sin_addr);
sendto(sockfd, buf, len, 0, (struct sockaddr*)&dest, sizeof(dest));
// sizeof(dest) 是編譯期常量 16,直接傳值
struct sockaddr_storage src; // 足夠大容納 IPv4/IPv6
socklen_t src_len = sizeof(src); // 必須初始化為緩衝區大小
char buf[1024];
ssize_t n = recvfrom(sockfd, buf, sizeof(buf), 0,
(struct sockaddr*)&src, &src_len);
// 調用後 src_len 被更新為實際地址長度 (16 或 28)
if (src.ss_family == AF_INET) {
struct sockaddr_in *v4 = (struct sockaddr_in*)&src;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &v4->sin_addr, ip, sizeof(ip));
printf("來自 IPv4: %s:%d\n", ip, ntohs(v4->sin_port));
} else if (src.ss_family == AF_INET6) {
// 處理 IPv6
}
recvfrom(..., &src_len) 傳遞**未初始化的指針**或**指向常量的指針** → 段錯誤/內核寫入只讀內存崩潰。sendto(..., sizeof(sockaddr)) 而實際結構是 sockaddr_in6 → 截斷地址導致發送失敗。recvfrom 返回的 src_len,直接強轉 sockaddr_in → IPv6 數據解析錯誤。sendto 穿值:應用告訴內核「我要發給這個地址,它佔多少字節」。
recvfrom 穿指針:內核告訴應用「數據來自這個地址,它實際佔多少字節」。
這是 **輸入參數(值)與輸出參數(指針)的本質區別**,由「發送端知長度、接收端不知長度」的非對稱性決定,並為 IPv4/IPv6 共存留足了擴展空間。