Socket 网络编程
一、协议族与地址族
socket(AF_INET, SOCK_STREAM, 0); // IPv4 TCP
socket(AF_INET6, SOCK_STREAM, 0); // IPv6 TCP
socket(AF_INET, SOCK_DGRAM, 0); // IPv4 UDP
socket(AF_UNIX, SOCK_STREAM, 0); // Unix 域
socket(AF_PACKET, SOCK_RAW, ...); // 链路层原始
二、字节序
htonl / htons / ntohl / ntohs // 网络字节序 = 大端
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);
inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf));
三、TCP Server
int lfd = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
listen(lfd, 128);
for (;;) {
struct sockaddr_in cli;
socklen_t clilen = sizeof(cli);
int cfd = accept(lfd, (struct sockaddr*)&cli, &clilen);
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &cli.sin_addr, ip, sizeof(ip));
printf("connected: %s:%d\n", ip, ntohs(cli.sin_port));
// 处理 cfd
close(cfd);
}
四、TCP Client
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in srv = {
.sin_family = AF_INET,
.sin_port = htons(80),
};
inet_pton(AF_INET, "192.168.1.10", &srv.sin_addr);
connect(fd, (struct sockaddr*)&srv, sizeof(srv));
// 收发
char buf[1024];
send(fd, "GET / HTTP/1.0\r\n\r\n", 18, 0);
ssize_t n = recv(fd, buf, sizeof(buf), 0);
五、UDP
// server
int fd = socket(AF_INET, SOCK_DGRAM, 0);
bind(fd, ...);
recvfrom(fd, buf, len, 0, (struct sockaddr*)&cli, &clilen);
sendto (fd, buf, len, 0, (struct sockaddr*)&cli, clilen);
// client
sendto(fd, buf, len, 0, (struct sockaddr*)&srv, sizeof(srv));
recvfrom(fd, buf, len, 0, NULL, NULL);
六、I/O 多路复用
1. select
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
int n = select(fd + 1, &rfds, NULL, NULL, &tv);
if (n > 0 && FD_ISSET(fd, &rfds)) { /* 可读 */ }
- 限制:fd 数量(默认 1024)
- 每次要重置集合
2. poll
struct pollfd pfd = { .fd = fd, .events = POLLIN };
int n = poll(&pfd, 1, 1000);
if (n > 0 && (pfd.revents & POLLIN)) { /* 可读 */ }
3. epoll(推荐)
int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
struct epoll_event events[64];
for (;;) {
int n = epoll_wait(epfd, events, 64, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == fd && (events[i].events & EPOLLIN)) {
// 处理
}
}
}
- O(1) 事件通知
- 支持 edge-trigger (EPOLLET)
- 支持 mmap(
epoll_create1 + eventfd)
4. epoll ET 模式注意
- 必须非阻塞
- 一次要读完(
while(read))
- 否则会丢失事件
七、Reactor 模式(推荐)
// 事件循环伪代码
while (1) {
int n = epoll_wait(epfd, events, MAX, -1);
for (i = 0; i < n; i++) {
int fd = events[i].data.fd;
if (fd == lfd) {
cfd = accept(lfd, ...);
epoll_add(epfd, cfd);
} else if (events[i].events & EPOLLIN) {
// 读取,处理请求
// 修改状态后可能触发 EPOLLOUT
} else if (events[i].events & EPOLLOUT) {
// 写入响应
}
}
}
八、常用 setsockopt
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); // 端口复用
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); // 保活
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); // 关 Nagle
setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes));
int sz = 64*1024;
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)); // 接收缓冲
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)); // 发送缓冲
struct linger lg = { .l_onoff = 1, .l_linger = 5 };
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg)); // 优雅关闭
九、超时
struct timeval tv = { .tv_sec = 3, .tv_usec = 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
十、原始 socket
// ICMP ping 示例
int fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
// 抓包(混杂模式)
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
// 注意:需要 CAP_NET_RAW
十一、高性能框架
- libuv:Node.js 的事件循环(也可单独用)
- libevent:BSD 血统,跨平台
- libev:轻量
- Boost.Asio:C++
- 协程:io_uring、io_uring-cpp、协程库
十二、嵌入式注意点
- 注意内存占用:每个连接 8KB+ 内核缓冲
- 用 select/poll 即可(连接数少时)
- 高并发场景才用 epoll
- 关闭 TIME_WAIT 可开启
TCP_TW_REUSE
- 关 SO_REUSEADDR 防止端口冲突
十三、安全
- 校验所有输入
- 用 TLS(OpenSSL / mbedTLS / wolfSSL)
- 限速、防 DoS
- 避免硬编码凭据