Socket 是什么?——通俗生活案例讲解

餐厅点餐模型
TCP Socket = 坐堂点餐(有座位、有服务)
餐厅场景:
┌─────────────────┐
│ 餐厅 │
│ (服务端) │
│ ──────────── │
│ 📞 收款台 │ ← listen() 等待客户
│ 🪑 座位 1 │
│ 🪑 座位 2 │
│ 🪑 座位 3 │
└─────────────────┘

accept()

你走进来 (客户端)
connect()

获得座位

点菜 send()

厨房做菜

上菜 recv()
流程:
顾客(客户端) 餐厅(服务端)
| |
|-- 走进餐厅 ----------> |
| socket()
| |
|-- 我要吃饭 ---------> listen()
| (等待有人进来)
| |
|<---- 欢迎光临 --------- accept()
| (分配座位和服务员)
| |
|-- 要一份红烧肉 -----> recv()
| |
|<---- 稍等片刻 -------- send()
| (厨房准备中)
| |
|<---- 红烧肉上菜 ------ send()
| |
|-- 吃完了,结账 -----> recv()
| |
|<---- 总共 68 元 -------- send()
| |
|-- 谢谢,再见 ----------> close()
| |
socket 通信除了用于Web/TCP 服务端那种 listen/accept ,还可以用作Linux 原始套接字/内核接口通信

TCP 三次握手(建立连接)

客户端 服务端
| |
|-- SYN (seq=x) ----------->| 第一次握手
| “我要连接,我的起始序号是 x”
| |
|<- SYN+ACK (seq=y, ack=x+1)-| 第二次握手
| “好的,我同意。我的起始序号是 y,我收到你的 x”
| |
|-- ACK (seq=x+1, ack=y+1)->| 第三次握手
| “确认收到,我发的下一个序号是 x+1,你发的是 y+1”
| |
|========= 连接建立 =========|
| |
| <------ 可以收发数据 ----->|
为什么需要三次?
如果只有两次:
客户端发 SYN → 服务端回复 SYN+ACK → 建立连接

问题:如果客户端的 SYN 在网络中延迟了很久才到达服务端,
服务端不知道这是一个"旧消息",可能会建立一个"幽灵连接"。

三次握手解决这个问题:
第三次 ACK 让客户端有机会确认:“这确实是我刚刚发的序号”,
从而避免创建无效连接。
TCP 四次挥手(断开连接)
客户端 服务端
| |
|-- FIN (seq=x) ----------->| 第一次挥手
| “我完了,我要关闭”
| |
|<- ACK (ack=x+1) ----------| 第二次挥手
| “我收到你的关闭信号”
| (但我可能还有数据要发)
| |
| … | 服务端可能还在发数据
| |
|<- FIN (seq=y) ------------| 第三次挥手
| “我也完了,现在可以完全关闭了”
| |
|-- ACK (ack=y+1) -------->| 第四次挥手
| “好的,我确认了”
| |
|========= 连接关闭 =========|

常用的socket函数

核心流程函数

  1. socket(domain, type, protocol):创建 socket,返回文件描述符(fd)。
  2. bind(fd, addr, addrlen):把 fd 绑定到本地 IP+端口(服务端常用)。
  3. listen(fd, backlog):把绑定后的 socket 变成“监听 socket”(服务端)。
  4. accept(listenFd, …):从监听队列取一个新连接,返回新的连接 fd(服务端)。
  5. connect(fd, addr, addrlen):主动连接服务端(客户端)。
  6. send(fd, buf, len, flags) / write(fd, buf, len):发送数据。
  7. recv(fd, buf, len, flags) / read(fd, buf, len):接收数据。
    close(fd):关闭 socket。

地址转换函数

  1. htons() / htonl():主机字节序 → 网络字节序(端口/IP 整数)。
  2. ntohs() / ntohl():网络字节序 → 主机字节序。
  3. inet_addr():IPv4 字符串转整数地址(老接口,能用但不推荐新代码继续扩展)。
    更推荐 inet_pton() / inet_ntop():可读性和可扩展性更好(支持 IPv6)。

常见辅助函数

  1. setsockopt(fd, level, optname, …):设置 socket 选项(例如 SO_REUSEADDR)。
  2. shutdown(fd, how):半关闭连接(只关读或只关写)。
  3. poll()/select()/epoll:多路复用,监听多个 fd 可读写事件(高并发服务端常用)。

socket类型

维度 A:协议族 / 地址族

AF_INET:IPv4 地址族
AF_INET6:IPv6 地址族
AF_PACKET:链路层原始报文
AF_NETLINK:用户态和内核态通信

PF_INET 和 AF_INET 在 Linux 里通常数值相同。

你可以简单理解成:

AF_* 偏“地址族”概念
PF_* 偏“协议族”概念

但实际使用里经常混用。

维度 B:socket 类型

SOCK_STREAM:流式,典型是 TCP
SOCK_DGRAM:报文式,典型是 UDP
SOCK_RAW:原始包,自己看/组包
SOCK_SEQPACKET:有序报文边界,较少见

ioctl 就是用户态程序向内核/驱动发送特殊控制命令的通用入口,不是网线上流动的报文,所以不用 recvmsg、sendto 这种报文接口,而用 ioctl。

这些地方虽然调用了 socket(…),但不是为了像 TCP/UDP 那样真正收发业务报文,而只是为了拿到一个文件描述符 fd,再把这个 fd 传给 ioctl(…),让内核执行网络配置操作

测试结果

测试步骤
终端 1(启动服务端)
./server
你会看到:
Server listening on 127.0.0.1:9000
终端 2(启动客户端)
./client
你会看到:
Connected to server 127.0.0.1:9000
Client sent: hello server
Client received: Message received
服务端会同时打印:
Client connected from 127.0.0.1:xxxxx
Received: hello server

socket通信代码(server.cpp)

#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>

namespace {
    int createTcpSocket(const std::string& address, int port)
    {
        int listenFd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (listenFd < 0)
        {
            std::cerr << "socket failed: " << std::strerror(errno) << std::endl;
            return -1;
        }

        sockaddr_in serverAddr{};
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = inet_addr(address.c_str());
        serverAddr.sin_port        = htons(static_cast<uint16_t>(port));

        if (::bind(listenFd, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0)
        {
            std::cerr << "bind failed: " << std::strerror(errno) << std::endl;
            ::close(listenFd);
            return -1;
        }

        if (::listen(listenFd, SOMAXCONN) < 0)
        {
            std::cerr << "listen failed: " << std::strerror(errno) << std::endl;
            ::close(listenFd);
            return -1;
        }

        return listenFd;
    }

    int acceptConnection(int listenFd)
    {
        int connFd = ::accept(listenFd, nullptr, nullptr);
        if (connFd < 0)
        {
            std::cerr << "accept failed: " << std::strerror(errno) << std::endl;
            return -1;
        }
        return connFd;
    }

    void handleClient(int connFd)
    {
        char    buffer[1024];
        ssize_t bytesRead;

        while ((bytesRead = ::read(connFd, buffer, sizeof(buffer))) > 0)
        {
            std::cout << "Received: " << std::string(buffer, bytesRead) << std::endl;
            const char* response = "Message received";
            if (::write(connFd, response, std::strlen(response)) < 0)
            {
                std::cerr << "write failed: " << std::strerror(errno) << std::endl;
                break;
            }
        }

        if (bytesRead < 0)
        {
            std::cerr << "read failed: " << std::strerror(errno) << std::endl;
        }

        ::close(connFd);
    }

    void runServer(const std::string& address, int port)
    {
        int listenFd = -1;

        listenFd = createTcpSocket(address, port);
        if (listenFd < 0)
        {
            return;
        }

        std::cout << "Server listening on " << address << ":" << port << std::endl;

        while (true)
        {
            const int connFd = acceptConnection(listenFd);
            if (connFd < 0)
            {
                continue;
            }

            std::thread clientThread(handleClient, connFd);
            clientThread.detach();
        }
    }
}

int main()
{
    runServer("127.0.0.1", 9000);
    return 0;
}

socket通信代码(client.cpp)

#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <unistd.h>

namespace {

int createTcpSocket()
{
    int sockFd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd < 0)
    {
        std::cerr << "socket failed: " << std::strerror(errno) << std::endl;
        return -1;
    }
    return sockFd;
}

int connectToServer(int sockFd, const std::string& address, int port)
{
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(address.c_str());
    serverAddr.sin_port        = htons(static_cast<uint16_t>(port));

    if (::connect(sockFd, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0)
    {
        std::cerr << "connect failed: " << std::strerror(errno) << std::endl;
        return -1;
    }

    return 0;
}

int sendMessage(int sockFd, const std::string& message)
{
    if (::send(sockFd, message.c_str(), message.size(), 0) < 0)
    {
        std::cerr << "send failed: " << std::strerror(errno) << std::endl;
        return -1;
    }

    return 0;
}

std::string receiveMessage(int sockFd)
{
    char buffer[1024] = {0};
    ssize_t bytesRead = ::recv(sockFd, buffer, sizeof(buffer) - 1, 0);
    if (bytesRead < 0)
    {
        std::cerr << "recv failed: " << std::strerror(errno) << std::endl;
        return "";
    }

    return std::string(buffer, buffer + bytesRead);
}

void runClient(const std::string& address, int port, const std::string& message)
{
    int sockFd = -1;

    try
    {
        sockFd = createTcpSocket();
        if (sockFd < 0)
        {
            return;
        }

        if (connectToServer(sockFd, address, port) < 0)
        {
            ::close(sockFd);
            return;
        }

        std::cout << "Connected to server " << address << ":" << port << std::endl;

        if (sendMessage(sockFd, message) < 0)
        {
            ::close(sockFd);
            return;
        }

        std::cout << "Client sent: " << message << std::endl;

        const std::string response = receiveMessage(sockFd);
        if (!response.empty())
        {
            std::cout << "Client received: " << response << std::endl;
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    if (sockFd >= 0)
    {
        ::close(sockFd);
    }
}

} // namespace

int main()
{
    runClient("127.0.0.1", 9000, "hello server");
    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐