Socket 基础¶
是什么 / 解决什么问题¶
Socket 是进程间网络通信的抽象接口。通过 socket API,程序可以在不关心底层网络协议细节的情况下进行 TCP/UDP 通信。
使用模式¶
TCP 服务器基本流程¶
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
// 1. 创建 socket
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
// AF_INET: IPv4
// AF_INET6: IPv6
// SOCK_STREAM: TCP (可靠、有序、面向连接)
// SOCK_DGRAM: UDP (不可靠、无序、无连接)
// 2. 绑定地址
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080), // 端口号(网络字节序)
.sin_addr.s_addr = INADDR_ANY, // 监听所有网卡
};
bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
// 3. 开始监听
listen(listen_fd, 128); // backlog: 未完成连接队列大小
// 4. 接受连接
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(client_addr);
int conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen);
// 5. 读写数据
char buf[1024];
ssize_t n = read(conn_fd, buf, sizeof(buf));
write(conn_fd, buf, n);
// 6. 关闭
close(conn_fd);
close(listen_fd);
TCP 客户端基本流程¶
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server = {
.sin_family = AF_INET,
.sin_port = htons(8080),
};
inet_pton(AF_INET, "192.168.1.1", &server.sin_addr);
// 连接服务器
connect(fd, (struct sockaddr *)&server, sizeof(server));
// 收发数据
write(fd, "hello", 5);
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
close(fd);
UDP 通信(无连接)¶
// 服务端
int fd = socket(AF_INET, SOCK_DGRAM, 0);
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
struct sockaddr_in client;
socklen_t clen = sizeof(client);
ssize_t n = recvfrom(fd, buf, sizeof(buf), 0,
(struct sockaddr *)&client, &clen);
sendto(fd, reply, reply_len, 0,
(struct sockaddr *)&client, clen);
// 客户端
int fd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(fd, msg, msg_len, 0, (struct sockaddr *)&server, sizeof(server));
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
创建时直接设置属性(Linux 扩展)¶
// SOCK_NONBLOCK: 创建时就是非阻塞
// SOCK_CLOEXEC: exec 时自动关闭
int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
// accept4 也支持
int conn = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
行为与语义¶
listen 的 backlog 参数¶
客户端 connect ──→ [SYN队列(半连接)] ──→ [Accept队列(全连接)] ──→ accept() 取走
三次握手进行中 三次握手完成,等待accept
backlog 控制的是 Accept 队列(全连接队列)的大小。
SYN 队列大小由 /proc/sys/net/ipv4/tcp_max_syn_backlog 控制。
listen(fd, 128)— Accept 队列最大 128 个已完成连接- 队列满时新连接的 SYN 会被静默丢弃(客户端会重试)
- 实际值被
/proc/sys/net/core/somaxconn截断(默认 4096)
read/write vs recv/send vs recvfrom/sendto¶
| API | 适用 | 额外功能 |
|---|---|---|
read/write |
TCP socket | 无 |
recv/send |
TCP socket | flags 参数(MSG_PEEK, MSG_NOSIGNAL 等) |
recvfrom/sendto |
UDP socket | 可指定/获取对端地址 |
recvmsg/sendmsg |
任意 | 辅助数据、scatter/gather、fd 传递 |
地址复用¶
// 为什么服务器重启时 bind 报 "Address already in use"?
// 因为旧连接处于 TIME_WAIT 状态,地址还被占用
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
// 允许 bind 到处于 TIME_WAIT 的地址
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
// 允许多个 socket bind 到同一个 address:port(负载均衡)
shutdown vs close¶
close(fd);
// 关闭 fd,引用计数减1。只有引用计数为0时才真正关闭连接。
// 如果 fd 被 dup/fork 过,close 不会立即断开。
shutdown(fd, SHUT_RD); // 关闭读端(后续 read 返回 0)
shutdown(fd, SHUT_WR); // 关闭写端(发送 FIN,对端 read 返回 0)
shutdown(fd, SHUT_RDWR); // 同时关闭
// shutdown 不管引用计数,立即影响底层连接
// 典型用途:通知对端"我不再发送数据了"
性能考量¶
- SO_REUSEPORT:多线程服务器各自 bind+listen,内核负载均衡,消除 accept 锁竞争
- accept4:避免额外的 fcntl 调用设置 NONBLOCK/CLOEXEC
- TCP_FASTOPEN:减少一次 RTT(客户端在 SYN 中携带数据)
- 连接池:避免频繁 connect 的三次握手和 TIME_WAIT 开销
常见陷阱与 FAQ¶
1. 忘记网络字节序转换¶
// 错误:直接赋值端口号
addr.sin_port = 8080; // 在小端机器上变成 0x901F
// 正确:htons 转换
addr.sin_port = htons(8080); // 正确的网络字节序 0x1F90
2. connect 超时太长¶
默认 TCP connect 超时约 2 分钟(内核重试 SYN)。对于需要快速失败的场景:
// 方法1:非阻塞 connect + epoll + 自定义超时
// 方法2:setsockopt 设置 TCP_USER_TIMEOUT
int timeout_ms = 3000;
setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout_ms, sizeof(timeout_ms));
3. read 返回的数据量不确定¶
TCP 是字节流,没有消息边界:
// 发送方:
write(fd, "hello world", 11);
// 接收方可能收到:
// 一次 read: "hello world" (11字节)
// 或两次 read: "hello" + " world"
// 或三次 read: "hel" + "lo wo" + "rld"
// 需要应用层协议界定消息边界(长度前缀、分隔符等)
观测与调试¶
# 查看监听端口
ss -tlnp
# 查看连接状态
ss -tnp dst :8080
# 查看 backlog 使用情况
ss -tlnp | grep 8080
# Recv-Q: 当前 Accept 队列中的连接数
# Send-Q: Accept 队列最大值(backlog)
# strace 看连接过程
strace -e trace=socket,bind,listen,accept4,connect -f ./server
延伸阅读¶
man 7 socket— socket 通用概念man 7 tcp/man 7 udp— 协议特定行为man 7 ip— IP 层选项- Stevens《Unix Network Programming》第一卷 — 经典教材