本地 IPC 四大類:
網絡通信核心問題:**如何唯一標識一個網絡進程?**
答案:三元組 (IP地址, 協議, 端口) — — TCP/IP 協議族已解決。
open → read/write → close 模式socket() — 創建描述符
int socket(int domain, int type, int protocol);
| 參數 | 常用值 | 說明 |
|---|---|---|
| domain | AF_INET, AF_INET6, AF_UNIX | 協議族,決定地址格式 |
| type | SOCK_STREAM, SOCK_DGRAM, SOCK_RAW | Socket 類型 |
| protocol | IPPROTO_TCP, IPPROTO_UDP, 0 | 具體協議,0 為自動選擇 |
注意:type 與 protocol 須匹配,如 SOCK_STREAM 不能配 IPPROTO_UDP。
bind() — 綁定地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
IPv4 地址結構:
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // 網絡字節序端口
struct in_addr sin_addr; // IPv4 地址
};
struct in_addr { uint32_t s_addr; };
字節序陷阱:主機序(大/小端)≠ 網絡序(大端)。**務必用 htons()/htonl() 轉換**,否則會引發難以排查的 Bug。
listen() / connect() — 建立連接
int listen(int sockfd, int backlog); // 服務端:被動監聽
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 客戶端:主動連接
listen 將主動 socket 轉為被動,backlog 為等待隊列長度connect 發起三次握手accept() — 接受連接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd 繼續監聽
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 更通用:
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
推薦 send/recv 或 sendmsg/recvmsg,支援標誌位控制。
close() — 關閉釋放描述符,TCP 發起四次揮手。
Client Server |------------------------>| SYN (seq=x) |<------------------------| SYN+ACK (seq=y, ack=x+1) |------------------------>| ACK (ack=y+1) | ESTABLISHED |
Client Server |------------------------>| FIN (seq=u) |<------------------------| ACK (ack=u+1) | CLOSE_WAIT | |<------------------------| FIN (seq=v) |------------------------>| ACK (ack=v+1) | TIME_WAIT | CLOSED
TIME_WAIT 作用:確保最後 ACK 到達,防止舊連接殘留包干擾新連接(持續 2MSL)。
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 4096
#define PORT 6666
int main() {
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket"); exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
perror("bind"); exit(1);
}
if (listen(listenfd, 10) == -1) {
perror("listen"); exit(1);
}
printf("====== waiting for client ======\n");
while (1) {
if ((connfd = accept(listenfd, NULL, NULL)) == -1) {
perror("accept"); continue;
}
int n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv: %s\n", buff);
send(connfd, buff, n, 0); // 回射
close(connfd);
}
close(listenfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 4096
int main(int argc, char** argv) {
if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); exit(1); }
int sockfd;
struct sockaddr_in servaddr;
char sendline[MAXLINE], recvline[MAXLINE];
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket"); exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
perror("inet_pton"); exit(1);
}
if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
perror("connect"); exit(1);
}
printf("send msg: ");
fgets(sendline, MAXLINE, stdin);
send(sockfd, sendline, strlen(sendline), 0);
int n = recv(sockfd, recvline, MAXLINE, 0);
recvline[n] = '\0';
printf("echo: %s\n", recvline);
close(sockfd);
return 0;
}
select/poll/epoll 實現高併發fcntl(fd, F_SETFL, O_NONBLOCK)SIGIOsendfile/splicesendto/recvfrom,無連接編寫程序實現:
Server 綁定 192.168.100.168:6666,收到 "Client Query" 印 "Receive Query"
Client 向 192.168.100.168:6666 依次發送三條消息後退出
提示:修改上述範例的 IP/端口與收發邏輯即可。
socket/bind/listen/accept/connect/read/write/close 七大核心 APIhtons/htonl)是避坑關鍵「一切皆 Socket」,深入內核協議棧,方得網絡編程真諦。