首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux Socket 网络编程全攻略:从原理到实战

     編輯:關於C語言

一切皆 Socket

Socket 起源于 BSD Unix,是网络通信的标准 API。Linux 遵循“一切皆文件”哲学:Socket 也是文件描述符,可用 read/write/close 操作,也提供专用 send/recv 等接口。

进程间通信标识:三元组

网络中唯一标识一个通信端点:(IP 地址, 协议, 端口)。TCP/UDP 端口区分同一主机上的不同应用进程。

核心 API 速查

函数作用关键参数
socket(domain, type, protocol)创建套接字,返回 fdAF_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 三次握手与四次挥手在 API 中的对应

阶段客户端服务端TCP 报文
建立连接connect() 发 SYN → 阻塞listen() 等待SYN
收 SYN+ACK → 发 ACK → connect() 返回accept() 返回新 fdSYN+ACK, ACK
关闭连接close() 发 FIN → 等 ACK → TIME_WAIT收 FIN → 发 ACK → close() 发 FINFIN, ACK, FIN, ACK
  • connect() 在第二次握手(收到 SYN+ACK)后返回。
  • accept() 在第三次握手(收到 ACK)后返回。
  • 主动关闭方进入 TIME_WAIT (2MSL),防止旧包干扰新连接。

最简 TCP Echo 服务器/客户端

Server

#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);
    }
}

Client

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);
}

并发模型演进

  1. 迭代服务器:上例,一次只服务一个客户。
  2. fork() 多进程:accept 后 fork,子进程处理,父进程继续 accept。简单但进程开销大。
  3. pthread 多线程:每连接一线程,共享内存,需锁保护共享数据。
  4. I/O 多路复用:select/poll/epoll 单线程监控多 fd。epoll (Linux) 支持万级连接,C10K 标配。
  5. 异步 I/O (io_uring):Linux 5.1+,真正异步,零拷贝,高性能新标准。

常见坑点速查

  • 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)。
  • 粘包/拆包:TCP 是流式,应用层需自定义协议(定长、分隔符、长度前缀)。
  • 缓冲区大小: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 抓包深入内核视角。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved