Socket 起源于 BSD Unix,是网络通信的标准 API。Linux 遵循“一切皆文件”哲学:Socket 也是文件描述符,可用 read/write/close 操作,也提供专用 send/recv 等接口。
网络中唯一标识一个通信端点:(IP 地址, 协议, 端口)。TCP/UDP 端口区分同一主机上的不同应用进程。
| 函数 | 作用 | 关键参数 |
|---|---|---|
socket(domain, type, protocol) | 创建套接字,返回 fd | AF_INET, SOCK_STREAM, 0 |
bind(fd, addr, len) | 绑定本地地址 | sockaddr_in 含 IP+端口 |
listen(fd, backlog) | 转为被动监听 | 内核维护已完成/未完成连接队列 |
connect(fd, addr, len) | 主动发起连接 | 阻塞直到三次握手完成 |
accept(fd, addr, len) | 从已完成队列取出连接 | 返回新 fd,原 fd 继续监听 |
read/write / send/recv | 数据收发 | send/recv 有 flags 控制 |
close(fd) | 关闭套接字 | 引用计数归零才发 FIN |
网络字节序 = 大端序。主机字节序可能是小端(x86/ARM)。必须转换:
uint16_t port = htons(6666); // host to network short
uint32_t ip = htonl(INADDR_ANY);
uint16_t hport = ntohs(sin_port); // network to host
忽略字节序是跨平台网络编程第一大坑。
| 阶段 | 客户端 | 服务端 | TCP 报文 |
|---|---|---|---|
| 建立连接 | connect() 发 SYN → 阻塞 | listen() 等待 | SYN |
收 SYN+ACK → 发 ACK → connect() 返回 | accept() 返回新 fd | SYN+ACK, ACK | |
| 关闭连接 | close() 发 FIN → 等 ACK → TIME_WAIT | 收 FIN → 发 ACK → close() 发 FIN | FIN, ACK, FIN, ACK |
connect() 在第二次握手(收到 SYN+ACK)后返回。accept() 在第三次握手(收到 ACK)后返回。TIME_WAIT (2MSL),防止旧包干扰新连接。#include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <unistd.h>
int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {.sin_family=AF_INET, .sin_port=htons(6666), .sin_addr.s_addr=INADDR_ANY};
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
listen(lfd, 128);
printf("Waiting on 6666...\n");
while (1) {
int cfd = accept(lfd, NULL, NULL);
char buf[1024]; int n = read(cfd, buf, sizeof(buf));
if (n > 0) write(cfd, buf, n);
close(cfd);
}
}
int main(int argc, char **argv) {
if (argc != 2) return 1;
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {.sin_family=AF_INET, .sin_port=htons(6666)};
inet_pton(AF_INET, argv[1], &addr.sin_addr);
connect(fd, (struct sockaddr*)&addr, sizeof(addr));
char buf[1024]; fgets(buf, sizeof(buf), stdin);
write(fd, buf, strlen(buf));
close(fd);
}
fork() 多进程:accept 后 fork,子进程处理,父进程继续 accept。简单但进程开销大。pthread 多线程:每连接一线程,共享内存,需锁保护共享数据。select/poll/epoll 单线程监控多 fd。epoll (Linux) 支持万级连接,C10K 标配。bind "Address already in use" → 设置 SO_REUSEADDR:setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))。read 返回 0 → 对端关闭,需 close 本端。send 返回 EPIPE/SIGPIPE → 对端已关闭,忽略信号 signal(SIGPIPE, SIG_IGN)。SO_SNDBUF/SO_RCVBUF 可调,但通常默认即可。服务器绑定 192.168.100.168:6666,仅接受来自 192.168.100.2 的连接。收到 "Client Query" 打印 "Receive Query"。
客户端依次发送 "Client Query test"、"Client Query"、"Client Query Quit" 后退出。
提示:accept 返回的 sockaddr_in 含客户端 IP,用 inet_ntop 转字符串比对;或用 firewall/iptables 在内核层过滤。
Socket 编程核心是理解 TCP 状态机与 API 对应关系、字节序转换、并发模型选择。从最简 Echo 起步,逐步引入 epoll、线程池、协议解析、TLS 加密,即可构建生产级网络服务。建议配合 man 7 ip、man 7 tcp、Wireshark 抓包深入内核视角。