1.简单的服务端和客户端(仅作为内容补充)


一、什么是 Socket?

Socket(套接字)是操作系统提供的网络通信接口。它把复杂的 TCP/IP 协议栈封装成一组简单的函数调用,让程序员可以像读写文件一样进行网络通信。

生活类比:

Socket 操作 电话类比
socket() 买一部电话
bind() 给电话分配一个号码
listen() 开机,等待来电
accept() 接听电话
connect() 拨打电话
read() / write() 通话
close() 挂断电话

二、TCP 通信流程

        服务器端                              客户端
        ────────                              ────────
     socket()                              socket()
        │                                     │
     bind()                                   │
        │                                     │
     listen()                                 │
        │                                     │
     accept() ←─────── 三次握手 ──────→  connect()
        │                                     │
     read()/write() ←──── 数据传输 ────→ read()/write()
        │                                     │
     close()                              close()

服务器必须先启动并监听,客户端才能连接成功。整个通信过程是全双工的——双方可以同时收发数据。


三、核心 API 一览

函数 原型 作用 调用方
socket() int socket(int domain, int type, int protocol) 创建套接字 双方
bind() int bind(int fd, const sockaddr *addr, socklen_t len) 绑定 IP + 端口 服务器
listen() int listen(int fd, int backlog) 开始监听 服务器
accept() int accept(int fd, sockaddr *addr, socklen_t *len) 接受连接 服务器
connect() int connect(int fd, const sockaddr *addr, socklen_t len) 发起连接 客户端
read() ssize_t read(int fd, void *buf, size_t count) 接收数据 双方
write() ssize_t write(int fd, const void *buf, size_t count) 发送数据 双方
close() int close(int fd) 关闭连接 双方

观察上表你会发现:bind()connect()accept() 的地址参数都是指针sockaddr *),而不是直接传结构体的值。为什么?下一节专门解释。


四、为什么传指针而不是传值?

这是理解 Socket API 设计的关键。几乎所有涉及地址的函数都要求传入 struct sockaddr * 指针,这背后有四个层面的原因。

4.1 哪些函数传了地址指针?

函数 地址参数 方向 说明
bind() const struct sockaddr *addr 入参(只读) 你告诉内核"绑定到这个地址"
connect() const struct sockaddr *addr 入参(只读) 你告诉内核"连接到这个地址"
accept() struct sockaddr *addr 出参(写入) 内核把客户端地址写回给你
getsockname() struct sockaddr *addr 出参(写入) 内核把本机地址写回给你
getpeername() struct sockaddr *addr 出参(写入) 内核把对端地址写回给你
sendto() const struct sockaddr *dest_addr 入参(只读) UDP 指定目标地址
recvfrom() struct sockaddr *src_addr 出参(写入) UDP 获取发送方地址

规律:带 const 的是输入型(你传给内核),不带 const 的是输出型(内核写回给你)。

4.2 原因一:值传递无法"回写"数据

这是最根本的原因。C 语言的函数参数是单向拷贝——函数内部拿到的是副本,修改副本不会影响调用者的原始变量。

// ❌ 假设 accept() 用值传递
sockaddr_in client_addr;
accept(fd, client_addr, &len);
// client_addr 被拷贝了一份传入函数
// 内核修改的是副本,你的 client_addr 永远是空的!
// 你根本拿不到客户端的 IP 和端口。

// ✅ 实际设计:传指针
sockaddr_in client_addr;
accept(fd, (sockaddr *)&client_addr, &len);
// 内核拿到的是 client_addr 的内存地址
// 可以直接往这块内存里写入客户端信息
// 函数返回后,你的 client_addr 就有数据了。

对于 accept()recvfrom() 这类需要内核向用户空间回写数据的函数,值传递在语言层面就不可行。你必须给内核一个"可以写入的内存地址"。

4.3 原因二:结构体太大,值传递代价高

struct sockaddr_in16 字节
struct sockaddr_in628 字节
struct sockaddr_storage128 字节
// 值传递:每次调用都在栈上拷贝整个结构体
bind(fd, addr, sizeof(addr));              // 拷贝 16~128 字节

// 指针传递:只拷贝一个地址(64位系统上 8 字节)
bind(fd, (sockaddr *)&addr, sizeof(addr)); // 只拷贝 8 字节

在高频调用场景下(如 UDP 服务器每秒处理数万个包),反复拷贝大结构体会带来不必要的性能开销。指针传递是固定 O(1) 的成本。

4.4 原因三:统一接口,兼容多种地址族

Socket API 设计于 1980 年代,需要同时支持多种协议,而它们的地址结构体大小完全不同:

struct sockaddr_in   { ... };  // IPv4,16 字节
struct sockaddr_in6  { ... };  // IPv6,28 字节
struct sockaddr_un   { ... };  // Unix Domain Socket,110 字节

如果用值传递,编译器必须知道确切类型才能确定拷贝多少字节,那就得为每种地址族定义不同的函数。而使用 struct sockaddr * 指针 + socklen_t len 的组合,一个函数签名就能处理所有地址类型

// 同一个 bind() 处理所有协议
bind(fd, (struct sockaddr *)&ipv4_addr, sizeof(ipv4_addr));  // IPv4
bind(fd, (struct sockaddr *)&ipv6_addr, sizeof(ipv6_addr));  // IPv6
bind(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr));  // Unix Socket

这就是经典的 “通用指针多态” 模式——用 void* 风格的指针 + 长度参数实现类型无关的接口。

4.5 原因四:系统调用的天然需求

Socket 函数本质上是系统调用,会触发用户态到内核态的切换。内核通过指针来:

  • 读取用户空间的数据(bind / connect)→ 调用 copy_from_user()
  • 写入结果到用户空间(accept / recvfrom)→ 调用 copy_to_user()

这套机制天然基于内存地址(指针),而非值拷贝。

4.6 对比总结

传递方式 能否回写数据 拷贝开销 多协议兼容 系统调用适配
值传递 ❌ 不可能 高(拷贝整个结构体) ❌ 需要不同签名 ❌ 不匹配
指针传递 ✅ 可以 低(仅 8 字节) ✅ 统一签名 ✅ 天然匹配

4.7 编程注意事项

// ⚠️ 错误1:忘记取地址
sockaddr_in addr{};
bind(fd, addr, sizeof(addr));              // ❌ 编译报错
bind(fd, (sockaddr *)&addr, sizeof(addr)); // ✅ 正确

// ⚠️ 错误2:输出型参数未初始化长度
sockaddr_in client_addr{};
socklen_t len = sizeof(client_addr);       // ✅ 必须提前设置
accept(fd, (sockaddr *)&client_addr, &len);

// ⚠️ 错误3:混淆输入/输出方向
// bind/connect 的地址参数带 const → 内核只读,不会修改你的结构体
// accept/recvfrom 的地址参数不带 const → 内核会写入数据

一句话记忆:输入传指针是为了让内核"看到"你的数据,输出传指针是为了让内核"写到"你的内存。


五、地址结构体详解

5.1 struct sockaddr_in(IPv4 专用)

struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族:AF_INET
    in_port_t      sin_port;     // 端口号(必须是网络字节序)
    struct in_addr sin_addr;     // IPv4 地址(必须是网络字节序)
    char           sin_zero[8];  // 填充字节,保持与 sockaddr 等长
};

5.2 struct in_addr

struct in_addr {
    uint32_t s_addr;   // 32位 IPv4 地址(网络字节序)
};

5.3 struct sockaddr(通用地址)

struct sockaddr {
    sa_family_t sa_family;   // 地址族
    char        sa_data[14]; // 地址数据(具体含义由 sa_family 决定)
};

这就是为什么 bind()connect()accept() 的参数类型是 struct sockaddr *——它是所有地址结构体的"基类"。使用时需要将具体的 sockaddr_in * 强制转换为 sockaddr *


六、字节序与地址转换函数

6.1 为什么需要字节序转换?

不同 CPU 存储多字节整数的方式不同:

  • 小端序(x86/x64):低字节在低地址
  • 大端序(网络标准):高字节在低地址

网络通信统一使用大端序(网络字节序),所以本机数据发送到网络前必须转换。

以整数 0x12345678 为例:

主机字节序(小端,x86):  内存中 → [0x78] [0x56] [0x34] [0x12]
网络字节序(大端):       网络中 → [0x12] [0x34] [0x56] [0x78]

6.2 转换函数速查表

函数 全称 方向 用途
htons() host to network short 主机 → 网络 端口号(16位)
htonl() host to network long 主机 → 网络 IPv4 地址(32位)
ntohs() network to host short 网络 → 主机 端口号(16位)
ntohl() network to host long 网络 → 主机 IPv4 地址(32位)
inet_pton() presentation to network 字符串 → 二进制 点分十进制 → 网络字节序
inet_ntop() network to presentation 二进制 → 字符串 网络字节序 → 点分十进制

记忆口诀h = host,n = network,s = short(16位),l = long(32位),p = presentation(字符串表示),ton = to network


七、两种地址赋值写法详解(重点)

设置 IP 地址时,你会在不同代码中看到两种完全不同的写法。这不是"两种风格",而是新旧两代 API 的更替

7.1 旧写法:inet_addr() + 直接赋值

addr.sin_addr.s_addr = inet_addr("127.0.0.1");

工作原理:

  • inet_addr("127.0.0.1") 将点分十进制字符串转为 uint32_t(已是网络字节序)
  • 返回值直接赋给 s_addr 字段
  • 操作对象是结构体的一个字段uint32_t

致命缺陷:

// inet_addr 失败时返回 INADDR_NONE,即 0xFFFFFFFF
// 而 255.255.255.255 的合法地址恰好也是 0xFFFFFFFF
in_addr_t a = inet_addr("255.255.255.255");  // 返回 0xFFFFFFFF(合法地址)
in_addr_t b = inet_addr("999.999.999.999");  // 返回 0xFFFFFFFF(转换失败)
// 两者返回值相同,你无法区分哪个是成功、哪个是失败!

7.2 新写法:inet_pton()(推荐)

inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

工作原理:

  • inet_pton 将字符串转换后,直接写入 struct in_addr 结构体
  • 第三个参数是 void *,指向目标结构体
  • 操作对象是整个结构体

优势——返回值语义明确:

int ret = inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
// ret == 1  → 转换成功
// ret == 0  → 字符串格式不合法(如 "999.999.999.999")
// ret == -1 → 系统错误(如 af 参数不支持)

7.3 为什么传入的参数不一样?

写法 传入参数 参数类型 原因
s_addr uint32_t(字段) inet_addr 返回一个整数,直接赋值给字段
&addr.sin_addr struct in_addr *(结构体指针) inet_pton 是通用函数,需要知道往哪种结构体写

为什么 inet_pton 要传结构体指针而不是字段地址?

因为 inet_pton 同时支持 IPv4 和 IPv6:

// IPv4:写入 struct in_addr(4字节)
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);

// IPv6:写入 struct in6_addr(16字节)
inet_pton(AF_INET6, "::1", &addr6.sin6_addr);

如果只传 uint32_t *,就无法处理 16 字节的 IPv6 地址。所以第三个参数设计为 void *,由第一个参数 af 决定写入多大的数据。

7.4 在 Linux 上两者等价吗?

struct in_addr {
    uint32_t s_addr;   // 唯一的成员
};

由于 struct in_addr 只有一个成员,所以 &addr.sin_addr&addr.sin_addr.s_addr 的内存地址完全相同。在 Linux 上效果一样,但传 &addr.sin_addr 是语义正确的写法,具有跨平台可移植性。

7.5 绑定地址的两种写法

// ========== 监听所有网卡(0.0.0.0)==========

// 旧写法:INADDR_ANY = 0,htonl(0) 仍然是 0
addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 新写法(推荐):更直观
inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);


// ========== 绑定指定 IP ==========

// 旧写法
addr.sin_addr.s_addr = inet_addr("192.168.1.100");

// 新写法(推荐)
inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);

7.6 完整对比

对比项 旧写法 inet_addr 新写法 inet_pton
操作对象 s_addr 字段(uint32_t struct in_addr 结构体
赋值方式 函数返回值直接赋值 函数内部写入指针指向的内存
错误处理 无法区分 255.255.255.255 和错误 返回值明确区分三种情况
IPv6 支持 ❌ 不支持 ✅ 支持
标准 BSD 扩展 POSIX 标准
推荐程度 仅用于读懂旧代码 新代码必须用这个

八、服务器端完整代码

#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>

using namespace std;

int main() {
    // ================================================================
    // 第1步:创建 TCP 套接字
    // ================================================================
    // AF_INET     → IPv4 协议族
    // SOCK_STREAM → 面向连接的流式套接字(TCP)
    // 0           → 自动选择协议(TCP 对应 IPPROTO_TCP)
    // 返回值:文件描述符 fd,失败返回 -1
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        cerr << "socket() failed" << endl;
        return -1;
    }

    // ================================================================
    // 第2步:设置端口复用(SO_REUSEADDR)
    // ================================================================
    // 作用:服务器重启时,端口可能处于 TIME_WAIT 状态(约60秒)
    //       不加此选项,bind() 会报 "Address already in use"
    // 注意:必须在 bind() 之前调用
    int opt = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        cerr << "setsockopt() failed" << endl;
        close(fd);
        return -1;
    }

    // ================================================================
    // 第3步:绑定地址和端口
    // ================================================================
    sockaddr_in addr{};          // 值初始化,所有字段置 0
    addr.sin_family = AF_INET;   // 地址族:IPv4
    addr.sin_port = htons(8888); // 端口 8888,转网络字节序

    // ---------- 绑定 IP 的两种写法 ----------

    // 【旧写法】监听所有网卡(0.0.0.0)
    // INADDR_ANY = 0,htonl(0) 仍然是 0
    // 含义:接受来自任何网卡的连接
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 【新写法】等价写法(推荐)
    // inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);

    // 【旧写法】绑定指定 IP
    // addr.sin_addr.s_addr = inet_addr("192.168.1.100");

    // 【新写法】绑定指定 IP(推荐)
    // inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);

    // bind() 将套接字与地址绑定
    // 第二个参数需要强转为 sockaddr*(通用地址类型)
    if (bind(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
        cerr << "bind() failed (port 8888 may be in use)" << endl;
        close(fd);
        return -1;
    }

    // ================================================================
    // 第4步:开始监听
    // ================================================================
    // 第二个参数 5 → 等待队列最大长度(半连接 + 全连接)
    // 当多个客户端同时连接时,超出的连接会被拒绝
    if (listen(fd, 5) == -1) {
        cerr << "listen() failed" << endl;
        close(fd);
        return -1;
    }

    cout << "Server listening on port 8888..." << endl;

    // ================================================================
    // 第5步:接受客户端连接
    // ================================================================
    // accept() 是阻塞调用:
    //   - 没有客户端连接时,程序停在这里等待
    //   - 有客户端连接时,返回一个新的文件描述符 cfd
    //
    // 重要:fd 和 cfd 是两个不同的文件描述符
    //   fd  → 监听套接字,继续等待其他客户端
    //   cfd → 通信套接字,专门和当前客户端收发数据
    sockaddr_in client_addr{};
    socklen_t len = sizeof(client_addr);
    int cfd = accept(fd, (sockaddr *)&client_addr, &len);
    if (cfd == -1) {
        cerr << "accept() failed" << endl;
        close(fd);
        return -1;
    }

    // ================================================================
    // 第6步:输出客户端信息
    // ================================================================
    // inet_ntop:将二进制地址(网络字节序)转为点分十进制字符串
    // 参数:
    //   AF_INET               → IPv4
    //   &client_addr.sin_addr → 源地址(struct in_addr 指针)
    //   ip_str                → 目标缓冲区
    //   sizeof(ip_str)        → 缓冲区大小
    char ip_str[INET_ADDRSTRLEN];  // INET_ADDRSTRLEN = 16
    inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));

    // ntohs:将网络字节序的端口号转为主机字节序以便打印
    cout << "Client connected from " << ip_str
         << ":" << ntohs(client_addr.sin_port) << endl;

    // ================================================================
    // 第7步:读取客户端消息
    // ================================================================
    // read() 返回值:
    //   n > 0  → 成功读取 n 个字节
    //   n == 0 → 对端关闭了连接(收到 FIN)
    //   n == -1 → 出错
    char buf[64] = {};
    ssize_t n = read(cfd, buf, sizeof(buf) - 1);  // 留 1 字节给 '\0'
    if (n == -1) {
        cerr << "read() failed" << endl;
    } else if (n == 0) {
        cout << "Client closed connection" << endl;
    } else {
        buf[n] = '\0';  // 手动添加字符串终止符
        cout << "Received: " << buf << endl;
    }

    // ================================================================
    // 第8步:发送回复消息
    // ================================================================
    const char *msg = "Hello from server";
    ssize_t sent = write(cfd, msg, strlen(msg));
    if (sent == -1) {
        cerr << "write() failed" << endl;
    } else {
        cout << "Sent: " << msg << endl;
    }

    // ================================================================
    // 第9步:关闭连接
    // ================================================================
    close(cfd);  // 先关闭与客户端的通信套接字
    close(fd);   // 再关闭监听套接字
    return 0;
}

九、客户端完整代码

#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>

using namespace std;

int main() {
    // ================================================================
    // 第1步:创建 TCP 套接字
    // ================================================================
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        cerr << "socket() failed" << endl;
        return -1;
    }

    // ================================================================
    // 第2步:设置服务器地址
    // ================================================================
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);  // 服务器端口,转网络字节序

    // ---------- 设置目标 IP 的两种写法 ----------

    // 【旧写法】inet_addr() 返回 uint32_t(已是网络字节序),直接赋值
    // 缺陷:无法区分 "255.255.255.255" 和转换失败
    // addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 【新写法】inet_pton() 将结果写入 struct in_addr(推荐)
    // 返回值:1=成功,0=格式错误,-1=系统错误
    if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) {
        cerr << "Invalid address" << endl;
        close(fd);
        return -1;
    }

    // ================================================================
    // 第3步:发起连接
    // ================================================================
    // connect() 是阻塞调用:
    //   - 成功:完成三次握手,返回 0
    //   - 失败:返回 -1(如服务器未启动、端口不对等)
    if (connect(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
        cerr << "connect() failed (server may not be running)" << endl;
        close(fd);
        return -1;
    }

    cout << "Connected to server" << endl;

    // ================================================================
    // 第4步:发送消息
    // ================================================================
    const char *msg = "Hello from client";
    ssize_t sent = write(fd, msg, strlen(msg));
    if (sent == -1) {
        cerr << "write() failed" << endl;
        close(fd);
        return -1;
    }
    cout << "Sent: " << msg << endl;

    // ================================================================
    // 第5步:接收服务器回复
    // ================================================================
    char buf[64] = {};
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    if (n == -1) {
        cerr << "read() failed" << endl;
    } else if (n == 0) {
        cout << "Server closed connection" << endl;
    } else {
        buf[n] = '\0';
        cout << "Received: " << buf << endl;
    }

    // ================================================================
    // 第6步:关闭连接
    // ================================================================
    close(fd);
    return 0;
}

十、编译与运行

# 编译
g++ -o server server.cpp
g++ -o client client.cpp

# 终端1:启动服务器
./server

# 终端2:启动客户端
./client

运行结果:

# 服务器终端
Server listening on port 8888...
Client connected from 127.0.0.1:54321
Received: Hello from client
Sent: Hello from server

# 客户端终端
Connected to server
Sent: Hello from client
Received: Hello from server

十一、两种写法完整对比(终极总结)

11.1 设置 IP 地址

// ==================== 旧写法 ====================
// 函数:inet_addr()
// 返回:uint32_t(网络字节序),失败返回 INADDR_NONE (0xFFFFFFFF)
// 赋值:直接赋给 s_addr 字段
addr.sin_addr.s_addr = inet_addr("192.168.1.100");

// ==================== 新写法 ====================
// 函数:inet_pton()
// 返回:1=成功,0=格式错误,-1=系统错误
// 赋值:函数内部写入 struct in_addr 结构体
inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);

11.2 监听所有网卡

// ==================== 旧写法 ====================
addr.sin_addr.s_addr = htonl(INADDR_ANY);

// ==================== 新写法 ====================
inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);

11.3 打印 IP 地址

// ==================== 旧写法 ====================
// inet_ntoa() 返回静态缓冲区指针,线程不安全,不可重入
char *ip = inet_ntoa(client_addr.sin_addr);
cout << ip << endl;

// ==================== 新写法(推荐) ====================
// inet_ntop() 写入用户提供的缓冲区,线程安全
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
cout << ip_str << endl;

11.4 对比表格

对比项 旧写法 新写法
字符串→二进制 inet_addr() inet_pton()
二进制→字符串 inet_ntoa() inet_ntop()
操作对象 s_addr 字段 struct in_addr 结构体
错误处理 有歧义(255.255.255.255) 返回值明确
IPv6 支持
线程安全 inet_ntoa 不安全 inet_ntop 安全
标准 BSD 扩展 POSIX 标准
推荐 仅用于读懂旧代码 新代码必须使用

十二、关键注意事项

12.1 字节序:什么时候需要转,什么时候不需要?

// ✅ 端口号:必须用 htons()
addr.sin_port = htons(8888);

// ✅ 手动设置 IP(旧写法):必须用 htonl()
addr.sin_addr.s_addr = htonl(INADDR_ANY);

// ✅ inet_addr / inet_pton 返回的已经是网络字节序,不需要再转!
addr.sin_addr.s_addr = inet_addr("127.0.0.1");        // ✅ 已是网络序
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);      // ✅ 已是网络序

// ❌ 重复转换会导致地址错误!
addr.sin_addr.s_addr = htonl(inet_addr("127.0.0.1")); // ❌ 转了两次!

12.2 read() 返回值必须检查

ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
    buf[n] = '\0';       // 手动加终止符
    // 处理数据...
} else if (n == 0) {
    // 对端关闭连接,应该 close(fd) 并退出
} else {
    // 出错,检查 errno
}

12.3 accept() 的两个 fd 不要混淆

int fd  = socket(...);     // 监听套接字:只负责等待新连接
int cfd = accept(fd, ...); // 通信套接字:负责和客户端收发数据

// fd 继续 listen,cfd 用来 read/write
// 服务器要支持多客户端,就需要对每个 cfd 创建线程/进程

12.4 SO_REUSEADDR 必须在 bind() 之前

int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  // 先设置
bind(fd, ...);  // 后绑定
// 顺序反了无效!

十三、完整调用流程图

服务器端:
  socket() ──→ setsockopt() ──→ bind() ──→ listen() ──→ accept()
                                                            │
                                                    ┌───────┘
                                                    ▼
                                              read() / write()
                                                    │
                                                    ▼
                                              close(cfd)
                                              close(fd)

客户端:
  socket() ──→ connect() ──→ write() ──→ read() ──→ close(fd)

十四、后续学习方向

方向 内容
并发服务器 fork() 多进程 / pthread 多线程
IO 多路复用 select()poll()epoll()
非阻塞 IO fcntl() 设置 O_NONBLOCK
UDP 通信 SOCK_DGRAM + sendto() / recvfrom()
高级特性 TCP 粘包处理、心跳机制、优雅关闭

一句话总结:Socket 编程的核心就是 7 个函数(socketbindlistenacceptconnectread/writeclose),地址设置认准 inet_pton + &addr.sin_addr 这一种写法就够了。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐