【Linux】Linux 网络编程入门:Socket 编程详解
Socket 是 Linux(以及几乎所有类 Unix 系统)网络编程的基石。
掌握 Socket 编程,是理解网络通信、服务器开发、分布式系统、微服务通信的基础。
本文从零开始,逐步带你理解 Socket 的本质 → 基本 API → 完整 TCP 服务器/客户端 → 常见模型,适合初学者快速上手,也适合有一定基础的人查漏补缺。
1. Socket 是什么?(最核心的概念)
一句话总结:
Socket 是操作系统提供的一种抽象,用于在不同主机(或同一主机不同进程)之间进行双向通信。
它本质上是内核中一段通信端点的描述,包含:
- 协议族(IPv4 / IPv6 / Unix Domain)
- 传输层协议(TCP / UDP)
- IP 地址
- 端口号
在 Linux 中,Socket 是一个文件描述符(fd),可以用 read/write/close 等系统调用操作。
2. Socket 编程核心 API 一览表
| 分类 | 函数原型 | 作用 | 常见参数说明 | 返回值含义 |
|---|---|---|---|---|
| 创建 | socket(int domain, int type, int protocol) | 创建 socket | domain: AF_INET / AF_INET6 / AF_UNIX | ≥0:文件描述符,-1:失败 |
| 绑定 | bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) | 给 socket 绑定 IP + 端口 | — | 0 成功,-1 失败 |
| 监听 | listen(int sockfd, int backlog) | 设置被动监听(服务器) | backlog:半连接队列长度建议值 | 0 成功,-1 失败 |
| 接受连接 | accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) | 接受客户端连接,返回新连接 fd | addr 用于返回客户端地址 | ≥0:新连接 fd,-1:失败 |
| 连接 | connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) | 客户端主动发起连接 | — | 0 成功,-1 失败 |
| 发送数据 | send(int sockfd, const void *buf, size_t len, int flags) | 发送数据 | flags:常用 0 / MSG_DONTWAIT / MSG_NOSIGNAL | >0:发送字节数,0:连接关闭,-1:错误 |
| 接收数据 | recv(int sockfd, void *buf, size_t len, int flags) | 接收数据 | — | >0:接收字节数,0:对方关闭,-1:错误 |
| 关闭 | close(int sockfd) | 关闭 socket | — | 0 成功,-1 失败 |
3. TCP 服务器完整示例(最经典的写法)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8888
#define BACKLOG 128
#define BUF_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUF_SIZE];
// 1. 创建 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 地址重用(避免 TIME_WAIT 导致 bind 失败)
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 3. 绑定地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 4. 开始监听
if (listen(server_fd, BACKLOG) == -1) {
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
while (1) {
// 5. 接受连接(阻塞式)
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept failed");
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("New connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
// 6. 读写数据
ssize_t n = read(client_fd, buffer, BUF_SIZE - 1);
if (n > 0) {
buffer[n] = '\0';
printf("Received: %s\n", buffer);
// 回显
write(client_fd, "Server received: ", 17);
write(client_fd, buffer, n);
}
// 7. 关闭客户端连接
close(client_fd);
}
close(server_fd);
return 0;
}
4. TCP 客户端完整示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUF_SIZE 1024
int main() {
int sock;
struct sockaddr_in serv_addr;
char buffer[BUF_SIZE];
// 1. 创建 socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 设置服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
perror("Invalid address");
exit(EXIT_FAILURE);
}
// 3. 连接服务器
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("connect failed");
exit(EXIT_FAILURE);
}
printf("Connected to server %s:%d\n", SERVER_IP, PORT);
// 4. 发送数据
const char *message = "Hello from client!";
send(sock, message, strlen(message), 0);
// 5. 接收响应
ssize_t n = read(sock, buffer, BUF_SIZE - 1);
if (n > 0) {
buffer[n] = '\0';
printf("Server reply: %s\n", buffer);
}
close(sock);
return 0;
}
5. 常见 Socket 编程模型对比(选择题/面试常考)
| 模型 | 并发能力 | 代码复杂度 | 资源占用 | 适用场景 | 备注 |
|---|---|---|---|---|---|
| 多进程(fork) | 高 | 中 | 高 | 连接数少、每个连接处理时间长 | 经典 Apache prefork 模式 |
| 多线程 | 中高 | 中 | 中 | 连接数中等 | pthread / C++ std::thread |
| IO 多路复用 | 高 | 高 | 低 | 高并发、短连接 | select / poll / epoll(Linux 首选) |
| 事件驱动 | 极高 | 高 | 低 | 高并发服务器 | libevent / libev / libuv / epoll |
| 协程 | 高 | 中~高 | 极低 | 高并发、业务逻辑复杂 | libco / boost.coroutine / C++20 coroutine |
6. 学习进阶路线建议(Linux 网络编程)
| 阶段 | 重点内容 | 推荐练习 |
|---|---|---|
| 入门 | socket / bind / listen / accept / connect / read / write / close | 实现 echo 服务器/客户端 |
| 中级 | setsockopt / getsockopt / SO_REUSEADDR / SO_KEEPALIVE / TCP_NODELAY | 处理 TIME_WAIT、粘包、半包 |
| 进阶 | select / poll / epoll / epoll ET / LT 模式 | 实现高并发 echo server |
| 高级 | 非阻塞 IO + 状态机 / Reactor / Proactor / 协程网络库 | 实现简易 Redis 服务器 / Web 服务器 |
| 实战 | muduo / libevent / nginx / redis 网络模块阅读 | 理解真实项目中的网络模型 |
7. 小结:一句话记住 Socket 编程本质
“Socket 编程 = 创建通信端点 + 绑定地址(服务器) + 建立连接 + 数据收发 + 关闭连接”
如果你现在想继续深入某个具体方向,可以直接告诉我:
- epoll 的 ET/LT 模式区别与代码示例
- 如何处理 TCP 粘包/半包(长度前缀、定界符、协议)
- 实现一个简单的 Reactor 模型服务器
- 多进程、多线程、epoll 三种并发模型完整对比代码
- setsockopt 常用选项详解(SO_REUSEPORT、TCP_NODELAY 等)
随时说,我可以继续手把手带你写代码或深入讲解。