文章目錄
- 前言
- 一,socket
- 二,服務端socket
- 3-1 創建socket
- 3-2 綁定地址和端口
- 3-3 接收數據
- 3-4 回復數據
- 3-5關閉socket
- 3-6 完整代碼
- 三,客戶端socket
- 3-1 為什么客戶端通常不需要手動定義 IP 和端口
前言
學習 socket
編程的意義在于:它讓你掌握計算機之間通信的核心原理,能親手實現聊天程序、文件傳輸、簡易服務器等網絡應用;同時這是理解 TCP/IP
協議、深入系統編程和進入后臺開發、分布式系統的基礎技能,也是面試和工程實踐中必不可少的知識。
一,socket
socket
(套接字) 是操作系統提供的一種 通信機制,最常用于網絡通信,在編程時,你可以把它當作 特殊的文件描述符(FD)
,既能讀、也能寫,文件用來在本地磁盤讀寫數據,而 socket
用來在不同主機之間交換數據。你可以把 socket
看作網絡文件,只是讀寫的數據不是在磁盤上,而是發送到網絡上的另一個進程。
二,服務端socket
主要作用:接收客戶端請求并回復,服務器端使用socket
分為幾個階段:創建 socket -> 綁定地址和端口 -> 等待客戶端發送數據 / 監聽連接 -> 處理數據或回復 -> 關閉 socket
3-1 創建socket
socket函數原型
int socket(int domain, int type, int protocol);
domain
:用于選擇網絡協議協議類型一般有值:AF_INET
(IPv4 網絡協議),AF_INET6
(IPv6 網絡協議),AF_UNIX/AF_LOCAL
(本地進程間通信)type
:指定傳輸方式,主要有兩種:SOCK_STREAM
面向連接,基于字節流(TCP),SOCK_DGRAM
無連接,基于報文(UDP)protocol
:通常指定具體協議:0
默認協議,IPPROTO_TCP
明確選擇 TCP,IPPROTO_UDP
明確選擇 UDP
在下面的代碼示例中我們會選擇:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
創建一個UDP
的套接字
3-2 綁定地址和端口
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
servaddr.sin_port = htons(12345); // 端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
上面的struct sockaddr_in servaddr
創建一個 IPv4
地址結構體 sockaddr_in
,用來保存服務端的 IP
地址和端口信息結構體定義大致如下:
struct sockaddr_in {short sin_family; // 地址族 (AF_INET)unsigned short sin_port; // 端口號struct in_addr sin_addr; // IP 地址char sin_zero[8]; // 填充為與 struct sockaddr 同大小
};
解釋上述代碼:
servaddr.sin_family = AF_INET;
設置 地址族為 IPv4 (AF_INET)
告訴內核這是一個 IPv4
的 socket
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
INADDR_ANY = 0.0.0.0
,表示服務端 綁定本機所有可用 IP
地址,如果服務器有多個網卡,客戶端發送到任意 IP
都能被接收
servaddr.sin_port = htons(12345); // 端口
設置端口號為 12345
,htons = host to network short
(主機字節序 → 網絡字節序),網絡通信使用 大端序,保證不同平臺可以正確解析端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
bind()
將 socket
文件描述符 與 IP
+ 端口
綁定
參數解釋:
sockfd
:之前 socket()
返回的文件描述符
(struct sockaddr*)&servaddr
: 地址信息,強制類型轉換為通用 sockaddr
sizeof(servaddr)
: 結構體大小
3-3 接收數據
char buffer[100];
struct sockaddr_in cliaddr {};
socklen_t len = sizeof(cliaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr * ) & cliaddr, & len);
這段代碼在一個 UDP
服務器程序中,從客戶端接收數據,存儲到 buffer
中,同時記錄客戶端的地址信息cliaddr
,并返回接收的字節數n
。
char buffer[100];
定義一個字符數組 buffer
,大小為 100
字節,用來存儲從客戶端接收的數據
struct sockaddr_in cliaddr {};
定義一個 sockaddr_in
結構體變量 cliaddr
,用于存儲客戶端的地址信息(如 IP
地址和端口號)。{}
初始化所有字段為 0
socklen_t len = sizeof(cliaddr);
定義一個 socklen_t
類型的變量 len
,初始化為 cliaddr
的大小(通常 16
字節,struct sockaddr_in
的大小)。
3-4 回復數據
const char* reply = "Hello Client";sendto(sockfd, reply, strlen(reply), 0, (struct sockaddr*)&cliaddr, len); // 回復
這段代碼用與服務器回復客戶端信息
其它的不再過多贅述
(struct sockaddr*)&cliaddr
:客戶端地址結構體,告訴內核消息發送到哪里
3-5關閉socket
close(sockfd);
不關閉會造成資源瀉漏
3-6 完整代碼
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 創建 UDP socketif (sockfd < 0) { perror("socket"); return -1; }sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(12345);bind(sockfd, (sockaddr*)&servaddr, sizeof(servaddr)); // 綁定端口char buffer[100];sockaddr_in cliaddr{};socklen_t len = sizeof(cliaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&cliaddr, &len); // 接收消息buffer[n] = '\0';std::cout << "Server received: " << buffer << std::endl;const char* reply = "Hi Client";sendto(sockfd, reply, strlen(reply), 0, (sockaddr*)&cliaddr, len); // 回復客戶端close(sockfd);return 0;
}
三,客戶端socket
客戶端socket
代碼我直接展示出來吧,我們理解了服務端之后,客戶端是非常好理解的
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 創建 UDP socketif (sockfd < 0) { perror("socket"); return -1; }sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_port = htons(12345);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本機測試const char* msg = "Hello Server";sendto(sockfd, msg, strlen(msg), 0, (sockaddr*)&servaddr, sizeof(servaddr)); // 發送消息char buffer[100];socklen_t len = sizeof(servaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&servaddr, &len); // 接收回復buffer[n] = '\0';std::cout << "Client received: " << buffer << std::endl;close(sockfd);return 0;
}
這里的sendto
和recvfrom
大家應該都懂,主要是這個客戶端是如何找到服務端的重要
sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_port = htons(12345);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本機測試
在這段代碼里的IP地址和端口號是找到服務端的關鍵
-
IP 地址
(127.0.0.1):告訴客戶端要把數據發送到哪個機器。127.0.0.1
表示本機,也就是服務端和客戶端在同一臺電腦。如果服務端在另一臺電腦,你就要寫它的真實IP
,例如192.168.1.100
。 -
端口號
(12345)
服務端在這個端口上監聽數據。UDP/TCP
都是靠端口區分不同服務的,就像房子門牌號一樣。所以客戶端通過IP, port
就能找到服務端。
3-1 為什么客戶端通常不需要手動定義 IP 和端口
客戶端的職責是 主動找服務端,在調用 sendto()
時:目標地址(服務端的 IP+端口
) 由程序員指定,源地址(客戶端的 IP+端口
) 不寫時由內核自動分配
IP
:自動選擇一塊能到達服務端的本地網卡的 IP
端口
:自動分配一個 臨時端口,通常在 49152–65535 之間
客戶端什么時候需要綁定 IP+端口:如果客戶端希望 使用固定端口(比如做 P2P
、游戲服務器通知端口),或者有多張網卡,必須指定 用哪張網卡的 IP
去通信,這種情況才會手動調用 bind()