系统调用 accept

简单来说:accept 的主要工作就是从全连接队列中获取一个连接,创建通信 socket,供用户使用。

在完成三次握手后,连接被放到全连接队列(也称 Accept Queue)里面,server 端 TCP 会创建一个 sock 结构来与 client 端的 scoket 进行一对一的数据传递。

但这个 sock 存在于内核中,server 端用户进程还无法使用。进程要想使用这个新的连接,必须调用 accept 系统调用生成一个与sock 关联的 socket,然后进程通过对这个 socket 进行 recv、send 等操作来实现与 client 端的数据传递。

也即 accept 返回一个新的套接字描述符,用于与客户端进行通信。通过 accept,服务器可以处理多个客户端的连接请求,实现多用户并发访问。

accept 阻塞是因为没有一个完成了三次握手的连接。accept 就是从全连接队列(也称 Accept Queue)里取节点,每个节点都是已经完成了三次握手的。

以下是 accept 系统调用的原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
sockfd:套接字的文件描述符,socket()系统调用返回的文件描述符fd(通常是服务器的监听套接字)。
addr:指向 struct sockaddr 类型的指针,用于存储连接的对端地址信息。可以为 NULL,表示不关心对端地址。
addrlen:是一个指向 socklen_t 类型的指针,用于指定 addr 缓冲区的大小。在调用 accept 之前,addrlen 应该被初始化为 struct sockaddr 缓冲区的大小,函数返回时,addrlen 会被设置为实际地址结构的长度。

返回值:
如果连接成功,返回一个新的文件描述符,这个文件描述符用于与客户端通信。
如果失败,返回 -1,并设置 `errno` 表示错误原因。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
// 创建套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

// 绑定地址
struct sockaddr_in server_addr;
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(8080);

if (bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(listenfd);
exit(EXIT_FAILURE);
}

// 监听连接
if (listen(listenfd, 10) == -1) {
perror("listen");
close(listenfd);
exit(EXIT_FAILURE);
}

// 等待客户端连接
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);

int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len);
if (clientfd == -1) {
perror("accept");
close(listenfd);
exit(EXIT_FAILURE);
}

// 至此,成功建立连接,可以使用 clientfd 与客户端通信

// 关闭套接字
close(clientfd);
close(listenfd);

return 0;
}

在这个示例中,accept 函数被用于等待并接受客户端的连接。它会阻塞程序的执行,直到有客户端连接请求到达,然后创建一个新的套接字 clientfd 用于与客户端通信。在实际使用中,你可以在 clientfd 上进行读写操作,与客户端进行数据交换。连接结束后,应该关闭 clientfdlistenfd

Reference


系统调用 accept
https://flepeng.github.io/002-Linux-41-系统调用-系统调用-accept/
作者
Lepeng
发布于
2024年9月23日
许可协议