目录

1 简介

1.1 本质

1.2 原理

1.3 标准

1.4 标准接口​

2 C语言示例

2.1 TCP实现

2.2 UDP实现

2.3 UNIX实现

3 面试常见问题


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 多路复用

具体来说:

  1. 为什么需要它们

    • 在 TCP/UDP 服务端,可能同时有很多客户端连接。

    • 如果用普通阻塞方式,每个客户端都要开一个线程等待数据,线程多了开销大。

    • select/poll 让一个线程就能同时管理多个套接字,不用每个客户端开线程。

  2. 它们干什么

    • 监控多个套接字的状态:

      • 哪些套接字有数据可读(recv 不会阻塞)

      • 哪些套接字可以写(send 不会阻塞)

      • 哪些套接字发生异常

    • 一旦有事件发生,它们就告诉你,你只去处理这些套接字,其余套接字不用浪费时间。

  3. 作用总结

    • 提高并发能力:少量线程管理大量客户端

    • 避免阻塞:不必让 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 面试常见问题

  1. 什么是 Socket?它在操作系统中的作用是什么?
    Socket 是操作系统提供的一组 API,用于在网络上进行进程间通信。它是应用层和传输层之间的接口,封装了 TCP/UDP/IP 协议,提供统一的读写接口。

  2. TCP 与 UDP 的区别是什么?在什么时候用 TCP/UDP?
    TCP 是面向连接、可靠、顺序传输的字节流协议;UDP 是无连接、不保证可靠性、保留消息边界的协议。TCP 适合需要可靠传输的场景,如文件传输、HTTP;UDP 适合实时性高、可容忍丢包的场景,如语音视频、游戏。

  3. 什么是 UNIX 域套接字(UNIX Domain Socket)?和 TCP/IP 套接字有什么区别?
    UNIX socket 是同一台机器上的进程间通信方式,使用文件路径表示套接字,不依赖网络协议,效率高。TCP/IP socket 可以跨机器通信,使用 IP + 端口标识。

  4. 阻塞 Socket 与非阻塞 Socket 的区别?如何设置非阻塞?
    阻塞 Socket 的读写操作会等待完成才返回,非阻塞 Socket 操作立即返回,未完成时返回 -1 并设置 errno = EAGAIN/EWOULDBLOCK。可以通过 fcntl(sockfd, F_SETFL, O_NONBLOCK) 或 ioctl(sockfd, FIONBIO, &on) 设置。

  5. 什么是 I/O 多路复用?select、poll、epoll 有什么区别?
    I/O 多路复用允许单线程监视多个文件描述符,知道哪些可读、可写或异常。select/poll 都需要线性扫描文件描述符数组,数量大时效率低;epoll 支持事件驱动和内核维护就绪队列,适合高并发。

  6. socket()、bind()、listen()、accept()、connect()、send()、recv() 的作用分别是什么?
    socket() 创建套接字;bind() 绑定本地地址和端口;listen() 监听 TCP 连接;accept() 接受客户端连接;connect() 发起客户端连接;send()/recv() 发送和接收数据。

  7. sendto() 与 recvfrom() 与 TCP send/recv 有何区别?
    sendto/recvfrom 用于无连接的 UDP,每次发送可指定目标地址,接收可获得来源地址;TCP send/recv 是面向连接的字节流,不保留消息边界。

  8. 如何在客户端实现连接超时?
    可以将套接字设置为非阻塞,然后调用 connect,如果返回 EINPROGRESS,通过 select/poll 等待指定时间判断是否连接成功。

  9. setsockopt() 可以设置哪些选项?举例说明 TCP_KEEPALIVE / SO_RCVTIMEO 等。
    setsockopt 可以设置接收/发送超时(SO_RCVTIMEO/SO_SNDTIMEO)、地址重用(SO_REUSEADDR)、心跳(TCP_KEEPALIVE)、缓冲区大小(SO_RCVBUF/SO_SNDBUF)等。

  10. shutdown() 与 close() 的区别是什么?在什么时候使用?
    close() 关闭套接字描述符,释放资源;shutdown() 可以部分关闭读或写端(SHUT_RD/SHUT_WR/SHUT_RDWR),适合半关闭 TCP 连接。

  11. 如何实现一个支持多客户端的 TCP 服务器?使用线程、进程还是 select/poll/epoll?
    可以使用多线程/多进程为每个客户端分配处理,也可以使用 select/poll/epoll 实现单线程多客户端 I/O 多路复用,高并发时更节省资源。

  12. 如何实现一个可靠的客户端自动重连机制?
    客户端在连接失败或连接断开时,循环尝试重新 connect,等待指定时间后重试,并可增加最大重试次数或指数退避策略。

  13. UDP 如何实现类似 TCP 的可靠传输?
    需要在应用层实现 ACK 确认、重传、序列号、超时重试等机制,或者使用现成协议如 QUIC、RUDP。

  14. 如何在多线程中安全使用同一个 Socket 发送数据?
    使用互斥锁保护 send/write 操作,避免同时多线程写导致数据混乱或部分发送。

  15. 如何实现客户端的心跳机制,及时发现服务器断开?
    客户端定期发送心跳包(固定格式或空包),服务器返回响应,若超时未收到响应,则认为连接断开。

  16. 客户端 recv 返回 0 表示什么?如何处理?
    表示 TCP 连接被对端关闭,应关闭套接字并尝试重连或退出。

  17. recv 返回 -1 且 errno = EAGAIN 时意味着什么?
    表示非阻塞套接字当前没有数据可读,不是错误,程序可以稍后重试或使用 select/poll 监听可读事件。

  18. 在高并发情况下,为什么不能为每个客户端创建线程?如何优化?
    线程数量过多会消耗大量内存和上下文切换资源。优化方法:使用 I/O 多路复用(select/poll/epoll)或线程池 + 异步处理。

  19. 解释 TCP 半连接队列和全连接队列(syn backlog)?如何优化?
    半连接队列存放已收到 SYN 但未完成三次握手的连接,全连接队列存放已完成握手的连接等待 accept。可通过调整内核参数(如 somaxconn、tcp_max_syn_backlog)优化。

  20. Socket 编程常见性能问题有哪些?如何调优?
    常见问题包括阻塞等待、线程过多、缓冲区过小、频繁上下文切换、Nagle 算法影响延迟等。可调优方法:使用非阻塞 + I/O 多路复用、调整 SO_RCVBUF/SO_SNDBUF、禁用 Nagle、合理线程池。

  21. 解释 TCP 的三次握手过程及作用?
    第一次握手:客户端发送 SYN;第二次握手:服务器返回 SYN+ACK;第三次握手:客户端返回 ACK,连接建立。作用是确认双方的发送和接收能力及序列号同步。

  22. TCP 四次挥手关闭连接的步骤及为什么需要四次?
    第一次挥手:主动关闭方发送 FIN;第二次:被动关闭方 ACK;第三次:被动关闭方发送 FIN;第四次:主动关闭方 ACK。四次是因为 TCP 是全双工,每个方向需要单独关闭。

  23. 什么是 TCP 滑动窗口?如何影响传输效率?
    滑动窗口是 TCP 流量控制机制,用于控制发送方未被确认的数据量。窗口大小影响吞吐量,窗口越大,可连续发送的数据越多,提高效率。

  24. 什么是 TCP 的拥塞控制?常见算法有哪些?
    拥塞控制是为了防止网络过载导致丢包。常见算法有慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快重传(Fast Retransmit)、快恢复(Fast Recovery)。

  25. 解释 TCP 的 TIME_WAIT 状态及存在的意义?
    TIME_WAIT 是主动关闭方等待足够时间以确保最后一个 ACK 被接收,防止旧报文干扰新连接。存在可以避免端口重用问题。

  26. 为什么 UDP 可能会出现丢包、重复或乱序?如何在应用层处理?
    UDP 不提供可靠性保障,网络拥塞或路由可能导致丢包或乱序。应用层可以通过序列号、确认应答、重传机制处理。

  27. 什么是 TCP 的 Nagle 算法?在什么情况下需要禁用?
    Nagle 算法用于合并小包,减少网络拥塞。在低延迟应用(如实时游戏或交易系统)可能需要禁用,通过 setsockopt 禁用 TCP_NODELAY。

  28. Socket 描述符泄露会带来什么问题?如何避免?
    泄露会消耗系统资源,达到上限后无法创建新套接字。应及时 close 套接字,使用 RAII/智能指针或在 finally/析构中释放。

  29. 解释 TCP Keepalive 的原理及作用?
    Keepalive 是 TCP 的心跳机制,定期发送空数据包检测对端是否存活,避免长时间死连接占用资源。

  30. 在 Linux 中,为什么 send/recv 可能返回小于请求长度?如何保证全部发送?
    TCP 是字节流,单次 send/recv 不保证发送/接收全部数据。可通过循环 send/write 或使用 writev/sendmsg 确保全部数据发送。

  31. TCP 和 UDP recv 收到的数据是否完整 

TCP recv 不保证完整消息,是字节流+内核调度的特性;UDP recv 保留报文边界,每次接收的就是完整消息。

    TCP recv 不一定收到完整消息的原因:

  • TCP 是字节流协议:不保留应用发送的消息边界,只保证字节顺序和可靠性。

  • 内核缓冲区和调度机制

    • 发送端内核缓冲区可能分段发送

    • 接收端内核缓冲区可能只存部分数据

    • 内核调度、网络传输、Nagle 算法都会影响一次 recv 返回的长度

  • 结果:应用层可能收到部分消息或多条消息合并,需要自己解析消息边界。

  • 总结:核心原因是 TCP 设计本质,其次是内核缓冲区和调度机制影响。

    UDP recv 一般能收到完整消息的原因:

  • UDP 是面向报文的协议:每次 sendto 就是一条独立的报文,内核保留报文边界。

  • 内核处理:接收端每次 recvfrom 都返回一条完整报文(除非缓冲区不足,超出部分丢弃)。

  • 网络特点:UDP 不做流量控制、拆分或合并,每条报文独立传输。

  • 结果:应用层 recvfrom 通常收到完整消息,无需自己处理边界。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐