当前位置: 首页 > news >正文

accept4系统调用及示例

1. 函数介绍

在网络编程中,服务器程序通常需要监听某个端口,等待客户端的连接请求。当一个客户端尝试连接到服务器时,内核会将这个连接请求放入一个等待队列中。
服务器程序需要一种方法从这个队列中取出(“接受”)一个连接请求,并为这个连接创建一个新的套接字(socket),通过这个新套接字与客户端进行数据通信。

accept 系统调用就是用来完成这个“接受连接”的任务的。它会阻塞(等待)直到队列中有新的连接请求,然后返回一个新的、已连接的套接字文件描述符。

accept4accept 的一个扩展版本。它在功能上与 accept 几乎相同,但增加了一个非常实用的特性:允许你在接受连接的同时,为新创建的套接字文件描述符设置一些标志(flags)。

最常见的用途是设置 SOCK_CLOEXEC 标志,这可以自动防止新套接字在执行 exec() 系列函数时被意外地传递给新程序,从而提高了程序的安全性和健壮性。
简单来说,accept4 就是 accept 的“增强版”,它让你在接到电话(连接)的同时,可以立刻给电话线(套接字)加上一些安全或便利的设置。

2. 函数原型

#define _GNU_SOURCE // 必须定义这个宏才能使用 accept4
#include <sys/socket.h> // 包含 accept4 函数声明// accept4 是 Linux 特有的系统调用
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

注意accept4 是 Linux 特有的。在可移植的 POSIX 代码中,通常使用标准的 accept,然后手动调用 fcntl 来设置标志。

3. 功能

从监听套接字 sockfd已完成连接队列(completed connection queue)中取出第一个连接请求,为这个连接创建一个新的、已连接的套接字,并根据 flags 参数设置该新套接字的属性。

4. 参数

  • sockfd:
    • int 类型。
    • 一个监听套接字的文件描述符。这个套接字必须已经通过 bind() 绑定了本地地址和端口,并通过 listen() 开始监听连接请求。
  • addr:
    • struct sockaddr * 类型。
    • 一个指向 sockaddr 结构体(或其特定协议的变体,如 sockaddr_in for IPv4)的指针。当 accept4 成功返回时,这个结构体将被填充为连接到服务器的客户端的地址信息(IP 地址和端口号)。
    • 如果你不关心客户端的地址信息,可以传 NULL
  • addrlen:
    • socklen_t * 类型。
    • 这是一个输入/输出参数。
    • 输入时:它应该指向一个 socklen_t 变量,该变量的值是 addr 指向的缓冲区的大小
    • 输出时accept4 成功返回后,这个 socklen_t 变量的值将被修改为实际存储在 addr 中的地址结构的大小
    • 如果 addrNULLaddrlen 也必须是 NULL
  • flags:
    • int 类型。
    • 一个位掩码,用于设置新创建的已连接套接字的属性。可以是以下值的按位或 (|) 组合:
      • SOCK_NONBLOCK: 为新套接字设置非阻塞模式。这样,后续在这个新套接字上的 I/O 操作(如 read, write)如果无法立即完成,不会阻塞,而是返回错误 EAGAINEWOULDBLOCK
      • SOCK_CLOEXEC: 为新套接字设置执行时关闭(Close-on-Exec)标志 (FD_CLOEXEC)。这确保了当程序调用 exec() 系列函数执行新程序时,这个新套接字会被自动关闭,防止它被新程序意外继承。这是一个重要的安全和资源管理特性。

5. 返回值

  • 成功: 返回一个新的、非负的文件描述符,它代表了与客户端通信的已连接套接字。服务器应该使用这个新的文件描述符与客户端进行 read/write 等操作。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

accept4 可能返回的错误码与 accept 基本相同:

  • EAGAINEWOULDBLOCK: (对于非阻塞套接字) 监听队列中当前没有已完成的连接。
  • EBADF: sockfd 不是有效的文件描述符。
  • ECONNABORTED: 连接已被客户端中止。
  • EFAULT: addr 参数指向了进程无法访问的内存地址。
  • EINTR: 系统调用被信号中断。
  • EINVAL: 套接字没有处于监听状态,或者 flags 参数包含无效标志。
  • EMFILE: 进程已打开的文件描述符数量达到上限 (RLIMIT_NOFILE)。
  • ENFILE: 系统已打开的文件描述符数量达到上限。
  • ENOMEM: 内核内存不足。
  • ENOBUFS: 网络子系统内存不足。
  • ENOTSOCK: sockfd 不是一个套接字。
  • EOPNOTSUPP: 套接字类型不支持 accept 操作(例如,不是 SOCK_STREAM)。
  • EPERM: 防火墙规则禁止连接。

7. 相似函数或关联函数

  • accept: 标准的接受连接函数。功能与 accept4 相同,但不支持 flags 参数。通常在 accept 返回后,需要再调用 fcntl 来设置 O_NONBLOCKFD_CLOEXEC
    // 使用 accept + fcntl 的等效操作
    new_fd = accept(sockfd, addr, addrlen);
    if (new_fd != -1) {// 设置非阻塞和 close-on-execint flags = fcntl(new_fd, F_GETFL, 0);fcntl(new_fd, F_SETFL, flags | O_NONBLOCK);flags = fcntl(new_fd, F_GETFD, 0);fcntl(new_fd, F_SETFD, flags | FD_CLOEXEC);
    }
    
  • listen: 将套接字置于监听状态,使其能够接收连接请求。
  • bind: 将套接字与本地地址和端口绑定。
  • socket: 创建一个套接字。
  • read / write: 通过已连接的套接字与客户端通信。
  • close: 关闭套接字。
  • fcntl: 用于获取和设置文件描述符标志,包括 O_NONBLOCKFD_CLOEXEC

8. 示例代码

下面的示例演示了一个简单的 TCP 服务器,它使用 accept4 来接受客户端连接,并利用 SOCK_CLOEXECSOCK_NONBLOCK 标志。

#define _GNU_SOURCE // 必须定义以使用 accept4
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h> // 包含 O_NONBLOCK 等#define PORT 8080
#define BACKLOG 10 // 监听队列的最大长度void handle_client(int client_fd, const struct sockaddr_in *client_addr) {char buffer[1024];ssize_t bytes_read;printf("Handling client %s:%d on fd %d\n",inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);// 读取客户端发送的数据while ((bytes_read = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {buffer[bytes_read] = '\0';printf("Received from client: %s", buffer);// 将数据回显给客户端if (write(client_fd, buffer, bytes_read) != bytes_read) {perror("write");break;}}if (bytes_read == 0) {printf("Client disconnected.\n");} else if (bytes_read == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {printf("No data available to read (non-blocking).\n");} else {perror("read");}}close(client_fd); // 关闭与该客户端的连接printf("Closed connection to client.\n");
}int main() {int server_fd, client_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);printf("--- Simple TCP Server using accept4 ---\n");// 1. 创建 socket// AF_INET: IPv4// SOCK_STREAM: TCP// 0: 使用默认协议 (TCP)server_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);if (server_fd == -1) {perror("socket");exit(EXIT_FAILURE);}printf("Created server socket: %d\n", server_fd);// 2. 准备服务器地址结构memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口server_addr.sin_port = htons(PORT);       // 绑定到指定端口 (网络字节序)// 3. 绑定 socket 到地址和端口if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind");close(server_fd);exit(EXIT_FAILURE);}printf("Bound server socket to port %d\n", PORT);// 4. 开始监听连接if (listen(server_fd, BACKLOG) == -1) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}printf("Listening for connections...\n");printf("Server is running. Connect to it using e.g., 'telnet 127.0.0.1 %d' or 'nc 127.0.0.1 %d'\n", PORT, PORT);printf("Press Ctrl+C to stop the server.\n");// 5. 主循环:接受连接while (1) {// 6. 使用 accept4 接受连接// SOCK_CLOEXEC: 自动设置 close-on-exec 标志// SOCK_NONBLOCK: 自动设置非阻塞模式client_fd = accept4(server_fd, (struct sockaddr *)&client_addr, &client_len, SOCK_CLOEXEC | SOCK_NONBLOCK);if (client_fd == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 对于阻塞的监听套接字,这不太可能发生// 但对于非阻塞的监听套接字,队列可能为空printf("No pending connections (EAGAIN/EWOULDBLOCK).\n");usleep(100000); // 等待 0.1 秒再试continue;} else if (errno == EINTR) {// 被信号中断,通常继续循环printf("accept4 interrupted by signal, continuing...\n");continue;} else {perror("accept4");// 对于其他严重错误,可以选择关闭服务器// close(server_fd);// exit(EXIT_FAILURE);continue; // 或者简单地继续尝试}}printf("\nAccepted new connection. Client fd: %d\n", client_fd);printf("Client address: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 7. 处理客户端 (在这个简单示例中,我们直接处理)// 注意:在实际的高性能服务器中,这里通常会 fork() 或使用线程/事件循环handle_client(client_fd, &client_addr);}// 8. 关闭服务器套接字 (实际上不会执行到这里)close(server_fd);printf("Server socket closed.\n");return 0;
}

9. 编译和运行

# 假设代码保存在 tcp_server_accept4.c 中
# 必须定义 _GNU_SOURCE
gcc -D_GNU_SOURCE -o tcp_server_accept4 tcp_server_accept4.c# 在一个终端运行服务器
./tcp_server_accept4# 在另一个终端使用 telnet 或 nc 连接服务器
telnet 127.0.0.1 8080
# 或者
nc 127.0.0.1 8080# 在 telnet/nc 窗口中输入一些文字,按回车,会看到服务器回显
# 输入 Ctrl+] 然后 quit (telnet) 或 Ctrl+C (nc) 来断开连接

10. 预期输出

服务器终端:

--- Simple TCP Server using accept4 ---
Created server socket: 3
Bound server socket to port 8080
Listening for connections...
Server is running. Connect to it using e.g., 'telnet 127.0.0.1 8080' or 'nc 127.0.0.1 8080'
Press Ctrl+C to stop the server.Accepted new connection. Client fd: 4
Client address: 127.0.0.1:54321
Handling client 127.0.0.1:54321 on fd 4
Received from client: Hello, Server!Client disconnected.
Closed connection to client.

客户端终端 (telnetnc):

Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello, Server!
Hello, Server! # 服务器回显
^]
telnet> quit
Connection closed.

11. 总结

accept4 是一个在 Linux 上非常有用的系统调用,特别适合于需要高性能和安全性的网络服务器程序。

  • 核心优势:它将“接受连接”和“设置套接字属性”这两个操作原子化地结合在一起,避免了使用 accept + fcntl 时可能存在的竞态条件(即在 acceptfcntl 之间,新套接字可能被意外使用)。
  • SOCK_CLOEXEC:自动设置 close-on-exec 标志,防止套接字被 exec() 继承,提高安全性。
  • SOCK_NONBLOCK:自动设置非阻塞模式,使得在新套接字上的 I/O 操作不会阻塞。
  • accept 的关系accept4(sockfd, addr, addrlen, 0) 在功能上等同于 accept(sockfd, addr, addrlen)
  • 可移植性accept4 是 Linux 特有的。如果需要编写可移植的代码,应使用 accept 并手动调用 fcntl

对于 Linux 系统编程新手来说,掌握 accept4 及其标志的使用,是编写健壮、高效网络服务的重要一步。

http://www.lryc.cn/news/608886.html

相关文章:

  • ABP VNext + CloudEvents:事件驱动微服务互操作性
  • 数据治理:DQC(Data Quality Center,数据质量中心)概述
  • [每周一更]-(第153期):**PDF终极防护指南:命令行全栈加密+一键权限锁死实战(附脚本模板)**
  • Docker--解决x509: certificate signed by unknown authority
  • 医院课题管理全动态流程 (AI-Enhanced, Data-Driven Research Lifecycle)
  • JAVA中的String类方法介绍
  • 基于transformer的目标检测——匈牙利匹配算法
  • 【Excel】利用函数和Power Query进行数据分析
  • 基于Matlab的深度学习智能行人检测与统计系统
  • Java企业级应用性能优化实战
  • 2025年人工智能十大趋势 - 基础模型的跃迁
  • 达梦数据库联机备份和脱机备份的区别
  • C++ 入门基础(3)
  • 自私挖矿攻击
  • C++引用:高效安全的别名机制详解
  • RPG增容3:尝试使用MVC结构搭建玩家升级UI(一)
  • Claude Code入门学习笔记(四)--Claude Code的使用
  • [硬件电路-150]:数字电路 - 数字电路与模拟电路的异同
  • 志邦家居PMO负责人李蓉蓉受邀为PMO大会主持人
  • Rust:开发 DLL 动态链接库时如何处理 C 字符串
  • 2025-0803学习记录21——地表分类产品的精度验证
  • 多向量检索:lanchain,dashvector,milvus,vestorsearch,MUVERA
  • gemini-cli +llms
  • 嵌入式硬件篇---Openmv
  • 问题集000
  • 对接古老系统的架构实践:封装混乱,走向有序
  • ⭐CVPR2025 FreeUV:无真值 3D 人脸纹理重建框架
  • 专网内网IP攻击防御:从应急响应到架构加固
  • 第十章:如何真正使用Java操作redis
  • 语义分割--deeplabV3+