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)) { /* 可读 */ }

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)) {
            // 处理
        }
    }
}

4. epoll ET 模式注意

七、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

十一、高性能框架

十二、嵌入式注意点

十三、安全