嵌入式C/C++进阶-进程/线程通信《套接字socket,一文详解》
目录
1 简介
Socket(套接字) 是一种通用的通信机制,它最初用于 网络通信(TCP/IP),但也可以用于 本地进程间通信(IPC)。它本质上是对底层网络通信的抽象封装,允许不同主机、甚至同一主机内的不同进程进行 双向数据传输。它主要分为网络套接字(跨主机)、Unix域套接字(本地套接字)等。主要用于进程间通信,虽然线程中也可以使用(不推荐)。
1.1 本质
Socket 是通信端点的抽象,它用一个文件描述符(FD)来表示通信通道。用户进程通过读写这个 FD,就能像操作文件一样进行数据收发。它屏蔽了底层的 TCP/IP 协议或内核数据传输细节,实现了统一的通信接口。 本质上:Socket = 用文件形式封装的通信通道。
1.2 原理
1️⃣ 创建阶段:调用 socket() 后,内核会创建一个 struct socket 对象,为其分配发送和接收缓冲区,并返回文件描述符给用户进程。
2️⃣ 连接阶段:服务端:通过 bind() 绑定地址和端口 → listen() 等待连接 → accept() 接收连接;客户端:调用 connect() 发起连接(TCP 会建立三次握手)。
3️⃣ 收发阶段:send():将数据从用户空间复制到内核发送缓冲区,由网络协议栈发送。recv():从内核接收缓冲区读取数据,复制到用户空间。在非阻塞模式下,如果缓冲区不可用,会返回 EAGAIN,可用 select() / epoll() 异步等待。
Unix 域套接字(UNIX Domain Socket, AF_UNIX)在本质与机制上与网络套接字几乎一样,只是它不经过网络协议栈,而是完全在内核内部通过内存实现数据传递。

1.3 标准
纳入 POSIX 标准(POSIX.1g 网络 API 扩展),常说 “POSIX Socket” 或 “BSD Socket”。
此外套接字不只有以上的TCP/IP或者Unix,它是一个 通用通信接口,由“协议族(Family)”决定具体通信机制,上述内容只是基于最常见的进行通俗讲解。无论你使用哪种协议族(IPv4、IPv6、本地 UNIX、原始、蓝牙……),应用层看到的其实都是同一套接口,这正是 BSD Socket 设计的最大优势:统一接口,屏蔽底层差异。
| 协议族(Address Family) | 说明 | 常见用途 |
|---|---|---|
| AF_INET | IPv4 网络协议族 | 使用 TCP/UDP 通信 |
| AF_INET6 | IPv6 网络协议族 | IPv6 网络通信 |
| AF_UNIX / AF_LOCAL | Unix 本地域协议族 | 本机进程间通信(IPC) |
| AF_PACKET | Linux 原始链路层接口 | 抓包、网络嗅探(如 tcpdump) |
| AF_NETLINK | 用户态与内核态通信 | 内核网络配置、路由管理(如 ip 命令) |
| AF_BLUETOOTH | 蓝牙协议族 | 蓝牙设备通信 |
| AF_CAN | 控制器局域网(CAN 总线) | 嵌入式设备通信(汽车、工业控制) |
| AF_XDP / AF_VSOCK | 高性能数据通道 / 虚拟机通信 | eBPF、虚拟化场景 |
1.4 标准接口


2 C语言示例
2.1 TCP实现
基于 BSD Socket 的 TCP 客户端封装
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
typedef struct {
int sockfd;
char server_ip[64];
int server_port;
int reconnect_interval;
int connected;
pthread_t recv_thread;
pthread_mutex_t lock;
} TCPClient;
// 接收线程函数
void* recv_loop(void* arg) {
TCPClient* client = (TCPClient*)arg;
char buffer[1024];
while (1) {
if (!client->connected) {
sleep(1);
continue;
}
int n = recv(client->sockfd, buffer, sizeof(buffer)-1, 0);
if (n > 0) {
buffer[n] = 0;
printf("[Recv]: %s\n", buffer);
} else if (n == 0) {
printf("Server closed connection\n");
client->connected = 0;
close(client->sockfd);
} else {
perror("recv");
client->connected = 0;
close(client->sockfd);
}
}
return NULL;
}
// 初始化客户端
void init_client(TCPClient* client, const char* ip, int port, int retry_sec) {
memset(client, 0, sizeof(TCPClient));
strncpy(client->server_ip, ip, sizeof(client->server_ip)-1);
client->server_port = port;
client->reconnect_interval = retry_sec;
pthread_mutex_init(&client->lock, NULL);
}
// 连接服务器
int connect_server(TCPClient* client) {
struct sockaddr_in serv_addr;
while (1) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(client->server_port);
inet_pton(AF_INET, client->server_ip, &serv_addr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
printf("Connected to server\n");
pthread_mutex_lock(&client->lock);
client->sockfd = sockfd;
client->connected = 1;
pthread_mutex_unlock(&client->lock);
return 0;
} else {
perror("connect");
close(sockfd);
printf("Retry in %d seconds...\n", client->reconnect_interval);
sleep(client->reconnect_interval);
}
}
}
// 发送数据
int send_data(TCPClient* client, const char* data, size_t len) {
pthread_mutex_lock(&client->lock);
if (!client->connected) {
pthread_mutex_unlock(&client->lock);
return -1;
}
int n = send(client->sockfd, data, len, 0);
if (n < 0) {
perror("send");
client->connected = 0;
close(client->sockfd);
}
pthread_mutex_unlock(&client->lock);
return n;
}
// 启动客户端
void start_client(TCPClient* client) {
connect_server(client);
pthread_create(&client->recv_thread, NULL, recv_loop, client);
}
// 停止客户端
void stop_client(TCPClient* client) {
client->connected = 0;
pthread_cancel(client->recv_thread);
pthread_join(client->recv_thread, NULL);
pthread_mutex_destroy(&client->lock);
close(client->sockfd);
}
// 示例使用
int main() {
TCPClient client;
init_client(&client, "127.0.0.1", 8888, 5);
start_client(&client);
while (1) {
char msg[128];
fgets(msg, sizeof(msg), stdin);
msg[strcspn(msg, "\n")] = 0; // 去掉换行
send_data(&client, msg, strlen(msg));
}
stop_client(&client);
return 0;
}
服务端封装
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
#define MAX_CLIENTS 100
typedef void (*on_message_cb)(int client_id, const char* msg, int len);
typedef struct {
int sockfd; // 客户端套接字
struct sockaddr_in addr; // 客户端地址
pthread_t recv_thread; // 接收线程
int connected; // 是否已连接
int id; // 客户端唯一ID
} Client;
typedef struct {
int listenfd;
Client clients[MAX_CLIENTS];
pthread_mutex_t clients_lock;
on_message_cb message_cb; // 消息回调
} TCPServer;
// 客户端接收线程
void* client_thread(void* arg) {
Client* client = (Client*)arg;
char buffer[1024];
while (client->connected) {
int n = recv(client->sockfd, buffer, sizeof(buffer)-1, 0);
if (n > 0) {
buffer[n] = 0;
if (client->id >=0 && client->connected) {
printf("[Client %d]: %s\n", client->id, buffer);
// 调用回调函数
if (client->connected) {
// 假设回调不阻塞
on_message_cb cb = ((TCPServer*)client->recv_thread)->message_cb;
if(cb) cb(client->id, buffer, n);
}
}
} else if (n == 0) {
printf("Client %d disconnected\n", client->id);
client->connected = 0;
close(client->sockfd);
} else {
perror("recv");
client->connected = 0;
close(client->sockfd);
}
}
return NULL;
}
// 初始化服务端
void init_server(TCPServer* server, int port, on_message_cb cb) {
memset(server, 0, sizeof(TCPServer));
pthread_mutex_init(&server->clients_lock, NULL);
server->message_cb = cb;
for (int i = 0; i < MAX_CLIENTS; i++)
server->clients[i].sockfd = -1;
server->listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (server->listenfd < 0) {
perror("socket");
exit(1);
}
int opt = 1;
setsockopt(server->listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
if (bind(server->listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(server->listenfd, 5) < 0) {
perror("listen");
exit(1);
}
printf("Server listening on port %d...\n", port);
}
// 添加新客户端
int add_client(TCPServer* server, int sockfd, struct sockaddr_in addr) {
pthread_mutex_lock(&server->clients_lock);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (server->clients[i].sockfd == -1) {
server->clients[i].sockfd = sockfd;
server->clients[i].addr = addr;
server->clients[i].connected = 1;
server->clients[i].id = i;
pthread_create(&server->clients[i].recv_thread, NULL, client_thread, &server->clients[i]);
pthread_mutex_unlock(&server->clients_lock);
return i;
}
}
pthread_mutex_unlock(&server->clients_lock);
return -1; // 满员
}
// 启动监听循环
void start_server(TCPServer* server) {
while (1) {
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(client_addr);
int connfd = accept(server->listenfd, (struct sockaddr*)&client_addr, &addrlen);
if (connfd < 0) {
perror("accept");
continue;
}
int id = add_client(server, connfd, client_addr);
if (id >= 0)
printf("New client connected: %d\n", id);
else {
printf("Client limit reached, rejecting connection\n");
close(connfd);
}
}
}
// 发送数据给指定客户端
int send_to_client(TCPServer* server, int client_id, const char* data, int len) {
if (client_id <0 || client_id >= MAX_CLIENTS)
return -1;
pthread_mutex_lock(&server->clients_lock);
Client* client = &server->clients[client_id];
if (!client->connected) {
pthread_mutex_unlock(&server->clients_lock);
return -1;
}
int n = send(client->sockfd, data, len, 0);
if (n < 0) {
perror("send");
client->connected = 0;
close(client->sockfd);
}
pthread_mutex_unlock(&server->clients_lock);
return n;
}
// 广播数据给所有客户端
void broadcast(TCPServer* server, const char* data, int len) {
pthread_mutex_lock(&server->clients_lock);
for (int i = 0; i < MAX_CLIENTS; i++) {
Client* client = &server->clients[i];
if (client->connected)
send(client->sockfd, data, len, 0);
}
pthread_mutex_unlock(&server->clients_lock);
}
// 停止服务端
void stop_server(TCPServer* server) {
pthread_mutex_lock(&server->clients_lock);
for (int i = 0; i < MAX_CLIENTS; i++) {
Client* client = &server->clients[i];
if (client->connected) {
client->connected = 0;
close(client->sockfd);
}
}
pthread_mutex_unlock(&server->clients_lock);
close(server->listenfd);
pthread_mutex_destroy(&server->clients_lock);
}
// 示例消息回调
void on_msg(int client_id, const char* msg, int len) {
printf("Callback - Client %d: %s\n", client_id, msg);
}
int main() {
TCPServer server;
init_server(&server, 8888, on_msg);
// 启动监听循环
start_server(&server);
// 停止服务端(可在信号处理或其他逻辑调用)
// stop_server(&server);
return 0;
}
高并发场景用select 和 poll ,作用就是帮程序“同时盯着多个套接字”,告诉你哪些套接字有事情需要处理,核心目的是实现 I/O 多路复用。
具体来说:
-
为什么需要它们
-
在 TCP/UDP 服务端,可能同时有很多客户端连接。
-
如果用普通阻塞方式,每个客户端都要开一个线程等待数据,线程多了开销大。
-
select/poll 让一个线程就能同时管理多个套接字,不用每个客户端开线程。
-
-
它们干什么
-
监控多个套接字的状态:
-
哪些套接字有数据可读(recv 不会阻塞)
-
哪些套接字可以写(send 不会阻塞)
-
哪些套接字发生异常
-
-
一旦有事件发生,它们就告诉你,你只去处理这些套接字,其余套接字不用浪费时间。
-
-
作用总结
-
提高并发能力:少量线程管理大量客户端
-
避免阻塞:不必让 recv/send 无限等待
-
节省系统资源:减少线程和上下文切换开销
-
可扩展:适合高并发服务器、嵌入式系统、聊天/游戏服务器等
-
2.2 UDP实现
-
客户端:
-
不需要连接,直接
sendto/recvfrom -
可以设置接收线程循环等待服务器消息
-
可以选择发送给指定服务器地址
-
-
服务端:
-
监听一个 UDP 端口
-
每条消息都带来源地址 (
struct sockaddr_in) -
可以选择单播回发送者或广播
-
1. UDP 客户端示例(带接收线程 + 发送接口)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
typedef struct {
int sockfd;
struct sockaddr_in server_addr;
pthread_t recv_thread;
int running;
} UDPClient;
// 接收线程
void* recv_loop(void* arg) {
UDPClient* client = (UDPClient*)arg;
char buffer[1024];
struct sockaddr_in from;
socklen_t addrlen = sizeof(from);
while (client->running) {
int n = recvfrom(client->sockfd, buffer, sizeof(buffer)-1, 0,
(struct sockaddr*)&from, &addrlen);
if (n > 0) {
buffer[n] = 0;
printf("[Server]: %s\n", buffer);
}
}
return NULL;
}
// 初始化客户端
void init_client(UDPClient* client, const char* ip, int port) {
client->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
client->running = 1;
memset(&client->server_addr, 0, sizeof(client->server_addr));
client->server_addr.sin_family = AF_INET;
client->server_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &client->server_addr.sin_addr);
pthread_create(&client->recv_thread, NULL, recv_loop, client);
}
// 发送数据
void send_data(UDPClient* client, const char* msg, size_t len) {
sendto(client->sockfd, msg, len, 0,
(struct sockaddr*)&client->server_addr, sizeof(client->server_addr));
}
// 停止客户端
void stop_client(UDPClient* client) {
client->running = 0;
pthread_join(client->recv_thread, NULL);
close(client->sockfd);
}
// 示例使用
int main() {
UDPClient client;
init_client(&client, "127.0.0.1", 8888);
char msg[128];
while (fgets(msg, sizeof(msg), stdin)) {
msg[strcspn(msg, "\n")] = 0;
send_data(&client, msg, strlen(msg));
}
stop_client(&client);
return 0;
}
2. UDP 服务端示例(多客户端可管理 + 消息回调)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#define MAX_CLIENTS 100
typedef void (*udp_msg_cb)(struct sockaddr_in* addr, const char* msg, int len);
typedef struct {
int sockfd;
udp_msg_cb message_cb;
int running;
} UDPServer;
// 接收线程
void* recv_loop(void* arg) {
UDPServer* server = (UDPServer*)arg;
char buffer[1024];
struct sockaddr_in from;
socklen_t addrlen = sizeof(from);
while (server->running) {
int n = recvfrom(server->sockfd, buffer, sizeof(buffer)-1, 0,
(struct sockaddr*)&from, &addrlen);
if (n > 0) {
buffer[n] = 0;
printf("[Client %s:%d] %s\n", inet_ntoa(from.sin_addr),
ntohs(from.sin_port), buffer);
if (server->message_cb)
server->message_cb(&from, buffer, n);
}
}
return NULL;
}
// 初始化服务端
void init_server(UDPServer* server, int port, udp_msg_cb cb) {
server->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
server->running = 1;
server->message_cb = cb;
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
if (bind(server->sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
exit(1);
}
pthread_t tid;
pthread_create(&tid, NULL, recv_loop, server);
}
// 发送数据给指定客户端
void send_to_client(UDPServer* server, struct sockaddr_in* addr, const char* msg, int len) {
sendto(server->sockfd, msg, len, 0, (struct sockaddr*)addr, sizeof(*addr));
}
// 停止服务端
void stop_server(UDPServer* server) {
server->running = 0;
close(server->sockfd);
}
// 示例消息回调
void on_msg(struct sockaddr_in* addr, const char* msg, int len) {
char reply[128];
snprintf(reply, sizeof(reply), "Server received: %s", msg);
send_to_client(&(UDPServer){.sockfd = 0}, addr, reply, strlen(reply)); // 注意:需传正确 server.sockfd
}
// 示例主函数
int main() {
UDPServer server;
init_server(&server, 8888, NULL);
printf("UDP server listening on port 8888...\n");
while(1) {
sleep(1);
}
stop_server(&server);
return 0;
}
注:UDP为什么要用sendto / recvfrom
1. UDP 默认是无连接的
-
推荐方式:使用
sendto/recvfrom-
每次都指定目标地址(客户端或服务端)
-
接收时可以知道消息来源
-
适合多客户端场景
-
2. UDP 也可以用 connect + send / recv
-
其实 UDP 也支持
connect(sockfd, addr, addrlen) -
效果:
-
套接字绑定了远程地址
-
后续
send就相当于sendto指定这个地址 -
后续
recv只接收这个远程地址的数据
-
-
限制:
-
只能和一个目标通信
-
不会自动知道其他客户端发送的消息
-
2.3 UNIX实现
1. UNIX Socket 客户端封装
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define UNIX_SOCKET_PATH "/tmp/my_unix_socket"
typedef struct {
int sockfd;
char path[108];
int connected;
pthread_t recv_thread;
pthread_mutex_t lock;
} UnixClient;
void* recv_loop(void* arg) {
UnixClient* client = (UnixClient*)arg;
char buffer[1024];
while (1) {
if (!client->connected) {
sleep(1);
continue;
}
int n = read(client->sockfd, buffer, sizeof(buffer)-1);
if (n > 0) {
buffer[n] = 0;
printf("[Server]: %s\n", buffer);
} else if (n == 0) {
printf("Server closed connection\n");
client->connected = 0;
close(client->sockfd);
} else {
perror("read");
client->connected = 0;
close(client->sockfd);
}
}
return NULL;
}
void init_client(UnixClient* client, const char* path) {
memset(client, 0, sizeof(UnixClient));
strncpy(client->path, path, sizeof(client->path)-1);
pthread_mutex_init(&client->lock, NULL);
}
int connect_server(UnixClient* client) {
struct sockaddr_un addr;
while (1) {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, client->path, sizeof(addr.sun_path)-1);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
printf("Connected to server\n");
pthread_mutex_lock(&client->lock);
client->sockfd = sockfd;
client->connected = 1;
pthread_mutex_unlock(&client->lock);
return 0;
} else {
perror("connect");
close(sockfd);
printf("Retrying in 3 seconds...\n");
sleep(3);
}
}
}
int send_data(UnixClient* client, const char* data, size_t len) {
pthread_mutex_lock(&client->lock);
if (!client->connected) {
pthread_mutex_unlock(&client->lock);
return -1;
}
int n = write(client->sockfd, data, len);
if (n < 0) {
perror("write");
client->connected = 0;
close(client->sockfd);
}
pthread_mutex_unlock(&client->lock);
return n;
}
void start_client(UnixClient* client) {
connect_server(client);
pthread_create(&client->recv_thread, NULL, recv_loop, client);
}
void stop_client(UnixClient* client) {
client->connected = 0;
pthread_cancel(client->recv_thread);
pthread_join(client->recv_thread, NULL);
pthread_mutex_destroy(&client->lock);
close(client->sockfd);
}
// 示例
int main() {
UnixClient client;
init_client(&client, UNIX_SOCKET_PATH);
start_client(&client);
char msg[128];
while (fgets(msg, sizeof(msg), stdin)) {
msg[strcspn(msg, "\n")] = 0;
send_data(&client, msg, strlen(msg));
}
stop_client(&client);
return 0;
}
2. UNIX Socket 服务端封装(多客户端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_SOCKET_PATH "/tmp/my_unix_socket"
#define MAX_CLIENTS 10
typedef struct {
int fd;
pthread_t thread;
} Client;
typedef struct {
int server_fd;
Client clients[MAX_CLIENTS];
int client_count;
} UnixServer;
void* client_handler(void* arg) {
Client* client = (Client*)arg;
char buffer[1024];
int n;
while ((n = read(client->fd, buffer, sizeof(buffer)-1)) > 0) {
buffer[n] = 0;
printf("[Client]: %s\n", buffer);
write(client->fd, buffer, n); // 回显
}
close(client->fd);
client->fd = -1;
return NULL;
}
void start_server(UnixServer* server) {
struct sockaddr_un addr;
server->server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
unlink(UNIX_SOCKET_PATH);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, UNIX_SOCKET_PATH, sizeof(addr.sun_path)-1);
bind(server->server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server->server_fd, 5);
printf("UNIX socket server listening...\n");
server->client_count = 0;
while (1) {
int client_fd = accept(server->server_fd, NULL, NULL);
if (client_fd >= 0 && server->client_count < MAX_CLIENTS) {
Client* c = &server->clients[server->client_count++];
c->fd = client_fd;
pthread_create(&c->thread, NULL, client_handler, c);
}
}
}
int main() {
UnixServer server;
start_server(&server);
return 0;
}
3 面试常见问题
-
什么是 Socket?它在操作系统中的作用是什么?
Socket 是操作系统提供的一组 API,用于在网络上进行进程间通信。它是应用层和传输层之间的接口,封装了 TCP/UDP/IP 协议,提供统一的读写接口。 -
TCP 与 UDP 的区别是什么?在什么时候用 TCP/UDP?
TCP 是面向连接、可靠、顺序传输的字节流协议;UDP 是无连接、不保证可靠性、保留消息边界的协议。TCP 适合需要可靠传输的场景,如文件传输、HTTP;UDP 适合实时性高、可容忍丢包的场景,如语音视频、游戏。 -
什么是 UNIX 域套接字(UNIX Domain Socket)?和 TCP/IP 套接字有什么区别?
UNIX socket 是同一台机器上的进程间通信方式,使用文件路径表示套接字,不依赖网络协议,效率高。TCP/IP socket 可以跨机器通信,使用 IP + 端口标识。 -
阻塞 Socket 与非阻塞 Socket 的区别?如何设置非阻塞?
阻塞 Socket 的读写操作会等待完成才返回,非阻塞 Socket 操作立即返回,未完成时返回 -1 并设置 errno = EAGAIN/EWOULDBLOCK。可以通过 fcntl(sockfd, F_SETFL, O_NONBLOCK) 或 ioctl(sockfd, FIONBIO, &on) 设置。 -
什么是 I/O 多路复用?select、poll、epoll 有什么区别?
I/O 多路复用允许单线程监视多个文件描述符,知道哪些可读、可写或异常。select/poll 都需要线性扫描文件描述符数组,数量大时效率低;epoll 支持事件驱动和内核维护就绪队列,适合高并发。 -
socket()、bind()、listen()、accept()、connect()、send()、recv() 的作用分别是什么?
socket() 创建套接字;bind() 绑定本地地址和端口;listen() 监听 TCP 连接;accept() 接受客户端连接;connect() 发起客户端连接;send()/recv() 发送和接收数据。 -
sendto() 与 recvfrom() 与 TCP send/recv 有何区别?
sendto/recvfrom 用于无连接的 UDP,每次发送可指定目标地址,接收可获得来源地址;TCP send/recv 是面向连接的字节流,不保留消息边界。 -
如何在客户端实现连接超时?
可以将套接字设置为非阻塞,然后调用 connect,如果返回 EINPROGRESS,通过 select/poll 等待指定时间判断是否连接成功。 -
setsockopt() 可以设置哪些选项?举例说明 TCP_KEEPALIVE / SO_RCVTIMEO 等。
setsockopt 可以设置接收/发送超时(SO_RCVTIMEO/SO_SNDTIMEO)、地址重用(SO_REUSEADDR)、心跳(TCP_KEEPALIVE)、缓冲区大小(SO_RCVBUF/SO_SNDBUF)等。 -
shutdown() 与 close() 的区别是什么?在什么时候使用?
close() 关闭套接字描述符,释放资源;shutdown() 可以部分关闭读或写端(SHUT_RD/SHUT_WR/SHUT_RDWR),适合半关闭 TCP 连接。 -
如何实现一个支持多客户端的 TCP 服务器?使用线程、进程还是 select/poll/epoll?
可以使用多线程/多进程为每个客户端分配处理,也可以使用 select/poll/epoll 实现单线程多客户端 I/O 多路复用,高并发时更节省资源。 -
如何实现一个可靠的客户端自动重连机制?
客户端在连接失败或连接断开时,循环尝试重新 connect,等待指定时间后重试,并可增加最大重试次数或指数退避策略。 -
UDP 如何实现类似 TCP 的可靠传输?
需要在应用层实现 ACK 确认、重传、序列号、超时重试等机制,或者使用现成协议如 QUIC、RUDP。 -
如何在多线程中安全使用同一个 Socket 发送数据?
使用互斥锁保护 send/write 操作,避免同时多线程写导致数据混乱或部分发送。 -
如何实现客户端的心跳机制,及时发现服务器断开?
客户端定期发送心跳包(固定格式或空包),服务器返回响应,若超时未收到响应,则认为连接断开。 -
客户端 recv 返回 0 表示什么?如何处理?
表示 TCP 连接被对端关闭,应关闭套接字并尝试重连或退出。 -
recv 返回 -1 且 errno = EAGAIN 时意味着什么?
表示非阻塞套接字当前没有数据可读,不是错误,程序可以稍后重试或使用 select/poll 监听可读事件。 -
在高并发情况下,为什么不能为每个客户端创建线程?如何优化?
线程数量过多会消耗大量内存和上下文切换资源。优化方法:使用 I/O 多路复用(select/poll/epoll)或线程池 + 异步处理。 -
解释 TCP 半连接队列和全连接队列(syn backlog)?如何优化?
半连接队列存放已收到 SYN 但未完成三次握手的连接,全连接队列存放已完成握手的连接等待 accept。可通过调整内核参数(如 somaxconn、tcp_max_syn_backlog)优化。 -
Socket 编程常见性能问题有哪些?如何调优?
常见问题包括阻塞等待、线程过多、缓冲区过小、频繁上下文切换、Nagle 算法影响延迟等。可调优方法:使用非阻塞 + I/O 多路复用、调整 SO_RCVBUF/SO_SNDBUF、禁用 Nagle、合理线程池。 -
解释 TCP 的三次握手过程及作用?
第一次握手:客户端发送 SYN;第二次握手:服务器返回 SYN+ACK;第三次握手:客户端返回 ACK,连接建立。作用是确认双方的发送和接收能力及序列号同步。 -
TCP 四次挥手关闭连接的步骤及为什么需要四次?
第一次挥手:主动关闭方发送 FIN;第二次:被动关闭方 ACK;第三次:被动关闭方发送 FIN;第四次:主动关闭方 ACK。四次是因为 TCP 是全双工,每个方向需要单独关闭。 -
什么是 TCP 滑动窗口?如何影响传输效率?
滑动窗口是 TCP 流量控制机制,用于控制发送方未被确认的数据量。窗口大小影响吞吐量,窗口越大,可连续发送的数据越多,提高效率。 -
什么是 TCP 的拥塞控制?常见算法有哪些?
拥塞控制是为了防止网络过载导致丢包。常见算法有慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快重传(Fast Retransmit)、快恢复(Fast Recovery)。 -
解释 TCP 的 TIME_WAIT 状态及存在的意义?
TIME_WAIT 是主动关闭方等待足够时间以确保最后一个 ACK 被接收,防止旧报文干扰新连接。存在可以避免端口重用问题。 -
为什么 UDP 可能会出现丢包、重复或乱序?如何在应用层处理?
UDP 不提供可靠性保障,网络拥塞或路由可能导致丢包或乱序。应用层可以通过序列号、确认应答、重传机制处理。 -
什么是 TCP 的 Nagle 算法?在什么情况下需要禁用?
Nagle 算法用于合并小包,减少网络拥塞。在低延迟应用(如实时游戏或交易系统)可能需要禁用,通过 setsockopt 禁用 TCP_NODELAY。 -
Socket 描述符泄露会带来什么问题?如何避免?
泄露会消耗系统资源,达到上限后无法创建新套接字。应及时 close 套接字,使用 RAII/智能指针或在 finally/析构中释放。 -
解释 TCP Keepalive 的原理及作用?
Keepalive 是 TCP 的心跳机制,定期发送空数据包检测对端是否存活,避免长时间死连接占用资源。 -
在 Linux 中,为什么 send/recv 可能返回小于请求长度?如何保证全部发送?
TCP 是字节流,单次 send/recv 不保证发送/接收全部数据。可通过循环 send/write 或使用 writev/sendmsg 确保全部数据发送。 -
TCP 和 UDP recv 收到的数据是否完整 ?
TCP recv 不保证完整消息,是字节流+内核调度的特性;UDP recv 保留报文边界,每次接收的就是完整消息。
TCP recv 不一定收到完整消息的原因:
-
TCP 是字节流协议:不保留应用发送的消息边界,只保证字节顺序和可靠性。
-
内核缓冲区和调度机制:
-
发送端内核缓冲区可能分段发送
-
接收端内核缓冲区可能只存部分数据
-
内核调度、网络传输、Nagle 算法都会影响一次 recv 返回的长度
-
-
结果:应用层可能收到部分消息或多条消息合并,需要自己解析消息边界。
-
总结:核心原因是 TCP 设计本质,其次是内核缓冲区和调度机制影响。
UDP recv 一般能收到完整消息的原因:
-
UDP 是面向报文的协议:每次 sendto 就是一条独立的报文,内核保留报文边界。
-
内核处理:接收端每次 recvfrom 都返回一条完整报文(除非缓冲区不足,超出部分丢弃)。
-
网络特点:UDP 不做流量控制、拆分或合并,每条报文独立传输。
-
结果:应用层 recvfrom 通常收到完整消息,无需自己处理边界。
更多推荐
所有评论(0)