作者: andylin02
学习章节: 第16章 网络IPC:套接字
关键词:套接字;socket;TCP;UDP;网络字节序;三次握手;四次挥手;非阻塞I/O


一、引言

1.1 从进程间通信到网络IPC

在第15章,我们学习了经典UNIX进程间通信(IPC)机制:管道、FIFO、消息队列、信号量和共享内存,这些机制都仅允许在同一台计算机上运行的进程之间通信

然而,网络时代的应用程序往往需要与运行在其他计算机上的进程交互。本章介绍的网络IPC:套接字(Socket) 正是为了解决这一问题而设计的。

1.2 套接字的设计目标

套接字接口的目标非常优雅:同样的接口既可以用于计算机间通信,又可以用于计算机内通信。通过这一接口,目标进程运行的位置对调用者来说是透明的——它们可以在同一台计算机上,也可以在不同的计算机上。

💡 设计哲学:套接字接口的设计体现了UNIX“一切皆文件”的理念——套接字描述符在UNIX系统中被当作一种文件描述符来处理,可以使用readwriteclose等熟悉的文件I/O函数进行网络通信。

二、套接字描述符

2.1 socket函数:创建套接字

套接字是通信端点的抽象。要创建一个套接字,需要调用socket函数:

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
// 返回值:若成功,返回套接字描述符;若出错,返回-1

参数详解

参数 说明 常见取值
domain 地址族,确定通信的特性 AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(本地通信,AF_UNIX别名)、AF_ROUTE(路由套接字)、AF_KEY(密钥管理)
type 套接字类型,确定通信特征 SOCK_STREAM(有序、可靠、双向、面向连接的字节流)、SOCK_DGRAM(固定长度、无连接、不可靠的数据报)、SOCK_RAW(IP协议数据报接口)、SOCK_SEQPACKET(有序的、可靠的双向面向连接的数据报)
protocol 协议类型 通常为0(让系统选择默认协议)。AF_INETSOCK_STREAM默认TCP,SOCK_DGRAM默认UDP

📖 拓展知识AF_(Address Family)地址族和PF_(Protocol Family)协议族在历史上曾有不同的语义,但现在大多数系统上它们是等价的。PF_LOCALPF_UNIX的别名,后者已废弃。

2.2 close与shutdown:关闭套接字

当不再需要套接字时,可以使用close函数关闭对套接字的访问。然而,套接字通信是双向的,有时候我们可能只想关闭一个方向。这时就需要用到shutdown函数:

#include <sys/socket.h>

int shutdown(int sockfd, int how);
// 返回值:若成功,返回0;若出错,返回-1

how参数的取值

how 含义 说明
SHUT_RD 关闭读端 无法从套接字读取数据
SHUT_WR 关闭写端 无法使用套接字发送数据
SHUT_RDWR 关闭读写 既无法读取数据,又无法发送数据

为什么需要shutdown而不只用close

close只有在最后一个引用该套接字的文件描述符被关闭时才会真正释放网络端点。而shutdown允许使套接字处于不活动状态,与引用它的文件描述符数目无关,并且可以很方便地关闭双向传输中的某一个方向。

三、寻址:找到通信目标

3.1 进程标识的组成

要标识一个目标通信进程,需要两个部分的信息:

  1. 计算机的网络地址:用于标识网络上我们想与之通信的计算机
  2. 端口号:用于标识该计算机上特定的进程(服务)

📖 端口号:TCP/IP协议中,端口号是传输层对网络服务的封装。IP地址:端口号的组合(即socket)唯一标识了网络中的一个通信端点。

3.2 字节序:大端 vs 小端

字节序是一个处理器架构的特性,用于指示像整数这样的大数据类型内部的字节如何排序:

字节序 特点 代表架构
大端(Big-Endian) 最大字节地址出现在数据最低有效字节(LSB)上 PowerPC、SPARC、网络协议
小端(Little-Endian) 最小字节地址出现在数据最低有效字节上 x86、x86_64

网络字节序:TCP/IP协议栈使用大端字节序。因此,不同字节序架构的计算机之间通信时,需要进行字节序转换。

3.3 字节序转换函数

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32);   // 32位主机字节序 → 网络字节序
uint16_t htons(uint16_t hostint16);   // 16位主机字节序 → 网络字节序
uint32_t ntohl(uint32_t netint32);    // 32位网络字节序 → 主机字节序
uint16_t ntohs(uint16_t netint16);    // 16位网络字节序 → 主机字节序

💡 命名规律h代表host,n代表network,l代表long(32位),s代表short(16位)。

3.4 套接字地址结构

3.4.1 通用套接字地址结构 sockaddr

在Linux中,套接字地址结构定义如下:

struct sockaddr {
    sa_family_t sa_family;   /* 地址族 */
    char sa_data[14];        /* 可变长度的地址数据 */
};
3.4.2 IPv4套接字地址结构 sockaddr_in

在实际编程中,我们更常用的是针对IPv4的sockaddr_in结构:

#include <netinet/in.h>

struct in_addr {
    in_addr_t s_addr;        /* IPv4地址,网络字节序 */
};

struct sockaddr_in {
    sa_family_t sin_family;  /* 地址族:AF_INET */
    in_port_t sin_port;      /* 端口号,网络字节序 */
    struct in_addr sin_addr; /* IPv4地址,网络字节序 */
    unsigned char sin_zero[8]; /* 填充字节,使大小与sockaddr一致 */
};

3.5 地址转换函数:inet_ptoninet_ntop

现代网络编程推荐使用inet_pton(Presentation to Numeric)和inet_ntop(Numeric to Presentation)进行地址转换:

#include <arpa/inet.h>

/* 将点分十进制字符串 → 二进制网络字节序地址 */
int inet_pton(int af, const char *src, void *dst);
// af: AF_INET 或 AF_INET6
// 返回值:成功返回1,格式无效返回0,出错返回-1

/* 将二进制网络字节序地址 → 点分十进制字符串 */
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// 返回值:成功返回dst,出错返回NULL

3.6 bind函数:将地址绑定到套接字

服务器需要将一个众所周知的地址绑定到套接字上,以便客户端能够找到它。这通过bind函数完成:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回值:成功返回0,出错返回-1

四、建立连接

4.1 connect:客户端发起连接

客户端使用connect函数建立与服务器的连接:

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回值:成功返回0,出错返回-1

4.2 listen:服务器宣告可接受连接

服务器调用listen来宣告可以接受连接请求:

#include <sys/socket.h>

int listen(int sockfd, int backlog);
// 返回值:成功返回0,出错返回-1

backlog参数用于指定未决连接队列的最大长度,即已经完成三次握手但尚未被accept接受的连接数上限。

4.3 accept:接收连接请求

使用accept函数获得连接请求并建立连接:

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
// 返回值:若成功,返回代表已连接套接字的文件描述符;若出错,返回-1

📖 重要理解accept所返回的文件描述符是一个新的套接字描述符,该描述符连接到调用connect的客户端。原始的监听套接字(sockfd)仍然保持打开状态,继续接收后续的连接请求。

五、数据传输

套接字提供了多个函数用于数据传输,既可以像文件I/O一样使用read/write,也可以使用专门设计的函数以获得更多控制能力。

5.1 发送数据

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
// 返回值:若成功,返回发送的字节数;若出错,返回-1

send函数与write类似,但可以指定flags标志来改变处理传输数据的方式。

ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
               const struct sockaddr *destaddr, socklen_t destlen);

sendtosend类似,但区别在于允许在无连接的套接字(如UDP)上指定目标地址。

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

sendmsg支持使用msghdr结构指定多重缓冲区传输数据,与writev类似。

5.2 接收数据

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
// 返回值:若成功,返回数据的字节数;若无可用数据,返回0;若出错,返回-1
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
                 struct sockaddr *restrict addr, socklen_t *restrict addrlen);
// 返回值:成功返回接收字节数,出错返回-1

recvfrom可以得到数据发送者的源地址,常用于UDP通信。

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
// 返回值:成功返回接收字节数,出错返回-1

recvmsg可以将接收到的数据送入多个缓冲区,类似于readv

六、TCP编程完整示例

6.1 TCP服务器端完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024
#define BACKLOG 5

int main(void)
{
    int listen_fd, conn_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    char buffer[BUFFER_SIZE];
    ssize_t n;
    
    /* 1. 创建套接字 */
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket error");
        exit(1);
    }
    
    /* 设置SO_REUSEADDR选项,避免端口被占用时bind失败 */
    int opt = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt error");
        exit(1);
    }
    
    /* 2. 绑定地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  /* 监听所有网络接口 */
    server_addr.sin_port = htons(PORT);
    
    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind error");
        exit(1);
    }
    
    /* 3. 监听 */
    if (listen(listen_fd, BACKLOG) < 0) {
        perror("listen error");
        exit(1);
    }
    
    printf("TCP服务器启动,监听端口 %d...\n", PORT);
    
    /* 4. 接受客户端连接 */
    client_len = sizeof(client_addr);
    conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
    if (conn_fd < 0) {
        perror("accept error");
        exit(1);
    }
    
    printf("客户端已连接: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), 
           ntohs(client_addr.sin_port));
    
    /* 5. 收发数据 */
    while ((n = recv(conn_fd, buffer, BUFFER_SIZE - 1, 0)) > 0) {
        buffer[n] = '\0';
        printf("收到消息: %s", buffer);
        
        /* 回显消息 */
        if (send(conn_fd, buffer, n, 0) != n) {
            perror("send error");
            break;
        }
    }
    
    if (n < 0) {
        perror("recv error");
    } else if (n == 0) {
        printf("客户端已断开连接\n");
    }
    
    /* 6. 关闭连接 */
    close(conn_fd);
    close(listen_fd);
    
    return 0;
}

6.2 TCP客户端完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_PORT 8888
#define BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    ssize_t n;
    
    if (argc != 2) {
        fprintf(stderr, "用法: %s <服务器IP>\n", argv[0]);
        exit(1);
    }
    
    /* 1. 创建套接字 */
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd < 0) {
        perror("socket error");
        exit(1);
    }
    
    /* 2. 设置服务器地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
        perror("inet_pton error");
        exit(1);
    }
    
    /* 3. 连接服务器 */
    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect error");
        exit(1);
    }
    
    printf("已连接到服务器 %s:%d\n", argv[1], SERVER_PORT);
    
    /* 4. 发送并接收数据 */
    while (1) {
        printf("请输入消息 (输入quit退出): ");
        if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) {
            break;
        }
        
        /* 发送数据 */
        if (send(sock_fd, buffer, strlen(buffer), 0) != (ssize_t)strlen(buffer)) {
            perror("send error");
            break;
        }
        
        /* 如果输入quit,退出循环 */
        if (strncmp(buffer, "quit", 4) == 0) {
            break;
        }
        
        /* 接收服务器回显 */
        n = recv(sock_fd, buffer, BUFFER_SIZE - 1, 0);
        if (n > 0) {
            buffer[n] = '\0';
            printf("服务器回显: %s", buffer);
        } else if (n == 0) {
            printf("服务器已关闭连接\n");
            break;
        } else {
            perror("recv error");
            break;
        }
    }
    
    /* 5. 关闭套接字 */
    close(sock_fd);
    
    return 0;
}

6.3 TCP三次握手与四次挥手流程图

服务器 客户端 服务器 客户端 三次握手建立连接 连接已建立,开始数据传输 数据传输阶段 四次挥手断开连接 服务器关闭读通道 客户端关闭写通道 1. SYN (seq=x) 2. SYN + ACK (seq=y, ack=x+1) 3. ACK (ack=y+1) 数据 (data) ACK 数据 (data) ACK 1. FIN (seq=u) 2. ACK (ack=u+1) 3. FIN (seq=v) 4. ACK (ack=v+1)

七、UDP编程完整示例

7.1 UDP服务器端完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main(void)
{
    int sock_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    char buffer[BUFFER_SIZE];
    ssize_t n;
    
    /* 1. 创建UDP套接字 */
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("socket error");
        exit(1);
    }
    
    /* 2. 绑定地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);
    
    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind error");
        exit(1);
    }
    
    printf("UDP服务器启动,监听端口 %d...\n", PORT);
    
    /* 3. 接收并处理数据 */
    client_len = sizeof(client_addr);
    while (1) {
        n = recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0,
                     (struct sockaddr *)&client_addr, &client_len);
        if (n < 0) {
            perror("recvfrom error");
            continue;
        }
        
        buffer[n] = '\0';
        printf("收到来自 %s:%d 的消息: %s\n",
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port),
               buffer);
        
        /* 回显消息 */
        if (sendto(sock_fd, buffer, n, 0,
                   (struct sockaddr *)&client_addr, client_len) != n) {
            perror("sendto error");
        }
    }
    
    close(sock_fd);
    return 0;
}

7.2 UDP客户端完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_PORT 8888
#define BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    socklen_t addr_len;
    ssize_t n;
    
    if (argc != 2) {
        fprintf(stderr, "用法: %s <服务器IP>\n", argv[0]);
        exit(1);
    }
    
    /* 1. 创建UDP套接字 */
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("socket error");
        exit(1);
    }
    
    /* 2. 设置服务器地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
        perror("inet_pton error");
        exit(1);
    }
    
    printf("UDP客户端启动,目标服务器 %s:%d\n", argv[1], SERVER_PORT);
    
    addr_len = sizeof(server_addr);
    
    while (1) {
        printf("请输入消息 (输入quit退出): ");
        if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) {
            break;
        }
        
        /* 发送数据 */
        if (sendto(sock_fd, buffer, strlen(buffer), 0,
                   (struct sockaddr *)&server_addr, addr_len) != (ssize_t)strlen(buffer)) {
            perror("sendto error");
            break;
        }
        
        /* 如果输入quit,退出循环 */
        if (strncmp(buffer, "quit", 4) == 0) {
            break;
        }
        
        /* 接收服务器回显 */
        n = recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0, NULL, NULL);
        if (n > 0) {
            buffer[n] = '\0';
            printf("服务器回显: %s", buffer);
        } else {
            perror("recvfrom error");
            break;
        }
    }
    
    close(sock_fd);
    return 0;
}

八、TCP与UDP的对比

8.1 核心差异

特性 TCP UDP
连接特性 面向连接(通信前必须建立逻辑连接) 无连接(无需预先建立连接)
可靠性 可靠传输,保证数据不丢失、不重复、按序到达 尽最大努力交付,不保证可靠交付
通信模式 基于字节流 基于数据报
三次握手/四次挥手 需要 不需要
流量控制
拥塞控制
报头开销 至少20字节 8字节
传输速度 较慢 较快
资源消耗

8.2 编程流程对比

步骤 TCP服务器 TCP客户端 UDP服务器 UDP客户端
1 socket() socket() socket() socket()
2 bind() bind()
3 listen()
4 accept() connect() recvfrom() / sendto() sendto() / recvfrom()
5 recv() / send() send() / recv()
6 close() close() close() close()

8.3 应用场景选择

TCP适用场景

  • 需要数据可靠传输的场景(如文件传输、电子邮件、网页浏览)
  • 数据顺序有严格要求的场景
  • 需要长连接交互的应用

UDP适用场景

  • 对实时性要求高、可以容忍少量丢包的场景(如视频通话、在线游戏)
  • 广播/多播通信
  • DNS查询等短事务通信

九、套接字选项

套接字机制提供两个套接字选项接口来控制套接字行为:

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);
// 返回值:成功返回0,出错返回-1

选项的分类

  1. 通用选项:工作在所有套接字类型上(如SO_REUSEADDRSO_KEEPALIVESO_RCVBUFSO_SNDBUF
  2. 在套接字层次管理的选项:依赖于下层协议的支持
  3. 特定于某协议的选项:为每个协议所独有

十、非阻塞I/O与异步I/O

默认情况下,recv函数在没有数据可用时会阻塞等待。同样地,当套接字输出队列没有足够空间来发送消息时,send函数会阻塞。在套接字非阻塞模式下,行为会改变——这些函数不会阻塞,而是失败并设置errnoEAGAINEWOULDBLOCK

设置非阻塞模式的方法

int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

💡 学习建议:第14章“高级I/O”中详细介绍了selectpollepoll等I/O多路转接技术,这些技术与套接字非阻塞I/O结合,是实现高性能网络服务器的基础。

十一、标准I/O与套接字的配合使用

11.1 为什么需要特殊处理?

套接字作为全双工通信端点,使用标准I/O库时需要特别注意缓冲问题:

  • 标准I/O流可以是全双工的(以r+类型打开)
  • 但在这样的流上,必须在输出函数后插入fflush调用才能接着调用输入函数
  • 标准I/O的缓冲机制可能导致数据延迟发送(完全缓冲时,数据会等缓冲区满后才发送)

11.2 解决方案:使用fdopen分离读写流

推荐的解决方案是为一个给定套接字打开两个标准I/O流:一个用于读,一个用于写:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* 绑定、连接等操作... */

/* 使用dup复制文件描述符,避免双关闭问题 */
FILE *readfp = fdopen(sockfd, "r");
FILE *writefp = fdopen(dup(sockfd), "w");

/* 分别使用fgets/fputs进行读写操作 */

/* 关闭时分别关闭两个流 */
fclose(readfp);
fclose(writefp);

为什么使用dup复制文件描述符?

如果在同一个文件描述符上调用两次fdopen并分别关闭,会导致“双关闭”问题。在多线程程序中,这可能引起严重的数据损坏。使用dup复制描述符可以避免这一问题。

十二、架构图与流程图

12.1 TCP通信全流程图

客户端

服务器端

三次握手

数据传输

四次挥手

socket() 创建监听套接字

bind() 绑定地址端口

listen() 设置监听队列

accept() 阻塞等待连接

recv()/send() 读写数据

close() 关闭连接套接字

socket() 创建套接字

connect() 连接服务器

send()/recv() 读写数据

close() 关闭套接字

12.2 UDP通信全流程图

客户端

服务器端

数据报

数据报

socket() 创建UDP套接字

bind() 绑定地址端口

recvfrom() 阻塞等待数据

sendto() 发送响应

socket() 创建UDP套接字

sendto() 发送数据到服务器

recvfrom() 接收服务器响应

close() 关闭套接字

12.3 套接字类型选择决策树

需要进程间通信

进程在
同一台计算机上?

使用第15章IPC
管道/FIFO/消息队列/共享内存

需要可靠传输?

选择TCP
SOCK_STREAM

需要实时性
可容忍丢包?

选择UDP
SOCK_DGRAM

考虑SCTP
SOCK_SEQPACKET

三次握手、流量控制、拥塞控制

无连接、低延迟、广播/多播

十三、常见问题与注意事项

问题 解决方法
bind时提示"Address already in use" 设置SO_REUSEADDR套接字选项
connectsend时产生SIGPIPE信号 忽略SIGPIPE信号或使用MSG_NOSIGNAL标志
标准I/O缓冲导致数据延迟发送 使用setbuf设置行缓冲或无缓冲,或在发送前调用fflush
多线程程序中使用fdopen后关闭 使用dup复制描述符分别打开读/写流,避免双关闭
recv/read返回值小于请求大小 必须循环接收直到获得期望的数据量
字节序错误导致端口/IP解析错误 使用htonl/htons/ntohl/ntohs进行转换
UDP丢包问题 应用层实现ACK确认和重传机制

💡 关键要点

  1. 套接字是通信端点的抽象,套接字描述符在UNIX中被当作文件描述符处理
  2. TCP是面向连接的可靠协议,适用于需要数据完整性的场景
  3. UDP是无连接的轻量协议,适用于对实时性要求高、可容忍丢包的场景
  4. 网络字节序是大端,与x86架构的小端字节序不同,必须进行转换
  5. shutdownclose的区别shutdown可单独关闭一个方向,且与引用计数无关
  6. accept返回的是新的套接字描述符,原始监听套接字继续接收后续连接

十四、总结

本章系统介绍了UNIX网络IPC——套接字编程的核心知识,主要包括:

  1. 套接字描述符:通过socket函数创建,套接字描述符可以像文件描述符一样使用
  2. 寻址:使用网络地址(IP)和端口号标识目标进程;通过字节序转换函数处理主机与网络字节序的差异
  3. 建立连接:TCP使用connect(客户端)和listen/accept(服务器)建立连接;UDP无需建立连接
  4. 数据传输send/recv用于TCP,sendto/recvfrom用于UDP,sendmsg/recvmsg支持多重缓冲区传输
  5. 套接字选项:通过setsockopt/getsockopt控制套接字行为
  6. TCP与UDP的对比:TCP面向连接、可靠但开销大;UDP无连接、轻量但不保证可靠
  7. 非阻塞I/O:通过设置O_NONBLOCK标志实现非阻塞操作
  8. 标准I/O与套接字的配合:使用fdopendup实现安全的I/O分离

套接字接口是现代网络编程的基石。无论是Web服务器、数据库服务还是即时通讯应用,其底层都离不开套接字。理解本章的内容,是迈向高性能网络编程的重要一步。

十五、下篇预告

第17章《高级进程间通信》 将介绍:

  • UNIX域套接字:本机进程间通信的高效方式
  • 命名空间与高级Socket特性
  • 多种IPC机制的综合运用与选择策略
  • 进程间通信的性能比较与优化

本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

Logo

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

更多推荐