C++ TCP/IP 网络编程函数

网络编程函数目录

[TOC]

socket(创建套接字)

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数
  • domain:指定协议族,可以是以下之一:
    • AF_INET:IPv4 协议族
    • AF_INET6:IPv6 协议族
    • AF_UNIX:UNIX 域套接字
  • type:指定套接字类型,可以是以下之一:
    • SOCK_STREAM:提供面向连接的、可靠的数据传输,使用 TCP 协议
    • SOCK_DGRAM:提供无连接的、不可靠的数据传输,使用 UDP 协议
    • SOCK_RAW:提供原始网络访问
  • protocol:指定协议,通常为 0。根据 domaintype 的组合,操作系统会自动选择合适的协议。
返回值
  • 如果函数成功创建了一个套接字,则返回一个非负整数,该整数称为套接字描述符。如果失败,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 socket 函数创建一个 IPv4 的 TCP 套接字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>

int main() {
int sockfd;

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

printf("Socket created successfully. Descriptor: %d\n", sockfd);

return 0;
}

在这个例子中,我们包含了一些必要的头文件,并在 main 函数中调用了 socket 函数。如果成功创建了套接字,它将返回一个非负整数,表示套接字的描述符。

请注意,在实际的网络编程中,你还需要处理套接字的绑定、监听、连接、发送和接收数据等操作。

bind (绑定地址到 socket)

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
  • sockfd:套接字描述符,由 socket 函数返回。
  • addr:指向一个 struct sockaddr 结构体的指针,用于指定要绑定的地址信息。
  • addrlenaddr 结构体的长度。
返回值
  • 如果绑定成功,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

bind 函数用于将一个套接字与特定的地址(IP 地址和端口号)绑定在一起,以便在网络上监听特定的地址。

以下是一个简单的例子,演示了如何使用 bind 函数:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 绑定到端口 8080
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有可用的网络接口

// 将套接字绑定到指定地址
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}

printf("Socket bound successfully.\n");

return 0;
}

在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字。接着,我们设置了一个 struct sockaddr_in 结构体,指定了要绑定的地址信息(IP 地址和端口号)。最后,我们调用 bind 函数将套接字与地址绑定在一起。

请注意,在实际的网络编程中,你可能需要先检查 bind 函数的返回值以确保绑定成功。同时,你还需要在绑定之后进行监听或者接受连接等操作。

listen (监听客户端请求)

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
参数
  • sockfd:套接字描述符,由 socket 函数返回,并且已经通过 bind 函数绑定到一个地址上。
  • backlog:指定在套接字接受新连接之前,可以排队等待的最大连接数。这个参数的具体含义可能因操作系统而异,但通常是一个正整数。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

listen 函数用于将一个套接字设置为被动监听状态,开始接受新的连接请求。

以下是一个简单的例子,演示了如何使用 listen 函数:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 绑定到端口 8080
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有可用的网络接口

// 将套接字绑定到指定地址
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}

// 设置套接字为监听状态,最多可以等待 5 个连接请求
if (listen(sockfd, 5) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

printf("Socket is now listening for incoming connections.\n");

return 0;
}

在这个例子中,我们在 bind 函数之后调用了 listen 函数,并将套接字设置为监听状态,最多可以等待 5 个连接请求。

请注意,在实际的网络编程中,你可能需要根据具体需求调整 backlog 参数,以确保你的服务器能够处理预期的连接数。

accpet (接受客户端连接)

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
  • sockfd:套接字描述符,处于监听状态的套接字。
  • addr:指向一个 struct sockaddr 结构体的指针,用于存储连接方的地址信息。
  • addrlenaddr 结构体的长度。
返回值
  • 如果成功,返回一个新的套接字描述符,用于与客户端通信。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

accept 函数用于从监听套接字中接受新的连接请求,并返回一个新的套接字,用于与客户端通信。

以下是一个简单的例子,演示了如何使用 accept 函数:

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
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int sockfd, new_sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addrlen = sizeof(client_addr);

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 绑定到端口 8080
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有可用的网络接口

// 将套接字绑定到指定地址
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}

// 设置套接字为监听状态,最多可以等待 5 个连接请求
if (listen(sockfd, 5) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

printf("Socket is now listening for incoming connections...\n");

// 接受新的连接请求
new_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);

if (new_sockfd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}

printf("Accepted a new connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 这里可以在 new_sockfd 上进行读写操作,与客户端通信

close(new_sockfd);
close(sockfd);

return 0;
}

在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,并将其绑定到特定的地址上。然后,我们将套接字设置为监听状态,并等待新的连接请求。

一旦有新的连接请求到达,accept 函数将返回一个新的套接字描述符,用于与客户端通信。我们可以在这个新的套接字上进行读写操作,与客户端进行通信。

请注意,一旦通信结束,记得要关闭相应的套接字。

connect (客户端连接服务器端)

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
  • sockfd:套接字描述符,用于与服务器建立连接。
  • addr:指向一个 struct sockaddr 结构体的指针,包含了服务器的地址信息。
  • addrlenaddr 结构体的长度。
返回值
  • 如果成功连接到服务器,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

connect 函数用于与远程服务器建立连接。

以下是一个简单的例子,演示了如何使用 connect 函数:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 连接到端口 8080
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 连接到本地主机

// 连接到服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
exit(EXIT_FAILURE);
}

printf("Connected to the server.\n");

// 这里可以在 sockfd 上进行读写操作,与服务器通信

close(sockfd);

return 0;
}

在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,并将其连接到本地主机的 8080 端口。接着,我们调用 connect 函数与服务器建立连接。

一旦成功连接,我们可以在 sockfd 上进行读写操作,与服务器进行通信。

请注意,一旦通信结束,记得要关闭相应的套接字。

sendto (UDP 发送数据)

简短描述

sendto 函数用于通过已连接或未连接的套接字发送数据。

函数原型
1
2
3
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数
  • sockfd:套接字文件描述符,标识要使用的套接字。
  • buf:指向包含要发送的数据的缓冲区的指针。
  • len:要发送的字节数。
  • flags:用于控制发送操作的标志,通常可以设置为0。
  • dest_addr:指向目标地址信息的指针,对于未连接套接字,用于指定数据的目的地。
  • addrlen:目标地址结构的长度。
返回值
  • 如果成功,返回实际发送的字节数。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 sendto 函数发送数据到指定的目标地址:

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
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;

// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if (sockfd == -1) {
perror("socket");
return 1;
}

// 设置目标地址信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345); // 设置目标端口
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置目标IP地址

// 准备要发送的数据
const char *message = "Hello, sendto function!\n";

// 使用 sendto 发送数据
ssize_t sent_bytes = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));

if (sent_bytes == -1) {
perror("sendto");
return 1;
}

printf("Sent %zd bytes to server.\n", sent_bytes);

// 关闭套接字
if (close(sockfd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先创建了一个数据报套接字,用于进行数据报通信。

然后,我们设置了目标地址信息,包括目标IP地址和端口号。

接着,我们准备要发送的数据,并使用 sendto 函数将数据发送到指定的目标地址。

sendto 函数返回实际发送的字节数。

最后,我们关闭了套接字以释放资源。

recvfrom (UDP 接收数据)

简短描述

recvfrom 函数用于从已连接或未连接的套接字接收数据,同时可以获取数据的发送者的地址信息。

函数原型
1
2
3
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数
  • sockfd:套接字文件描述符,标识要使用的套接字。
  • buf:指向存储接收数据的缓冲区的指针。
  • len:缓冲区的大小,即最多接收的字节数。
  • flags:用于控制接收操作的标志,通常可以设置为0。
  • src_addr:用于存储发送者地址信息的 struct sockaddr 结构的指针,如果不需要获取发送者地址信息,可以将其设置为 NULL
  • addrlen:指向存储发送者地址信息长度的整数的指针,如果不需要获取发送者地址信息,可以将其设置为 NULL
返回值
  • 如果成功,返回实际接收的字节数。
  • 如果连接关闭,返回0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 recvfrom 函数从套接字接收数据,并获取发送者的地址信息:

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
54
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addrlen = sizeof(client_addr);

// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if (sockfd == -1) {
perror("socket");
return 1;
}

// 绑定套接字到本地地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = INADDR_ANY;

if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
return 1;
}

char buffer[1024];

// 使用 recvfrom 接收数据
ssize_t received_bytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_addrlen);

if (received_bytes == -1) {
perror("recvfrom");
return 1;
}

// 输出接收到的数据
printf("Received %zd bytes: %.*s\n", received_bytes, (int)received_bytes, buffer);

// 输出发送者的地址信息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
printf("Received from %s:%d\n", client_ip, ntohs(client_addr.sin_port));

// 关闭套接字
if (close(sockfd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先创建了一个数据报套接字,并将其绑定到本地地址。

然后,我们使用 recvfrom 函数接收从客户端发送过来的数据,并获取了发送者的地址信息。

接收到的数据存储在 buffer 中,recvfrom 函数返回实际接收的字节数。

我们还通过 inet_ntop 函数将发送者的IP地址从二进制转换为点分十进制格式,并输出到屏幕上。

最后,我们关闭了套接字以释放资源。

shutdown (选择性断开输入或输出)

简短描述

shutdown 函数用于关闭套接字的一部分功能,可以选择性地关闭读取或写入功能。

函数原型
1
2
3
#include <sys/socket.h>

int shutdown(int sockfd, int how);
参数
  • sockfd:套接字文件描述符,标识要使用的套接字。
  • how:指定要关闭的功能,可以是以下选项之一:
    • SHUT_RD:关闭套接字的读取功能,不能再从套接字读取数据。
    • SHUT_WR:关闭套接字的写入功能,不能再向套接字写入数据。(会向对端发送 EOF
    • SHUT_RDWR:关闭套接字的读取和写入功能,相当于同时调用 SHUT_RDSHUT_WR
返回值
  • 如果成功,返回0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 shutdown 函数关闭套接字的写入功能:

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
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

int main() {
int sockfd;

// 创建一个套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd == -1) {
perror("socket");
return 1;
}

// 连接到服务器等操作...

// 关闭套接字的写入功能
if (shutdown(sockfd, SHUT_WR) == -1) {
perror("shutdown");
return 1;
}

// 使用 sockfd 进行读取操作,将会返回0表示已经到达流的末尾

// 关闭套接字
if (close(sockfd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先创建了一个套接字。

然后,我们可以进行连接到服务器等操作(这里省略了连接的部分)。

接着,我们使用 shutdown 函数关闭了套接字的写入功能,表示我们不会再向套接字写入数据。

之后,我们可以使用套接字进行读取操作,如果已经到达流的末尾,读取操作将返回0。

最后,我们关闭了套接字以释放资源。

close (关闭套接字连接)

函数原型
1
2
3
#include <unistd.h>

int close(int fd);
参数
  • fd:文件描述符,可以是套接字描述符、文件描述符等。
返回值
  • 如果成功关闭文件描述符,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

close 函数用于关闭一个打开的文件描述符,包括套接字描述符。

以下是一个简单的例子,演示了如何使用 close 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <unistd.h>

int main() {
int fd;

// 打开一个文件或者创建一个套接字,获得一个文件描述符
// 这里假设 fd 是一个有效的文件描述符

// 关闭文件描述符
if (close(fd) < 0) {
perror("close");
return 1;
}

printf("File descriptor closed successfully.\n");

return 0;
}

在这个例子中,我们首先获取了一个有效的文件描述符(这里假设它已经通过合适的方式获得),然后调用 close 函数来关闭它。

请注意,如果 close 函数成功执行,它会返回 0,表示文件描述符已经成功关闭。

dup (复制FD)

简单描述

dup 函数用于复制文件描述符。

详细描述

dup 函数可以用于复制一个已存在的文件描述符,创建一个新的文件描述符,这个新的描述符和原来的描述符指向同一个文件。

函数原型

1
2
3
#include <unistd.h>

int dup(int oldfd);

函数参数

  • oldfd:要复制的文件描述符。

返回值

  • 成功:返回新的文件描述符,即复制后的描述符。
  • 失败:返回-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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}

int new_fd = dup(fd);
if (new_fd == -1) {
perror("dup");
close(fd);
return 1;
}

char buffer[1024];
ssize_t n = read(new_fd, buffer, sizeof(buffer));
if (n == -1) {
perror("read");
close(fd);
close(new_fd);
return 1;
}

printf("Read %zd bytes: %s\n", n, buffer);

close(fd);
close(new_fd);
return 0;
}

在上面的例子中,程序首先使用 open 函数打开一个文件,得到文件描述符 fd。然后使用 dup 函数复制这个文件描述符,得到新的文件描述符 new_fd。接着可以使用这两个描述符进行读取文件操作。

注意事项

  • 使用 dup 函数可以创建一个新的文件描述符,指向同一个文件,这样可以在程序中同时使用两个描述符进行读写操作。
  • 在不再需要使用这两个描述符时,需要分别调用 close 函数关闭它们,以释放相关资源。

dup2 (复制FD,并指定新fd值)

简单描述

dup2 函数用于复制文件描述符,并且可以指定新的文件描述符的值。

详细描述

dup2 函数和 dup 函数类似,都可以用于复制一个已存在的文件描述符。但 dup2 允许指定新的文件描述符的值,而不是由系统自动分配。

函数原型

1
2
3
#include <unistd.h>

int dup2(int oldfd, int newfd);

函数参数

  • oldfd:要复制的文件描述符。
  • newfd:要设置的新文件描述符的值。

返回值

  • 成功:返回新的文件描述符,即复制后的描述符。
  • 失败:返回-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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}

int new_fd = 10; // 假设新的文件描述符值为10

if (dup2(fd, new_fd) == -1) {
perror("dup2");
close(fd);
return 1;
}

char buffer[1024];
ssize_t n = read(new_fd, buffer, sizeof(buffer));
if (n == -1) {
perror("read");
close(fd);
return 1;
}

printf("Read %zd bytes: %s\n", n, buffer);

close(fd);
return 0;
}

在上面的例子中,程序首先使用 open 函数打开一个文件,得到文件描述符 fd。然后使用 dup2 函数将 fd 复制到指定的新文件描述符 new_fd。接着可以使用这两个描述符进行读取文件操作。

注意事项

  • 使用 dup2 函数可以创建一个新的文件描述符,并且可以指定其值,而不是由系统自动分配。
  • 在不再需要使用这两个描述符时,需要分别调用 close 函数关闭它们,以释放相关资源。

getsockopt (获取套接字的选项值)

简短描述

getsockopt 函数用于获取套接字的选项值。

函数原型
1
2
3
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
参数
  • sockfd:套接字文件描述符,标识要使用的套接字。
  • level:选项所在的协议层级,通常可以设置为 SOL_SOCKET 表示在套接字层级操作。
  • optname:选项的名称,如 SO_REUSEADDR
  • optval:指向用于接收选项值的缓冲区的指针。
  • optlen:指向存储 optval 大小的整数的指针。
返回值
  • 如果成功,返回0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 getsockopt 函数获取套接字选项值:

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
#include <stdio.h>
#include <sys/socket.h>

int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd == -1) {
perror("socket");
return 1;
}

int option;
socklen_t option_len = sizeof(option);

// 使用 getsockopt 获取 SO_REUSEADDR 选项值
if (getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, &option_len) == -1) {
perror("getsockopt");
return 1;
}

printf("SO_REUSEADDR option value: %d\n", option);

// 关闭套接字
if (close(sockfd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先创建了一个套接字。

然后,我们定义了一个整数变量 option 以及一个 socklen_t 类型的变量 option_len 来存储选项值和选项值的大小。

接着,我们使用 getsockopt 函数来获取 SO_REUSEADDR 选项的值。

获取成功后,选项值将存储在 option 中,我们将其打印出来。

最后,我们关闭了套接字以释放资源。

setsockopt (设置套接字选项值)

简短描述

setsockopt 函数用于设置套接字的选项值。

函数原型
1
2
3
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数
  • sockfd:套接字文件描述符,标识要使用的套接字。
  • level:选项所在的协议层级,通常可以设置为 SOL_SOCKET 表示在套接字层级操作。
  • optname:选项的名称,如 SO_REUSEADDR
  • optval:指向包含新选项值的缓冲区的指针。
  • optlen:指定了 optval 缓冲区的大小。
可选项
协议层 选项名 作用 适用于 setsockopt 适用于 getsockopt
SOL_SOCKET SO_REUSEADDR 允许重新使用本地地址 Yes Yes
SOL_SOCKET SO_REUSEPORT 允许多个进程或线程绑定相同的端口 Yes Yes
SOL_SOCKET SO_BROADCAST 允许发送广播消息 Yes Yes
SOL_SOCKET SO_KEEPALIVE 开启保持活动检测 Yes Yes
SOL_SOCKET SO_RCVBUF 接收缓冲区大小 Yes Yes
SOL_SOCKET SO_SNDBUF 发送缓冲区大小 Yes Yes
SOL_SOCKET SO_RCVTIMEO 接收超时时间 Yes Yes
SOL_SOCKET SO_SNDTIMEO 发送超时时间 Yes Yes
IPPROTO_TCP TCP_NODELAY 禁用 Nagle 算法 Yes Yes
IPPROTO_TCP TCP_MAXSEG 设置 TCP 最大段大小 Yes Yes
IPPROTO_TCP TCP_KEEPIDLE 设置 TCP 连接的空闲时间 Yes Yes
IPPROTO_TCP TCP_KEEPINTVL 设置 TCP 保持活动检测的间隔 Yes Yes
IPPROTO_TCP TCP_KEEPCNT 设置 TCP 保持活动检测的尝试次数 Yes Yes
IPPROTO_TCP TCP_QUICKACK 启用快速应答模式 Yes Yes
IPPROTO_IP IP_TTL 设置 IP 生存时间 Yes Yes
IPPROTO_IP IP_MULTICAST_TTL 设置多播 TTL(UDP socket) Yes
IPPROTO_IP IP_ADD_MEMBERSHIP 加入多播组 Yes
IPPROTO_IPV6 IPV6_V6ONLY 仅接受 IPv6 流量 Yes Yes

image-20230908152408671

image-20230908152419109

返回值
  • 如果成功,返回0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 setsockopt 函数设置套接字选项值:

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
#include <stdio.h>
#include <sys/socket.h>

int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd == -1) {
perror("socket");
return 1;
}

int option = 1;

// 使用 setsockopt 设置 SO_REUSEADDR 选项值
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) == -1) {
perror("setsockopt");
return 1;
}

printf("SO_REUSEADDR option set\n");

// 关闭套接字
if (close(sockfd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先创建了一个套接字。

然后,我们定义了一个整数变量 option 并将其设置为1,表示启用 SO_REUSEADDR 选项。

接着,我们使用 setsockopt 函数来设置套接字的选项值。

设置成功后,我们打印一条消息。

最后,我们关闭了套接字以释放资源。

write (将数据写入FD)

简短描述

write 函数用于将数据写入文件描述符(通常是文件、管道或套接字)中。

函数原型
1
2
3
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
参数
  • fd:文件描述符,标识要写入的目标文件或其他数据流。
  • buf:指向要写入的数据的缓冲区的指针。
  • count:要写入的字节数。
返回值
  • 如果成功,返回实际写入的字节数(可能小于 count)。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 write 函数将数据写入文件:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
const char *message = "Hello, write function!\n";
int fd;

// 打开文件以供写入
fd = open("output.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

if (fd == -1) {
perror("open");
return 1;
}

// 使用 write 函数将数据写入文件
ssize_t bytes_written = write(fd, message, strlen(message));

if (bytes_written == -1) {
perror("write");
return 1;
}

printf("Wrote %zd bytes to the file.\n", bytes_written);

// 关闭文件
if (close(fd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先打开一个名为 “output.txt” 的文件,以便我们可以将数据写入其中。

然后,我们使用 write 函数将 message 中的数据写入文件。strlen(message) 用于计算要写入的字节数。

最后,我们关闭文件以释放资源。

请注意,write 函数返回实际写入的字节数,这可能小于 count

read (从 FD 读取数据)

简短描述

read 函数用于从文件描述符(通常是文件、管道或套接字)中读取数据。

函数原型
1
2
3
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
参数
  • fd:文件描述符,标识要读取的源文件或其他数据流。
  • buf:指向存储读取数据的缓冲区的指针。
  • count:要读取的最大字节数。
返回值
  • 如果成功,返回实际读取的字节数。
  • 如果已经到达文件末尾,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 read 函数从文件中读取数据:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
char buffer[100];
int fd;

// 打开文件以供读取
fd = open("input.txt", O_RDONLY);

if (fd == -1) {
perror("open");
return 1;
}

// 使用 read 函数从文件中读取数据
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));

if (bytes_read == -1) {
perror("read");
return 1;
}

// 输出所读取的内容
printf("Read %zd bytes: %.*s\n", bytes_read, (int)bytes_read, buffer);

// 关闭文件
if (close(fd) == -1) {
perror("close");
return 1;
}

return 0;
}

在这个例子中,我们首先打开一个名为 “input.txt” 的文件,以便我们可以从中读取数据。

然后,我们使用 read 函数将最多 sizeof(buffer) 个字节的数据读取到缓冲区 buffer 中。

read 函数返回实际读取的字节数。

最后,我们输出所读取的内容,并关闭文件以释放资源。

writev (向文件描述符写入多个非连续缓冲区的数据)

简单描述

writev 函数用于向文件描述符写入多个非连续缓冲区的数据。

详细描述

writev 函数可以用于将多个非连续的缓冲区的数据一次性写入到文件描述符中。这个函数通常用于提高写入效率,尤其是在需要将散布在不同内存区域的数据写入到文件时。

函数原型

1
2
3
#include <sys/uio.h>

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

函数参数

  • fd:目标文件描述符。
  • iov:一个指向 iovec 结构体数组的指针,每个 iovec 结构体描述了一个缓冲区的起始地址和长度。
  • iovcntiovec 结构体数组的长度,也就是缓冲区的个数。

返回值

  • 成功:返回写入的总字节数。
  • 失败:返回-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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
char msg1[] = "Hello, ";
char msg2[] = "world!\n";

struct iovec iov[2];
iov[0].iov_base = msg1;
iov[0].iov_len = strlen(msg1);
iov[1].iov_base = msg2;
iov[1].iov_len = strlen(msg2);

int total_bytes = writev(STDOUT_FILENO, iov, 2);

if (total_bytes == -1) {
perror("writev");
exit(EXIT_FAILURE);
}

printf("Total bytes written: %d\n", total_bytes);

return 0;
}

在上面的例子中,程序创建了两个缓冲区 msg1msg2,然后通过 struct iovec 结构体数组将它们描述为两个不连续的缓冲区,最后使用 writev 函数一次性将它们写入到标准输出中。

注意事项

  • 使用 writev 函数可以减少系统调用的次数,提高写入效率。
  • struct iovec 结构体中的 iov_base 成员指向缓冲区的起始地址,iov_len 成员表示缓冲区的长度。
  • 可以根据实际需求创建更多的 iovec 结构体来描述多个缓冲区。

readv (从文件描述符读取多个非连续缓冲区的数据)

简单描述

readv 函数用于从文件描述符读取多个非连续缓冲区的数据。

详细描述

readv 函数可以用于将多个非连续的缓冲区的数据一次性读取到文件描述符中。这个函数通常用于提高读取效率,尤其是在需要从文件中读取到不同内存区域时。

函数原型

1
2
3
#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

函数参数

  • fd:源文件描述符。
  • iov:一个指向 iovec 结构体数组的指针,每个 iovec 结构体描述了一个缓冲区的起始地址和长度。
  • iovcntiovec 结构体数组的长度,也就是缓冲区的个数。

返回值

  • 成功:返回读取的总字节数。
  • 失败:返回-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
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
char buffer1[5];
char buffer2[10];

struct iovec iov[2];
iov[0].iov_base = buffer1;
iov[0].iov_len = sizeof(buffer1);
iov[1].iov_base = buffer2;
iov[1].iov_len = sizeof(buffer2);

int total_bytes = readv(STDIN_FILENO, iov, 2);

if (total_bytes == -1) {
perror("readv");
exit(EXIT_FAILURE);
}

printf("Total bytes read: %d\n", total_bytes);

return 0;
}

在上面的例子中,程序创建了两个缓冲区 buffer1buffer2,然后通过 struct iovec 结构体数组将它们描述为两个不连续的缓冲区,最后使用 readv 函数一次性将数据从标准输入中读取到这两个缓冲区中。

注意事项

  • 使用 readv 函数可以减少系统调用的次数,提高读取效率。
  • struct iovec 结构体中的 iov_base 成员指向缓冲区的起始地址,iov_len 成员表示缓冲区的长度。
  • 可以根据实际需求创建更多的 iovec 结构体来描述多个缓冲区。

send (在已有套接字上发送数据)

简单描述

send 函数用于在一个已连接的套接字上发送数据。

详细描述

send 函数用于在已连接的套接字上发送数据。它可以用于发送TCP或者UDP套接字上的数据。

函数原型

1
2
3
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

函数参数

  • sockfd:已连接套接字的文件描述符。
  • buf:指向要发送的数据的缓冲区的指针。
  • len:要发送的数据的长度。
  • flags:可选的标志参数,通常可以设置为0。
常用flag
Flag 作用 适用于 send 适用于 recv
0 默认行为,通常表示阻塞等待直到数据被发送或接收完毕 Yes Yes
MSG_DONTWAIT 表示以非阻塞方式发送或接收数据 Yes Yes
MSG_OOB 处理带外(out-of-band)数据 Yes Yes
MSG_PEEK 接收数据时保留数据,而不从接收队列中移除 No Yes
MSG_WAITALL 保证在接收数据时接收到指定长度的数据 No Yes
MSG_NOSIGNAL 当远程连接断开时不发送 SIGPIPE 信号 Yes No
MSG_CONFIRM 用于 sendto,表示数据报发送时要求确认 Yes No
MSG_EOR 表示接收到数据报的末尾 No Yes
MSG_DONTROUTRE 数据传输过程中不参照路由表,在本地网络中寻找目的地 YES NO

下面对每个 flag 进行简要解释:

  1. 0: 默认行为,通常表示阻塞等待直到数据被发送或接收完毕。
  2. MSG_DONTWAIT: 表示以非阻塞方式发送或接收数据,即立即返回,无论是否可以完成操作。
  3. MSG_OOB: 处理带外(out-of-band)数据,用于紧急数据传输。
  4. MSG_PEEK: 接收数据时保留数据,而不从接收队列中移除,使得之后的 recv 仍然可以接收到相同的数据。
  5. MSG_WAITALL: 保证在接收数据时接收到指定长度的数据,直到全部数据接收完毕。
  6. MSG_NOSIGNAL: 当远程连接断开时不发送 SIGPIPE 信号,而是返回一个错误。
  7. MSG_CONFIRM: 用于 sendto,表示数据报发送时要求确认。
  8. MSG_EOR: 表示接收到数据报的末尾。

请注意,sendrecv 的 flag 参数可以通过按位或操作(|)来组合使用,以实现多个选项的组合。例如,如果你想以非阻塞方式发送并且处理带外数据,可以使用 MSG_DONTWAIT | MSG_OOB

返回值

  • 成功:返回发送的字节数(可能小于请求发送的字节数)。
  • 失败:返回-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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;

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

// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}

// 发送数据
const char *message = "Hello, server!";
int message_len = strlen(message);
int sent_bytes = send(sockfd, message, message_len, 0);

if (sent_bytes == -1) {
perror("send");
} else {
printf("Sent %d bytes: %s\n", sent_bytes, message);
}

// 关闭套接字
close(sockfd);

return 0;
}

在上面的例子中,程序创建了一个TCP套接字,连接到服务器,并使用 send 函数发送数据到服务器。

注意事项

  • send 可能会发送比 len 更少的字节。在这种情况下,可以使用循环来保证所有数据都被发送。
  • 在非阻塞套接字上,send 可能会返回 EAGAIN 或者 EWOULDBLOCK 错误,表示当前套接字不可写。此时可以使用 select 或者 poll 函数等待套接字变得可写后再次尝试发送。

recv (从已连接的套接字中接收数据)

简单描述

recv 函数用于从已连接的套接字中接收数据。

详细描述

recv 函数用于从已连接的套接字中接收数据。它可以用于接收TCP或者UDP套接字上的数据。

函数原型

1
2
3
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

函数参数

  • sockfd:已连接套接字的文件描述符。
  • buf:指向接收数据的缓冲区的指针。
  • len:缓冲区的长度。
  • flags:可选的标志参数,通常可以设置为0。

返回值

  • 成功:返回接收到的字节数(可能小于请求接收的字节数)。
  • 失败:返回-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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[1024];

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

// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}

// 接收数据
int received_bytes = recv(sockfd, buffer, sizeof(buffer), 0);

if (received_bytes == -1) {
perror("recv");
} else {
buffer[received_bytes] = '\0';
printf("Received: %s\n", buffer);
}

// 关闭套接字
close(sockfd);

return 0;
}

在上面的例子中,程序创建了一个TCP套接字,连接到服务器,并使用 recv 函数接收数据。

注意事项

  • recv 可能会接收比 len 更少的字节。在这种情况下,可以使用循环来保证所有数据都被接收。
  • 在非阻塞套接字上,recv 可能会返回 EAGAIN 或者 EWOULDBLOCK 错误,表示当前套接字没有可读数据。此时可以使用 select 或者 poll 函数等待套接字变得可读后再次尝试接收。

open (打开或创建一个文件,返回FD)

简短描述

open 函数用于打开或创建一个文件,并返回与之关联的文件描述符。

函数原型
1
2
3
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);
参数
  • pathname:要打开的文件的路径名。
  • flags:用于控制打开文件的行为,可以是以下选项的组合:
    • O_RDONLY:只读打开文件。
    • O_WRONLY:只写打开文件。
    • O_RDWR:读写打开文件。
    • O_CREAT:如果文件不存在,则创建文件。
    • O_TRUNC:如果文件已存在,截断文件(将其大小设为0)。
    • O_APPEND:在文件末尾追加写入。
  • mode:当创建新文件时,设置文件的权限。通常与 O_CREAT 一起使用。

open 函数的第三个参数是文件的权限模式,通常使用八进制数表示,它控制了文件的访问权限。该参数在创建文件时起作用,如果文件已经存在,则会被忽略。

权限模式是一个三位的数字,通常由三组三位数表示(如 777)。每组数字分别代表了所有者、群组和其他用户的权限。每个三位数又由三个权限标志位组成,分别是读(r)、写(w)和执行(x)。

具体来说:

  • r 表示读权限,允许读取文件内容。
  • w 表示写权限,允许修改文件内容。
  • x 表示执行权限,对于可执行文件,允许运行它。

这些权限位可以通过将它们相加得到所需的权限模式。例如:

  • 0 表示没有任何权限。
  • 1 表示执行权限。
  • 2 表示写权限。
  • 3 表示写和执行权限。
  • 4 表示读权限。
  • 5 表示读和执行权限。
  • 6 表示读和写权限。
  • 7 表示读、写和执行权限。

举个例子,如果你想设置所有者具有读、写和执行权限,群组和其他用户具有只读权限,你可以使用权限模式 755

在C语言中,可以使用八进制数表示权限模式,例如 06440755

示例代码:

1
int file_descriptor = open("example.txt", O_WRONLY | O_CREAT, 0644);

上述代码使用 open 函数创建了一个文件,具有所有者读写权限和群组、其他用户只读权限的权限模式。

返回值
  • 如果成功,返回与文件相关联的文件描述符(非负整数)。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 open 函数打开或创建一个文件:

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
#include <stdio.h>
#include <fcntl.h>

int main() {
int fd;

// 打开一个已存在的文件以供读取
fd = open("existing_file.txt", O_RDONLY);

if (fd == -1) {
perror("open");
return 1;
}

printf("Opened existing_file.txt for reading.\n");

// 创建一个新文件以供写入
fd = open("new_file.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);

if (fd == -1) {
perror("open");
return 1;
}

printf("Created new_file.txt for writing.\n");

return 0;
}

在这个例子中,我们首先尝试打开一个名为 “existing_file.txt” 的已存在文件以供读取。

如果成功,open 函数将返回一个与文件关联的文件描述符,可以用于后续的读取操作。

接着,我们尝试创建一个名为 “new_file.txt” 的新文件以供写入。

我们在 flags 参数中使用了 O_WRONLY(只写)、O_CREAT(如果不存在则创建)、O_TRUNC(截断文件)以及 mode 参数来指定文件的权限。

如果成功,open 函数将返回一个与新创建文件关联的文件描述符,可以用于后续的写入操作。

fopen (以指定模式打开文件,返回FILE*)

简短描述

fopen 函数用于以指定的模式打开文件,并返回一个与之关联的文件指针。最后需要使用 fclose 关闭文件

函数原型
1
2
3
#include <stdio.h>

FILE *fopen(const char *pathname, const char *mode);
参数
  • pathname:要打开的文件的路径名。
  • mode:打开文件的模式字符串,可以是以下之一:
    • "r":只读模式,打开文件用于读取。
    • "w":只写模式,创建一个新文件或截断一个已存在文件,并打开文件用于写入。
    • "a":追加模式,打开文件用于写入,将数据追加到文件末尾。
    • "r+":读写模式,打开文件用于读取和写入。
    • "w+":读写模式,创建一个新文件或截断一个已存在文件,并打开文件用于读取和写入。
    • "a+":读写模式,打开文件用于读取和写入,将数据追加到文件末尾。
返回值
  • 如果成功,返回一个指向文件的指针。
  • 如果出现错误,返回 NULL,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 fopen 函数打开或创建一个文件:

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
#include <stdio.h>

int main() {
FILE *file;

// 以只读模式打开已存在的文件
file = fopen("existing_file.txt", "r");

if (file == NULL) {
perror("fopen");
return 1;
}

printf("Opened existing_file.txt for reading.\n");

// 创建一个新文件以供写入
file = fopen("new_file.txt", "w");

if (file == NULL) {
perror("fopen");
return 1;
}

printf("Created new_file.txt for writing.\n");

fclose(file);
return 0;
}

在这个例子中,我们首先尝试以只读模式打开一个名为 “existing_file.txt” 的已存在文件。

如果成功,fopen 函数将返回一个指向文件的指针,可以用于后续的读取操作。

接着,我们尝试创建一个名为 “new_file.txt” 的新文件以供写入。

我们在 mode 参数中使用了 "w"(只写模式),这将创建一个新文件或截断一个已存在文件,并打开文件用于写入。

如果成功,fopen 函数将返回一个指向新创建文件的指针,可以用于后续的写入操作。

fclose (关闭 fopen 打开的文件)

简短描述

fclose 函数用于关闭一个打开的文件。

函数原型
1
2
3
#include <stdio.h>

int fclose(FILE *stream);
参数
  • stream:要关闭的文件指针。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回 EOF,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 fclose 函数关闭一个打开的文件:

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
#include <stdio.h>

int main() {
FILE *file;

// 打开文件以供写入
file = fopen("example.txt", "w");

if (file == NULL) {
perror("fopen");
return 1;
}

// 写入数据到文件
fprintf(file, "Hello, fclose function!\n");

// 关闭文件
if (fclose(file) == EOF) {
perror("fclose");
return 1;
}

printf("File closed successfully.\n");

return 0;
}

在这个例子中,我们首先打开一个名为 “example.txt” 的文件以供写入。

然后,我们使用 fprintf 函数将数据写入文件。

最后,我们使用 fclose 函数关闭文件。如果成功,fclose 函数将返回 0,否则返回 EOF

fdopen (转换fd->FILE*)

简单描述

fdopen 函数用于将一个已存在的文件描述符关联到一个 FILE* 流。

详细描述

fdopen 函数允许将一个已经打开的文件描述符转换成一个 FILE* 流,这样就可以使用标准I/O库函数来操作该文件描述符。

函数原型

1
2
3
#include <stdio.h>

FILE* fdopen(int fd, const char *mode);

函数参数

  • fd:已存在的文件描述符。
  • mode:打开模式字符串,可以是 "r"(只读),"w"(写入),"a"(追加)等。

返回值

  • 成功:返回与给定文件描述符关联的 FILE* 指针。
  • 失败:返回 NULL,并设置 errno 来指示错误类型。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <unistd.h>

int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
return 1;
}

FILE *file = fdopen(fd, "w");
if (file == NULL) {
perror("fdopen");
close(fd);
return 1;
}

fprintf(file, "Hello, fdopen!\n");

fclose(file);
return 0;
}

在上面的例子中,程序首先使用 open 函数打开一个文件,得到文件描述符 fd。然后使用 fdopen 将该文件描述符转换成一个 FILE* 流,接着可以使用标准I/O库函数如 fprintf 写入数据到文件中。

注意事项

  • fdopen 可以用于将文件描述符与 stdio.h 中提供的标准I/O函数关联起来,使得可以使用标准I/O库函数来操作该文件描述符。
  • 当使用 fdopen 关联一个文件描述符时,不应该再使用 close 函数关闭该文件描述符,应该使用 fclose 关闭与之关联的 FILE* 流。
  • 使用 fdopen 打开文件时,要确保文件描述符和打开模式的兼容性。例如,如果打开模式是 "w",则文件描述符必须是可写的。

fileno (转换FILE*->fd)

简单描述

fileno 函数用于获取一个 FILE* 流对应的文件描述符。

详细描述

fileno 函数可以用于获取一个 FILE* 流对应的底层文件描述符,这样可以使用底层文件描述符进行系统调用或其他操作。

函数原型

1
2
3
#include <stdio.h>

int fileno(FILE *stream);

函数参数

  • stream:一个指向已打开的 FILE* 流的指针。

返回值

  • 成功:返回与给定 FILE* 流关联的文件描述符。
  • 失败:返回-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
#include <stdio.h>
#include <unistd.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("fopen");
return 1;
}

int fd = fileno(file);
if (fd == -1) {
perror("fileno");
fclose(file);
return 1;
}

char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1) {
perror("read");
fclose(file);
return 1;
}

printf("Read %zd bytes: %s\n", n, buffer);

fclose(file);
return 0;
}

在上面的例子中,程序首先使用 fopen 函数打开一个文件,得到一个 FILE* 流。然后使用 fileno 函数获取该流对应的文件描述符,接着可以使用底层文件描述符进行系统调用,比如使用 read 函数读取文件。

注意事项

  • fileno 函数用于获取与给定 FILE* 流关联的文件描述符,可以用于在 stdio.h 提供的标准I/O函数和系统调用之间进行转换。
  • 使用 fileno 获取文件描述符后,要小心使用该描述符,以防对底层文件的操作与 FILE* 流之间发生冲突。

fcntl (控制文件描述符属性)

参考:https://blog.csdn.net/qq_37414405/article/details/83690447

简单描述

fcntl 函数用于对已打开的文件描述符进行控制操作,包括修改文件状态标志、获取文件状态信息等。

详细描述

fcntl 函数是一个功能强大的系统调用,可以对文件描述符进行各种控制操作,如修改文件状态标志、获取文件状态信息、复制文件描述符等。

函数原型
1
2
3
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */);
参数
  • fd:文件描述符,可以是套接字描述符、文件描述符等。

  • cmd:控制命令,指定 fcntl 将执行的操作。

    复制一个现有的描述符(cmd=F_DUPFD).

    获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

    获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

    获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

    获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).

    • F_DUPFD:复制文件描述符。
    • F_GETFD:获取文件描述符标志。
    • F_SETFD:设置文件描述符标志。
    • F_GETFL:获取文件状态标志(文件打开方式)。
    • F_SETFL:设置文件状态标志(文件打开方式)。
    • F_GETLK:获取文件锁定信息。
    • F_SETLK:设置文件锁定信息。
    • F_SETLKW:设置文件锁定信息并等待。
  • arg:根据 cmd 的不同,arg 可能是一个整数值,也可能是一个结构体指针。

返回值
  • 根据 cmd 的不同,fcntl 函数会返回不同的值。通常,成功执行会返回一个非负整数,出错时返回 -1,并设置 errno
使用方法

fcntl 是一个用于对文件描述符进行控制操作的系统调用。它可以用于执行许多不同的任务,包括设置文件描述符属性、锁定文件、以及改变文件描述符的行为等。

下面我将详细讲解 fcntl 的几种常见用法:

1. 设置文件描述符属性(F_SETFL)
1
int fcntl(int fd, int cmd, ...);
  • cmd 参数被设置为 F_SETFL 时,可以用来设置文件描述符的标志属性。
1
2
3
int flags = fcntl(fd, F_GETFL); // 获取当前标志
flags |= O_NONBLOCK; // 添加非阻塞标志
fcntl(fd, F_SETFL, flags); // 设置新的标志
2. 获取文件描述符标志(F_GETFL)
1
int fcntl(int fd, int cmd, ...);
  • cmd 参数被设置为 F_GETFL 时,可以用于获取文件描述符的标志属性。
1
int flags = fcntl(fd, F_GETFL);
3. 获取/设置文件锁(F_SETLK、F_SETLKW、F_GETLK)
1
int fcntl(int fd, int cmd, struct flock *lock);
  • cmd

    参数可以是以下之一:

    • F_SETLK: 设置文件锁。如果请求的锁与现有锁冲突,将返回 -1。
    • F_SETLKW: 设置文件锁,但会等待直到锁定成功。
    • F_GETLK: 获取文件锁的状态。
1
2
3
4
5
6
struct flock fl;
fl.l_type = F_WRLCK; // 设置锁类型为写锁
fl.l_whence = SEEK_SET;
fl.l_start = 0; // 从文件开始位置开始锁定
fl.l_len = 0; // 锁定整个文件
fcntl(fd, F_SETLK, &fl); // 设置文件锁
4. 设置异步 I/O 信号处理器(F_SETOWN)
1
int fcntl(int fd, int cmd, pid_t owner);
  • cmd 参数被设置为 F_SETOWN 时,可以将文件描述符的异步 I/O 信号的接收者设置为指定的进程。
1
fcntl(fd, F_SETOWN, getpid()); // 将当前进程设置为异步 I/O 信号的接收者
5. 获取/设置文件描述符标志(F_GETFD、F_SETFD)
1
int fcntl(int fd, int cmd, long arg);
  • cmd

    参数可以是以下之一:

    • F_GETFD: 获取文件描述符标志。
    • F_SETFD: 设置文件描述符标志。
1
2
3
int flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC; // 设置FD_CLOEXEC标志
fcntl(fd, F_SETFD, flags);
6. 获取/设置文件状态标志(F_GETFL、F_SETFL)
1
int fcntl(int fd, int cmd, ...);
  • cmd

    参数可以是以下之一:

    • F_GETFL: 获取文件状态标志。
    • F_SETFL: 设置文件状态标志。
1
2
3
int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK; // 设置非阻塞标志
fcntl(fd, F_SETFL, flags);
7. 获取/设置信号处理器(F_GETOWN、F_SETOWN)
1
int fcntl(int fd, int cmd, ...);
  • cmd

    参数可以是以下之一:

    • F_GETOWN: 获取异步 I/O 信号的接收者。
    • F_SETOWN: 设置异步 I/O 信号的接收者。
1
pid_t owner = fcntl(fd, F_GETOWN); // 获取异步 I/O 信号的接收者

fcntl 函数用于对已打开的文件描述符进行各种控制操作。

以下是一个简单的例子,演示了如何使用 fcntl 函数:

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
#include <stdio.h>
#include <fcntl.h>

int main() {
int fd;

// 打开一个文件或者创建一个套接字,获得一个文件描述符
// 这里假设 fd 是一个有效的文件描述符

// 设置文件描述符为非阻塞模式
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
perror("fcntl");
return 1;
}

flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
perror("fcntl");
return 1;
}

printf("File descriptor set to non-blocking mode.\n");

return 0;
}

在这个例子中,我们首先获取了一个有效的文件描述符(这里假设它已经通过合适的方式获得),然后使用 fcntl 函数将该文件描述符设置为非阻塞模式。

请注意,fcntl 函数的参数 cmd 决定了它的具体行为,同时也决定了是否需要提供额外的参数 arg。因此,在实际使用时,请根据需要选择正确的 cmd 值,并根据需要提供相应的参数。

setsockopt (设置套接字可选项)

简短描述

setsockopt 函数用于设置套接字选项,可以在套接字创建后动态地调整其行为。

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数
  • sockfd:套接字描述符,指定要设置选项的套接字。

  • level:选项所属的协议层次,通常是 SOL_SOCKET(套接字层次)。

  • optname:要设置的选项名,具体的选项可以是各种套接字选项,比如 SO_REUSEADDRSO_RCVBUF 等。

    SO_REUSEADDR 取消四次挥手时的 TIME_WAIT,使得端口可立即重用,默认 0;改为 1 即可重用。

    • SO_REUSEADDR 允许一个套接字绑定到一个已在使用中的地址(ip:port)。这在套接字关闭后,立即重新启动程序时很有用,因为在套接字关闭后,该地址会被内核保留一段时间以确保之前连接的数据能被正确地传递。
    • 它可以避免 “Address already in use” 错误。
    • 注意,SO_REUSEADDR 允许地址的重复使用,但是仍然会保证连接的唯一性。

    SO_REUSEPORT:默认0,改为1可重用。

    • SO_REUSEPORT 允许多个套接字绑定到同一个IP地址和端口号上。它通常用于实现负载均衡,多个套接字可以同时接收来自同一个端口的数据包。
    • 这个选项在 Linux 3.9 及以后的内核版本才可用。
    • 使用 SO_REUSEPORT 时,需要确保所有绑定到相同地址和端口的套接字都设置了相同的 SO_REUSEPORT 选项。
  • optval:指向包含选项值的内存区域。

  • optlenoptval 内存区域的长度。

返回值
  • 如果成功设置选项,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 setsockopt 函数:

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
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>

int main() {
int sockfd;
int reuse = 1; // 启用地址重用选项

// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

// 设置 SO_REUSEADDR 选项
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0) {
perror("setsockopt");
exit(EXIT_FAILURE);
}

printf("SO_REUSEADDR option set.\n");

// 在此之后,你可以继续配置套接字的其他选项或者进行其他操作

close(sockfd);

return 0;
}

在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,然后使用 setsockopt 函数将 SO_REUSEADDR 选项设置为启用状态。

SO_REUSEADDR 选项允许在套接字关闭后,仍然可以绑定到相同的地址和端口,这在服务器程序经常需要重新启动时很有用。

请注意,我们在设置选项之前,首先创建了一个套接字并检查其是否创建成功。

inet_ntop (netip -> string)

简短描述

inet_ntop 函数用于将网络字节序的二进制地址转换为文本形式的地址表示。

函数原型
1
2
3
#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数
  • af:地址族,可以是 AF_INET(IPv4)或者 AF_INET6(IPv6)。
  • src:指向包含二进制地址的内存区域的指针。
  • dst:指向存储文本形式地址的缓冲区的指针。
  • size:目标缓冲区 dst 的长度。
返回值
  • 如果转换成功,返回指向目标缓冲区的指针,即 dst
  • 如果出现错误,返回 NULL,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 inet_ntop 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <arpa/inet.h>

int main() {
const char *binary_ip = "\x7F\x00\x00\x01"; // 127.0.0.1 的二进制表示
char text_ip[INET_ADDRSTRLEN];

// 将二进制地址转换为文本形式
const char *result = inet_ntop(AF_INET, binary_ip, text_ip, sizeof(text_ip));

if (result == NULL) {
perror("inet_ntop");
return 1;
}

printf("IP Address: %s\n", text_ip);

return 0;
}

在这个例子中,我们首先定义了一个包含了二进制表示的 IPv4 地址的字符串 binary_ip。接着,我们创建一个字符数组 text_ip 用于存储转换后的文本形式地址。

然后,我们调用 inet_ntop 函数将二进制地址转换为文本形式,结果存储在 text_ip 中。

请注意,INET_ADDRSTRLEN 是一个宏定义,用于表示 IPv4 地址的最大长度,通常是 16 个字符。

inet_addr (将点分十进制IP转换为网络序)

简短描述

inet_addr 函数将一个点分十进制的IP地址转换为网络字节序(大端序)的32位整数形式。

函数原型
1
2
3
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
参数
  • cp:指向包含点分十进制IP地址的字符串的指针。
返回值
  • 如果成功,返回网络字节序的32位整数形式的IP地址。
  • 如果出现错误,返回 INADDR_NONE(通常是 -1),表示转换失败。
使用方法

以下是一个简单的例子,演示了如何使用 inet_addr 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <arpa/inet.h>

int main() {
const char *ip_address = "192.168.1.1";
in_addr_t result;

// 将点分十进制IP地址转换为网络字节序的整数形式
result = inet_addr(ip_address);

if (result == INADDR_NONE) {
perror("inet_addr");
return 1;
}

printf("IP address in network byte order: 0x%08X\n", result);

return 0;
}

在这个例子中,我们将一个点分十进制的IP地址字符串 "192.168.1.1" 传递给 inet_addr 函数。

如果转换成功,inet_addr 将返回网络字节序的32位整数形式的IP地址。

请注意,INADDR_NONE 是一个特殊的宏,表示转换失败。

inet_aton (将点分十进制转换为网络序,存到一个变量)

简短描述

inet_aton 函数将一个点分十进制的IP地址转换为网络字节序的32位整数形式。

函数原型
1
2
3
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
参数
  • cp:指向包含点分十进制IP地址的字符串的指针。
  • inp:指向 struct in_addr 结构的指针,用于存储转换后的IP地址。
返回值
  • 如果成功,返回非零值。
  • 如果出现错误,返回零。
使用方法

以下是一个简单的例子,演示了如何使用 inet_aton 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <arpa/inet.h>

int main() {
const char *ip_address = "192.168.1.1";
struct in_addr result;

// 将点分十进制IP地址转换为网络字节序的整数形式
if (inet_aton(ip_address, &result) == 0) {
perror("inet_aton");
return 1;
}

printf("IP address in network byte order: 0x%08X\n", result.s_addr);

return 0;
}

在这个例子中,我们将一个点分十进制的IP地址字符串 "192.168.1.1" 传递给 inet_aton 函数。

如果转换成功,inet_aton 将把转换后的IP地址存储在 result 结构中。

请注意,struct in_addr 结构包含了一个 s_addr 成员,用于存储32位整数形式的IP地址。

inet_ntoa (将IP转为点分十进制字符串)

简短描述

inet_ntoa 函数将一个32位整数形式的IP地址转换为点分十进制的字符串形式。

函数原型
1
2
3
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
参数
  • in:一个 struct in_addr 结构,其中包含了32位整数形式的IP地址。
返回值
  • 成功返回一个指向包含点分十进制IP地址的静态缓冲区的指针。
  • 失败返回 -1
使用方法

以下是一个简单的例子,演示了如何使用 inet_ntoa 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <arpa/inet.h>

int main() {
struct in_addr addr;
addr.s_addr = inet_addr("192.168.1.1");

// 将32位整数形式的IP地址转换为点分十进制字符串
char *ip_str = inet_ntoa(addr);

if (ip_str == NULL) {
perror("inet_ntoa");
return 1;
}

printf("IP address in dotted-decimal format: %s\n", ip_str);

return 0;
}

在这个例子中,我们首先将一个点分十进制的IP地址字符串转换为32位整数形式,然后将其存储在 struct in_addr 结构中。

接着,我们使用 inet_ntoa 函数将这个32位整数形式的IP地址转换为点分十进制的字符串。

请注意,inet_ntoa 返回一个指向静态缓冲区的指针,因此在后续的调用中可能会被覆盖。

进程

fork (创建新进程)

简短描述

fork 函数用于创建一个新的进程,新进程是原进程的副本。

调用 fork 函数后,如果之前有套接字返回的描述符,会被复制,但是不会复制套接字。

函数原型
1
2
3
#include <unistd.h>

pid_t fork(void);
参数

该函数没有参数。

返回值
  • 在父进程中,返回新创建的子进程的PID(进程ID)。
  • 在子进程中,返回0。
  • 如果出现错误,返回 -1。
使用方法

以下是一个简单的例子,演示了如何使用 fork 函数创建一个新的进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid;

// 使用 fork 创建一个新的进程
pid = fork();

if (pid == -1) {
perror("fork");
return 1;
}

if (pid == 0) {
// 在子进程中
printf("Child process. PID: %d\n", getpid());
} else {
// 在父进程中
printf("Parent process. PID: %d, Child PID: %d\n", getpid(), pid);
}

return 0;
}

在这个例子中,我们调用了 fork 函数来创建一个新的进程。

在父进程中,fork 返回新创建的子进程的PID,而在子进程中,fork 返回0。

因此,我们可以通过判断返回值来确定当前是父进程还是子进程。

在父进程中,我们会得到子进程的PID,而在子进程中,我们得到的PID 就是0。

pipe (进程间通信)

简单描述

pipe 函数用于创建一个管道,使得一个进程的输出可以直接成为另一个进程的输入。

详细描述

pipe 函数创建一个管道,它是一个特殊的文件,可以用于进程间通信。它通常用于父子进程之间或者兄弟进程之间进行通信。

函数原型

1
2
3
#include <unistd.h>

int pipe(int filedes[2]);

函数参数

  • filedes:一个包含两个整数的数组,用于存储管道的文件描述符,filedes[0] 用于读取,filedes[1] 用于写入。

返回值

  • 成功:0
  • 失败:-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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
int fd[2];
char buffer[100];

// 创建管道
if (pipe(fd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}

// 在子进程中写入数据
if (fork() == 0) {
close(fd[0]); // 关闭读取端
write(fd[1], "Hello, pipe!", 13);
close(fd[1]); // 关闭写入端
exit(EXIT_SUCCESS);
} else { // 父进程
close(fd[1]); // 关闭写入端
read(fd[0], buffer, sizeof(buffer));
close(fd[0]); // 关闭读取端
printf("Received: %s\n", buffer);
}

return 0;
}

在上面的例子中,程序创建了一个管道,并通过 fork 创建了一个子进程。子进程关闭了读取端,写入了数据到管道,然后关闭了写入端。父进程关闭了写入端,读取了数据,然后关闭了读取端。这样,子进程写入的数据通过管道传递到了父进程中。

注意事项

  • 管道的容量是有限的,如果写入速度过快,可能会导致写入阻塞或者数据丢失。
  • 如果没有进程继续读取管道,写入操作可能会导致信号 SIGPIPE 被发送给写入进程,这可能导致程序异常终止。
  • 管道是单向的,如果需要双向通信,需要创建两个管道,分别用于不同方向的通信。

wait (等待子进程终止)

简短描述

wait 函数用于等待子进程的终止,并获取子进程的退出状态。

wait 函数的作用是:

  1. 父进程调用 wait 函数后会一直阻塞,直到它的一个子进程结束。
  2. 一旦子进程结束,wait 会返回被等待的子进程的进程号(PID)。
  3. 同时,子进程的退出状态会被保存在 status 中,以便父进程后续查询。
函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
参数
  • status:指向一个整数的指针,用于存储子进程的退出状态。

status 是一个整型指针,用于存储子进程的退出状态信息。可以通过一些宏来解析 status 中的信息,比如 WIFEXITED(status) 来检查子进程是否正常退出。

以下是一些常用的宏:

  • WIFEXITED(status):判断子进程是否正常退出。
  • WEXITSTATUS(status):获取子进程的返回值(只有在 WIFEXITED 返回非零时才有效)。
  • WIFSIGNALED(status):判断子进程是否因信号而终止。
  • WTERMSIG(status):获取导致子进程终止的信号编号(只有在 WIFSIGNALED 返回非零时才有效)。
返回值
  • 如果成功,返回终止的子进程的PID(进程ID)。
  • 如果出现错误,返回 -1。
使用方法

以下是一个简单的例子,演示了如何使用 wait 函数等待子进程的终止:

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
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
pid_t pid;
int status;

// 创建一个子进程
pid = fork();

if (pid == -1) {
perror("fork");
return 1;
}

if (pid == 0) {
// 在子进程中
printf("Child process. PID: %d\n", getpid());
exit(42); // 子进程退出,退出状态为42
} else {
// 在父进程中
printf("Parent process. PID: %d, Child PID: %d\n", getpid(), pid);

// 使用 wait 等待子进程的终止
pid_t terminated_pid = wait(&status);

if (terminated_pid == -1) {
perror("wait");
return 1;
}

// 通过宏分离
if (WIFEXITED(status)) {
printf("Child process %d exited with status %d\n", terminated_pid, WEXITSTATUS(status));
} else {
printf("Child process %d did not exit normally\n", terminated_pid);
}
}

return 0;
}

在这个例子中,我们首先调用了 fork 函数来创建一个新的进程。

在父进程中,fork 返回新创建的子进程的PID,而在子进程中,fork 返回0。

因此,我们可以通过判断返回值来确定当前是父进程还是子进程。

在子进程中,我们打印出子进程的PID,并调用 exit 函数来退出进程,同时指定退出状态为42。

在父进程中,我们打印出父进程和子进程的PID,并使用 wait 函数等待子进程的终止。

wait 函数会阻塞父进程,直到一个子进程终止为止。

一旦子进程终止,父进程将得到终止的子进程的PID,并将子进程的退出状态存储在 status 变量中。

我们使用 WIFEXITED 宏来检查子进程是否正常退出,如果是,我们可以通过 WEXITSTATUS 宏获取退出状态。

waitpid (等待指定子进程终止)

简短描述

waitpid 函数用于等待指定的子进程的终止,并获取子进程的退出状态。

函数原型
1
2
3
4
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
参数
  • pid:要等待的子进程的PID。可以使用以下值:
    • -1:等待任意子进程。
    • 0:等待与当前进程在同一个进程组的任意子进程。
    • 大于0的值:等待指定PID的子进程。
  • status:指向一个整数的指针,用于存储子进程的退出状态。
  • options:指定等待的行为选项,通常可以设置为0。
    • WNOHANG:非阻塞模式,即使没有子进程结束,也立即返回,返回0表示没有子进程退出。
    • WUNTRACED:也等待已经暂停的子进程,但不是终止的子进程。
返回值
  • 如果成功,返回终止的子进程的PID(进程ID)。
  • 如果出现错误,返回 -1。
使用方法

以下是一个简单的例子,演示了如何使用 waitpid 函数等待指定的子进程的终止:

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
pid_t child_pid;

if ((child_pid = fork()) == 0) {
// 子进程
printf("Child process running...\n");
sleep(2);
_exit(123); // 子进程退出,返回值为123
} else if (child_pid > 0) {
// 父进程
int status;
pid_t terminated_pid;

// 使用 WNOHANG 非阻塞模式等待子进程结束
do {
terminated_pid = waitpid(child_pid, &status, WNOHANG);
if (terminated_pid == 0) {
printf("Child is still running...\n");
sleep(1);
}
} while (terminated_pid == 0);

if (terminated_pid == child_pid) {
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child terminated by signal %d\n", WTERMSIG(status));
}
} else if (terminated_pid == -1) {
perror("waitpid");
}
} else {
perror("fork");
}

return 0;
}

在上述示例中,父进程创建了一个子进程,然后使用 waitpid 函数非阻塞地等待子进程结束。

一旦子进程终止,父进程将得到终止的子进程的PID,并将子进程的退出状态存储在 status 变量中。

我们使用 WIFEXITED 宏来检查子进程是否正常退出,如果是,我们可以通过 WEXITSTATUS 宏获取退出状态。

I/O 复用

select (监视多个文件描述符)

简单描述

select 函数用于在多个文件描述符上等待可读、可写或出错事件,从而实现非阻塞的 I/O 操作。

详细描述

select 函数允许程序同时监视多个文件描述符,当其中之一变得可读、可写或发生错误时,它将返回并允许程序执行相应的操作。

函数原型

1
2
3
4
#include <sys/select.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数参数

  • nfds:需要监视的文件描述符集合中的最大值(即最大的文件描述符值加一)。
  • readfds:包含所有你希望监视可读事件的文件描述符的集合。
  • writefds:包含所有你希望监视可写事件的文件描述符的集合。
  • exceptfds:包含所有你希望监视错误事件的文件描述符的集合。
  • timeout:一个指向 struct timeval 结构体的指针,用于设置 select 的超时时间。

image-20230910123906579

以下是 timeval 结构体的定义:

1
2
3
4
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};

接下来我将解释每个字段的含义:

  1. tv_sec
    • 类型:time_t
    • 用于存储秒数部分的时间值。它表示了从1970年1月1日00:00:00开始的秒数。
  2. tv_usec
    • 类型:suseconds_t
    • 用于存储微秒部分的时间值。它表示了秒数的小数部分,即一秒的百万分之一。

timeval 结构体通常用于测量时间间隔或者计时器。下面是一个简单的例子,演示如何使用 gettimeofday 函数来获取当前时间:

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
#include <stdio.h>
#include <sys/time.h>

int main() {
struct timeval start, end;
long elapsed;

// 获取起始时间
gettimeofday(&start, NULL);

// 模拟一些工作
for (int i = 0; i < 1000000; i++) {
// do some work
}

// 获取结束时间
gettimeofday(&end, NULL);

// 计算时间差
elapsed = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);

printf("Elapsed time: %ld microseconds\n", elapsed);

return 0;
}

在上述例子中,我们首先声明了两个 struct timeval 类型的变量 startend 来存储起始时间和结束时间。然后,我们使用 gettimeofday 函数来获取当前的时间戳,分别将其存储到 startend 中。接着,我们模拟了一些工作(这里使用了一个简单的循环),之后再次调用 gettimeofday 获取结束时间。

最后,我们计算了时间差,将其以微秒为单位打印出来。

请注意,在实际的程序中,可能会使用更高级的时间处理函数,例如 clock_gettime,它提供了更高的精度和更多的选项,特别是在需要精确计时的情况下。

返回值

  • 成功:就绪文件描述符的总数。超时返回 0。
  • 失败:-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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>

int main() {
fd_set readfds;
struct timeval timeout;

// 清空并设置文件描述符集合
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds); // 监视标准输入

// 设置超时时间为5秒
timeout.tv_sec = 5;
timeout.tv_usec = 0;

int result = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);

if (result == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (result == 0) {
printf("Timeout\n");
} else {
if (FD_ISSET(STDIN_FILENO, &readfds)) {
printf("Data is available\n");
}
}

return 0;
}

在上面的例子中,程序使用 select 监视标准输入,并设置了5秒的超时时间。如果在超时前标准输入变得可读,程序将输出 “Data is available”;如果超时时间到达仍然没有可读事件发生,程序将输出 “Timeout”。

注意事项

  • select 函数在监视的文件描述符集合上发生变化时返回,如果超时时间设置为0,它将成为一个非阻塞的 I/O 监视工具。
  • select 的效率可能会随着监视的文件描述符数量的增加而下降,因此对于大量文件描述符的情况,可能需要使用更高效的机制,如 epollkqueue

epoll_create (创建一个 epoll 实例监视多个fd上的事件)

简单描述

epoll_create 函数用于创建一个 epoll 实例,用于高效地监视多个文件描述符上的事件。

详细描述

epoll_create 函数创建一个 epoll 实例,它是一个事件通知机制,可以用于高效地监视多个文件描述符上的可读、可写、出错等事件。

函数原型

1
2
3
#include <sys/epoll.h>

int epoll_create(int size);

函数参数

  • size:在内核事件表中的最大监听事件数,但这个参数在新版 Linux 已经不再使用,传入一个大于 0 的值即可。

返回值

  • 成功:返回一个 epoll 实例的文件描述符。
  • 失败:返回-1,并设置 errno 来指示错误类型。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main() {
int epoll_fd = epoll_create(10); // 创建一个 epoll 实例,参数已经不再使用

if (epoll_fd == -1) {
perror("epoll_create");
return 1;
}

// 使用 epoll_fd 进行事件监视...

close(epoll_fd); // 关闭 epoll 实例

return 0;
}

在上面的例子中,程序使用 epoll_create 函数创建了一个 epoll 实例,并得到了一个文件描述符 epoll_fd。然后可以使用这个描述符进行事件监视。

注意事项

  • epoll_create 创建一个 epoll 实例,用于高效地监视多个文件描述符上的事件。
  • 在使用完 epoll 实例后,需要使用 close 函数关闭,释放相关资源。

epoll_ctl (增删改要监视的 epoll 实例中的事件)

简单描述

epoll_ctl 函数用于控制 epoll 实例中的事件。

详细描述

epoll_ctl 函数可以用于向 epoll 实例中添加、修改或删除监视的事件。

函数原型

1
2
3
#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

函数参数

  • epfd:epoll 实例的文件描述符。
  • op:操作类型,可以是以下之一:
    • EPOLL_CTL_ADD:将一个新的文件描述符添加到 epoll 实例中进行监视。
    • EPOLL_CTL_MOD:修改一个已经在 epoll 实例中的文件描述符的事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • fd:要操作的文件描述符。
  • event:一个指向 epoll_event 结构体的指针,用于指定要监视的事件类型。

epoll_ctl 函数中,your_fd 是要操作的文件描述符,而 event.data.fd 则是用于传递给 epoll 实例的文件描述符,以便在事件就绪时能够获取到相应的文件描述符。

epoll_event 结构体

epoll_event 结构体是用于注册事件和返回就绪事件的数据结构,它在使用 Linux 的 epoll I/O 多路复用机制时扮演着重要的角色。

以下是 epoll_event 结构体的定义:

1
2
3
4
struct epoll_event {
uint32_t events; // 表示关心的事件类型,如 EPOLLIN、EPOLLOUT 等
epoll_data_t data; // 用户数据,可以是一个指针或者一个文件描述符
};

接下来,我将详细解释 epoll_event 结构体的两个字段:

  1. events

    • 类型:uint32_t

    • 用于表示关心的事件类型,它可以是以下几个宏的组合:

      • EPOLLIN:表示可读事件,当套接字上有数据可读时触发。
      • EPOLLOUT:表示可写事件,当套接字可写时触发。
      • EPOLLRDHUP:表示对端关闭连接或者半关闭连接。
      • EPOLLPRI:表示有紧急数据可读。
      • EPOLLERR:表示发生错误。
      • EPOLLHUP:表示发生挂起事件。
      • EPOLLET:表示将事件设置为边缘触发模式(Edge Triggered)。
      • EPOLLONESHOT:表示将事件设置为一次性模式,即只触发一次,之后需要重新添加。

      image-20230910182254618

  2. data

    • 类型:epoll_data_t
    • 是一个联合体,可以包含用户自定义的数据,通常是一个指针或者一个文件描述符。
    1
    2
    3
    4
    5
    6
    typedef union epoll_data {
    void *ptr; // 指针
    int fd; // 文件描述符
    uint32_t u32; // 32 位无符号整数
    uint64_t u64; // 64 位无符号整数
    } epoll_data_t;

    通常情况下,如果我们希望在就绪事件时知道是哪个文件描述符发生了事件,我们会将相应的文件描述符放入 data.fd 中,同时可以在 events 中指定关心的事件类型。

使用 epoll_event 结构体时,通常会先将需要监听的事件添加到 epoll 实例中,然后等待 epoll_wait 函数返回已就绪的事件。

下面是一个简单的示例:

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
struct epoll_event event;
int epoll_fd = epoll_create1(0);

// 添加监听事件
event.events = EPOLLIN | EPOLLOUT;
event.data.fd = your_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, your_fd, &event);

// 等待就绪事件
struct epoll_event events[10];
int ready_fds = epoll_wait(epoll_fd, events, 10, -1);

// 处理就绪事件
for (int i = 0; i < ready_fds; i++) {
int fd = events[i].data.fd;
uint32_t ev = events[i].events;

// 根据事件类型做相应处理
if (ev & EPOLLIN) {
// 可读事件
}
if (ev & EPOLLOUT) {
// 可写事件
}
// ...
}

close(epoll_fd);

在上述示例中,我们创建了一个 epoll 实例 epoll_fd,并将需要监听的事件添加到其中。然后使用 epoll_wait 函数等待就绪事件,一旦事件就绪,就可以根据 events[i].data.fdevents[i].events 来处理相应的事件类型。

返回值

  • 成功:返回0。
  • 失败:返回-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
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main() {
int epoll_fd = epoll_create(10);

if (epoll_fd == -1) {
perror("epoll_create");
return 1;
}

struct epoll_event event;
event.events = EPOLLIN; // 监听可读事件
event.data.fd = STDIN_FILENO; // 监听标准输入

if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
return 1;
}

// 使用 epoll_fd 进行事件监视...

close(epoll_fd);

return 0;
}

在上面的例子中,程序首先创建了一个 epoll 实例并获得了一个文件描述符 epoll_fd。然后,通过初始化一个 epoll_event 结构体来指定要监视的事件类型,以及关联的文件描述符。接着使用 epoll_ctl 将标准输入文件描述符添加到 epoll 实例中进行监视。

注意事项

  • 使用 epoll_ctl 可以向 epoll 实例中添加、修改或删除监视的事件,可以在程序运行过程中动态地控制需要监视的文件描述符和事件类型。
  • 在使用完 epoll 实例后,需要使用 close 函数关闭,释放相关资源。

epoll_wait (等待fd中的事件)

简单描述

epoll_wait 函数用于等待文件描述符上的事件。

详细描述

epoll_wait 函数会阻塞程序的执行,直到指定的文件描述符上发生了指定的事件。

函数原型

1
2
3
#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

函数参数

  • epfd:epoll 实例的文件描述符。
  • events:一个指向 epoll_event 结构体数组的指针,用于存储发生的事件。
  • maxeventsevents 数组的最大长度,即最多能够存储多少个事件。
  • timeout:等待事件的超时时间,以毫秒为单位。传入 -1 表示一直等待,传入 0 表示立即返回,传入正整数表示等待指定的毫秒数。

返回值

  • 成功:返回发生的事件数量。
  • 失败:返回-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
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>

int main() {
int epoll_fd = epoll_create(10);

if (epoll_fd == -1) {
perror("epoll_create");
return 1;
}

struct epoll_event event;
event.events = EPOLLIN; // 监听可读事件
event.data.fd = STDIN_FILENO; // 监听标准输入

if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
return 1;
}

struct epoll_event events[10]; // 存储发生的事件
int event_count;

while (1) {
event_count = epoll_wait(epoll_fd, events, 10, -1);

if (event_count == -1) {
perror("epoll_wait");
break;
}

for (int i = 0; i < event_count; i++) {
if (events[i].data.fd == STDIN_FILENO) {
char buffer[1024];
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
if (n > 0) {
printf("Read %zd bytes: %s\n", n, buffer);
}
}
}
}

close(epoll_fd);

return 0;
}

在上面的例子中,程序创建了一个 epoll 实例,并将标准输入文件描述符添加到 epoll 实例中进行监视。然后使用 epoll_wait 函数等待事件的发生。当事件发生时,epoll_wait 函数会返回,并将发生的事件存储在 events 数组中。程序可以遍历 events 数组,处理发生的事件。

注意事项

  • 使用 epoll_wait 函数可以等待指定的文件描述符上发生指定的事件,并在事件发生时返回,以便程序进行相应的处理。
  • 在使用完 epoll 实例后,需要使用 close 函数关闭,释放相关资源。

线程和信号处理

pthread_create (创建一个新线程)

简短描述

pthread_create 函数用于创建一个新的线程。

函数原型
1
2
3
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数
  • thread:一个指向 pthread_t 结构的指针,用于存储新线程的标识符。
  • attr:一个指向 pthread_attr_t 结构的指针,指定线程的属性。通常情况下可以设置为 NULL,使用默认属性。
  • start_routine:一个指向线程函数的指针,即新线程将要执行的函数。
  • arg:传递给 start_routine 函数的参数。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回一个正的错误代码。
使用方法

以下是一个简单的例子,演示了如何使用 pthread_create 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <pthread.h>

void* thread_function(void* arg) {
printf("Hello from the new thread!\n");
return NULL;
}

int main() {
pthread_t thread;

// 创建一个新线程,并调用 thread_function
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

// 等待新线程结束
pthread_join(thread, NULL);

printf("New thread has finished.\n");

return 0;
}

在这个例子中,我们首先定义了一个新线程将要执行的函数 thread_function

main 函数中,我们创建了一个新线程,并将 thread_function 作为线程的入口函数。

然后,我们使用 pthread_join 函数等待新线程结束,以确保在主线程中正确等待新线程的执行。

注意事项
  • pthread_create 创建一个新线程,并将其加入到调用进程的线程组中。
  • 使用 pthread_join 可以等待线程结束,以确保主线程在子线程结束后才会继续执行。

pthread_join (等待一个线程结束)

简单描述

pthread_join 函数用于等待一个特定线程的结束。

详细描述

pthread_join 函数会阻塞调用线程(一般是主线程),直到指定的线程结束为止。

函数原型

1
2
3
#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

函数参数

  • thread:要等待的线程的ID。
  • retval:一个指向指针的指针,用于存储被等待线程的返回值。

返回值

  • 成功:返回0,表示线程等待成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

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
#include <stdio.h>
#include <pthread.h>

void *print_message(void *ptr) {
char *message = (char *)ptr;
printf("%s\n", message);
return (void *)42;
}

int main() {
pthread_t thread_id;

const char *message = "Hello, pthread_join!";

int result = pthread_create(&thread_id, NULL, print_message, (void *)message);

if (result) {
printf("Error: pthread_create failed, return code: %d\n", result);
return 1;
}

void *ret_value;
pthread_join(thread_id, &ret_value); // 等待线程结束

printf("Thread returned: %ld\n", (long)ret_value);

return 0;
}

在上面的例子中,程序创建了一个新的线程,将其执行函数设为 print_message,并传递了一个字符串作为参数。新线程将从 print_message 函数开始执行。

main 函数中,调用了 pthread_join 函数来等待线程结束,同时获取了线程的返回值。

注意事项

  • pthread_join 用于等待一个特定线程的结束,同时可以获取线程的返回值。
  • 如果不关心线程的返回值,可以将 retval 参数设置为 NULL
  • 使用 pthread_join 时,调用线程会被阻塞,直到指定的线程结束为止。

pthread_detach (将线程标记为分离状态)

简短描述

pthread_detach 函数用于将一个线程标记为分离状态,使得线程在退出时能够自动清理资源。后台运行,终止后则清除资源。

函数原型
1
2
3
#include <pthread.h>

int pthread_detach(pthread_t thread);
参数
  • thread:目标线程的标识符。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回错误代码。
使用方法

以下是一个简单的例子,演示了如何使用 pthread_detach 函数:

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
#include <stdio.h>
#include <pthread.h>

void* thread_function(void* arg) {
printf("Hello from the detached thread!\n");
return NULL;
}

int main() {
pthread_t thread;

// 创建一个新线程,并将其标记为分离状态
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

// 将线程标记为分离状态
if (pthread_detach(thread) != 0) {
perror("pthread_detach");
return 1;
}

// 主线程不会等待新线程结束

printf("Main thread continues...\n");

return 0;
}

在这个例子中,我们首先创建了一个新的线程,并将其标记为分离状态,使得新线程在退出时能够自动清理资源。

新线程将执行 thread_function 函数,然后自动退出。

主线程不会等待新线程结束,而是会继续执行。

请注意,一旦线程被标记为分离状态,就不能再使用 pthread_join 函数等待它的结束了。

pthread_exit (终止调用的线程)

简短描述

pthread_exit 函数用于终止调用线程的执行,并返回一个指定的值。

函数原型
1
2
3
#include <pthread.h>

void pthread_exit(void *value_ptr);
参数
  • value_ptr:线程的返回值,可以是任意类型的指针。
返回值
  • 无返回值。
使用方法

以下是一个简单的例子,演示了如何使用 pthread_exit 函数:

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
#include <stdio.h>
#include <pthread.h>

void* thread_function(void* arg) {
printf("Hello from the new thread!\n");

// 退出线程,并返回一个值
pthread_exit((void*)42);
}

int main() {
pthread_t thread;
void* exit_value;

// 创建一个新线程,并调用 thread_function
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

// 等待新线程结束,并获取其返回值
if (pthread_join(thread, &exit_value) != 0) {
perror("pthread_join");
return 1;
}

printf("New thread has finished with exit value: %ld\n", (long)exit_value);

return 0;
}

在这个例子中,我们首先定义了一个新线程将要执行的函数 thread_function

main 函数中,我们创建了一个新线程,并将 thread_function 作为线程的入口函数。

thread_function 中,我们使用 pthread_exit 函数退出线程,并返回一个值(这里是 42)。

在主线程中,我们使用 pthread_join 函数等待新线程结束,并获取其返回值。

pthread_mutex_init (线程同步,初始化互斥锁)

简单描述

pthread_mutex_init 函数用于初始化互斥锁(Mutex)。

详细描述

互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_init 函数用于创建并初始化互斥锁,使其可用于线程间的同步。

函数原型

1
2
3
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

函数参数

  • mutex:指向 pthread_mutex_t 类型的互斥锁对象的指针,用于存储初始化后的互斥锁。
  • attr:指向 pthread_mutexattr_t 类型的属性对象的指针,用于设置互斥锁的属性,通常传入 NULL 表示使用默认属性。

返回值

  • 成功:返回0,表示互斥锁初始化成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

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 <pthread.h>

void *thread_function(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;

pthread_mutex_lock(mutex); // 上锁
printf("Thread locked the mutex.\n");

// 执行一些临界区代码

pthread_mutex_unlock(mutex); // 解锁
printf("Thread unlocked the mutex.\n");

return NULL;
}

int main() {
pthread_t thread;
pthread_mutex_t mutex;

if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init");
return 1;
}

if (pthread_create(&thread, NULL, thread_function, &mutex) != 0) {
perror("pthread_create");
return 1;
}

// 主线程也可以使用互斥锁

pthread_mutex_lock(&mutex);
printf("Main thread locked the mutex.\n");

// 执行一些临界区代码

pthread_mutex_unlock(&mutex);
printf("Main thread unlocked the mutex.\n");

if (pthread_join(thread, NULL) != 0) {
perror("pthread_join");
return 1;
}

if (pthread_mutex_destroy(&mutex) != 0) {
perror("pthread_mutex_destroy");
return 1;
}

return 0;
}

在上面的例子中,程序使用 pthread_mutex_init 初始化了一个互斥锁,并在主线程和子线程中使用该互斥锁来保护临界区,确保多个线程不会同时访问临界区。最后,使用 pthread_mutex_destroy 销毁了互斥锁。

注意事项

  • 互斥锁是一种常用的线程同步工具,用于保护共享资源,防止多个线程同时访问。
  • 在使用完互斥锁后,应使用 pthread_mutex_destroy 函数来销毁互斥锁,以释放相关资源。

pthread_mutex_destroy (销毁互斥锁)

简单描述

pthread_mutex_destroy 函数用于销毁互斥锁。

详细描述

互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_destroy 函数用于销毁一个已经初始化的互斥锁,释放相关资源。

函数原型

1
2
3
#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

函数参数

  • mutex:指向 pthread_mutex_t 类型的互斥锁对象的指针。

返回值

  • 成功:返回0,表示互斥锁销毁成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <pthread.h>

int main() {
pthread_mutex_t mutex;

if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init");
return 1;
}

// 使用互斥锁...

if (pthread_mutex_destroy(&mutex) != 0) {
perror("pthread_mutex_destroy");
return 1;
}

return 0;
}

在上面的例子中,程序使用 pthread_mutex_init 初始化了一个互斥锁,然后在使用完互斥锁后,通过调用 pthread_mutex_destroy 销毁了它,释放相关资源。

注意事项

  • 使用 pthread_mutex_destroy 可以销毁一个已经初始化的互斥锁,释放相关资源。
  • 在销毁互斥锁之前,要确保没有线程在使用它。否则,会导致未定义的行为。

pthread_mutex_lock (锁定互斥锁)

简单描述

pthread_mutex_lock 函数用于锁定互斥锁。

详细描述

互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_lock 函数用于锁定一个互斥锁,以确保只有一个线程能够进入临界区。

函数原型

1
2
3
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

函数参数

  • mutex:指向 pthread_mutex_t 类型的互斥锁对象的指针。

返回值

  • 成功:返回0,表示互斥锁成功锁定。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

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
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
pthread_mutex_lock(&mutex); // 锁定互斥锁

// 执行一些临界区代码

pthread_mutex_unlock(&mutex); // 解锁互斥锁

return NULL;
}

int main() {
pthread_t thread1, thread2;

if (pthread_create(&thread1, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

if (pthread_create(&thread2, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

if (pthread_join(thread1, NULL) != 0) {
perror("pthread_join");
return 1;
}

if (pthread_join(thread2, NULL) != 0) {
perror("pthread_join");
return 1;
}

return 0;
}

在上面的例子中,程序创建了两个线程,它们会同时尝试锁定同一个互斥锁。因为互斥锁只允许一个线程进入临界区,所以一个线程会成功锁定,另一个线程会在 pthread_mutex_lock 处阻塞,直到第一个线程解锁互斥锁。

注意事项

  • 使用 pthread_mutex_lock 可以锁定一个互斥锁,以确保只有一个线程能够进入临界区。
  • 使用完互斥锁后,应该使用 pthread_mutex_unlock 函数来解锁互斥锁,以便其他线程可以进入临界区。

pthread_mutex_unlock (解锁互斥锁)

简单描述

pthread_mutex_unlock 函数用于解锁互斥锁。

详细描述

互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_unlock 函数用于解锁一个互斥锁,以释放对临界区的控制,使其他线程可以访问共享资源。

函数原型

1
2
3
#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

函数参数

  • mutex:指向 pthread_mutex_t 类型的互斥锁对象的指针。

返回值

  • 成功:返回0,表示互斥锁成功解锁。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

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
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
pthread_mutex_lock(&mutex); // 锁定互斥锁

// 执行一些临界区代码

pthread_mutex_unlock(&mutex); // 解锁互斥锁

return NULL;
}

int main() {
pthread_t thread1, thread2;

if (pthread_create(&thread1, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

if (pthread_create(&thread2, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}

if (pthread_join(thread1, NULL) != 0) {
perror("pthread_join");
return 1;
}

if (pthread_join(thread2, NULL) != 0) {
perror("pthread_join");
return 1;
}

return 0;
}

在上面的例子中,程序创建了两个线程,它们会同时尝试锁定同一个互斥锁。一个线程成功锁定后,执行临界区代码,然后解锁互斥锁,使得另一个线程可以访问临界区。

注意事项

  • 使用 pthread_mutex_unlock 可以解锁一个互斥锁,以释放对临界区的控制,使其他线程可以访问共享资源。
  • 使用互斥锁时,必须遵守正确的加锁和解锁顺序,以避免死锁等问题。

sem_init (初始化一个信号量)

简单描述

sem_init 函数用于初始化一个信号量。

详细描述

信号量是一种用于线程间或进程间同步的机制,sem_init 用于初始化一个新的信号量,为其分配资源。

函数原型

1
2
3
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

函数参数

  • sem:指向 sem_t 类型的信号量对象的指针,用于存储初始化后的信号量。
  • pshared:指示信号量是在进程间共享还是在线程间共享的参数,通常传入 0 表示在线程间共享(也就是指在一个进程内部共享)。
  • value:信号量的初始值。

返回值

  • 成功:返回0,表示信号量初始化成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <semaphore.h>

int main() {
sem_t my_semaphore;

if (sem_init(&my_semaphore, 0, 1) != 0) {
perror("sem_init");
return 1;
}

// 使用信号量...

if (sem_destroy(&my_semaphore) != 0) {
perror("sem_destroy");
return 1;
}

return 0;
}

在上面的例子中,程序使用 sem_init 初始化了一个信号量,并在程序中使用了它。最后,使用 sem_destroy 销毁了信号量。

注意事项

  • sem_init 用于初始化一个新的信号量,为其分配资源。
  • 在使用完信号量后,应该使用 sem_destroy 函数来销毁信号量,以释放相关资源。

sem_destroy (销毁一个信号量)

简单描述

sem_destroy 函数用于销毁一个信号量。

详细描述

信号量是一种用于线程间或进程间同步的机制,sem_destroy 用于销毁一个已经初始化的信号量,释放相关资源。

函数原型

1
2
3
#include <semaphore.h>

int sem_destroy(sem_t *sem);

函数参数

  • sem:指向 sem_t 类型的信号量对象的指针。

返回值

  • 成功:返回0,表示信号量销毁成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <semaphore.h>

int main() {
sem_t my_semaphore;

if (sem_init(&my_semaphore, 0, 1) != 0) {
perror("sem_init");
return 1;
}

// 使用信号量...

if (sem_destroy(&my_semaphore) != 0) {
perror("sem_destroy");
return 1;
}

return 0;
}

在上面的例子中,程序使用 sem_init 初始化了一个信号量,并在程序中使用了它。最后,使用 sem_destroy 销毁了信号量。

注意事项

  • sem_destroy 用于销毁一个已经初始化的信号量,释放相关资源。
  • 在销毁信号量之前,要确保没有线程在使用它。否则,会导致未定义的行为。

sem_post (信号量加1)

简单描述

sem_post 函数用于增加信号量的值。

详细描述

信号量是一种用于线程间或进程间同步的机制,sem_post 用于增加信号量的值,表示某个资源变得可用。

函数原型

1
2
3
#include <semaphore.h>

int sem_post(sem_t *sem);

函数参数

  • sem:指向 sem_t 类型的信号量对象的指针。

返回值

  • 成功:返回0,表示操作成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <semaphore.h>

int main() {
sem_t my_semaphore;

if (sem_init(&my_semaphore, 0, 0) != 0) {
perror("sem_init");
return 1;
}

// 使用信号量...

if (sem_post(&my_semaphore) != 0) {
perror("sem_post");
return 1;
}

return 0;
}

在上面的例子中,程序使用 sem_init 初始化了一个信号量,初始值为0。然后使用 sem_post 增加了信号量的值,表示某个资源变得可用。

注意事项

  • sem_post 用于增加信号量的值,表示某个资源变得可用。
  • 在使用信号量时,需要注意控制好资源的可用性,以确保在正确的时机调用 sem_post 函数。

sem_wait (信号量大于0则减1)

简单描述

sem_wait 函数用于等待信号量的值大于0,然后将其减少1。

详细描述

信号量是一种用于线程间或进程间同步的机制,sem_wait 用于等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。

函数原型

1
2
3
#include <semaphore.h>

int sem_wait(sem_t *sem);

函数参数

  • sem:指向 sem_t 类型的信号量对象的指针。

返回值

  • 成功:返回0,表示操作成功。
  • 失败:返回一个非零的错误码,表示出现了错误。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <semaphore.h>

int main() {
sem_t my_semaphore;

if (sem_init(&my_semaphore, 0, 1) != 0) {
perror("sem_init");
return 1;
}

// 使用信号量...

if (sem_wait(&my_semaphore) != 0) {
perror("sem_wait");
return 1;
}

return 0;
}

在上面的例子中,程序使用 sem_init 初始化了一个信号量,初始值为1。然后使用 sem_wait 函数等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。

注意事项

  • sem_wait 用于等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。
  • 在使用信号量时,需要注意控制好资源的可用性,以确保在正确的时机调用 sem_wait 函数。

signal (绑定特定信号到行为)

简短描述

signal 函数用于设置信号处理函数,定义了程序在接收到特定信号时的行为。

函数原型
1
2
3
#include <signal.h>

void (*signal(int signum, void (*handler)(int)))(int);
参数
  • signum:要设置处理函数的信号编号。
  • handler:指向处理函数的指针。可以设置为 SIG_DFL(默认行为)或 SIG_IGN(忽略信号),也可以是用户自定义的处理函数。
信号
信号名 说明
SIGABRT 程序异常终止
SIGFPE 算术运算错误
SIGILL 非法指令
SIGINT 终端中断符,类似于Ctrl+C
SIGSEGV 无效的内存引用
SIGTERM 终止请求发送到程序
SIGHUP 进程终端或控制进程终止
SIGQUIT 键盘的退出键被按下
SIGALRM 定时器实现(alarm函数超时)
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
SIGCHLD 子进程状态发生变化或终止
SIGCONT 恢复被停止的进程
SIGSTOP 停止进程
SIGTSTP 终端停止信号,类似于Ctrl+Z
SIGTTIN 后台进程请求输入
SIGTTOU 后台进程请求输出
SIGBUS 总线错误
SIGPIPE 向一个没有读者的管道写数据
SIGPOLL 对一个pollable设备进行非阻塞操作(如管道、套接字)的非阻塞操作
SIGPROF 以CPU时间测量的定时器超时
SIGSYS 非法系统调用
SIGTRAP 由断点指令或其他机器特定的陷阱指令引起的调试事件
SIGURG 套接字上接收到紧急情况
SIGVTALRM 虚拟定时器超时
SIGXCPU CPU时间限制超时
SIGXFSZ 文件大小限制超出
SIGIO 通常用于通知程序,特别是在非阻塞 I/O 操作中,数据已经准备好被读取或写入。fcntl 函数F_SETOWN
返回值
  • 如果成功,返回先前与该信号相关联的处理函数的指针。
  • 如果出现错误,返回 SIG_ERR
使用方法

以下是一个简单的例子,演示了如何使用 signal 函数设置信号处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 自定义的信号处理函数
void signal_handler(int signum) {
printf("Received signal %d\n", signum);
}

int main() {
// 设置信号处理函数
signal(SIGINT, signal_handler);

printf("Press Ctrl+C to send SIGINT signal\n");

// 进入无限循环,等待信号
while(1) {
sleep(1);
}

return 0;
}

在这个例子中,我们首先定义了一个名为 signal_handler 的自定义信号处理函数。

然后,我们使用 signal 函数来将 SIGINT 信号(由 Ctrl+C 发送)与该处理函数关联起来。

接着,我们进入一个无限循环,等待信号的到来。

当我们在终端中按下 Ctrl+C 时,会发送 SIGINT 信号,导致程序调用了我们定义的信号处理函数。

信号处理函数会打印出接收到的信号编号。

sigaction (处理特定信号函数)

简单描述

sigaction 函数用于设置对信号的处理方式,可以指定信号处理函数或者忽略信号。

详细描述

sigaction 函数允许程序员指定在接收到特定信号时应采取的操作。它允许程序员为每个信号指定一个处理函数,或者忽略信号,或者使用默认的处理方式。

函数原型

1
2
3
#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

函数参数

  • signum:要处理的信号的编号。
  • act:新的信号处理方式,是一个 struct sigaction 结构体指针,其中包含了信号处理函数等信息。
  • oldact:用于存储之前的信号处理方式的结构体指针,可以为 NULL

下面是 sigaction 结构体的定义:

1
2
3
4
5
6
7
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

接下来我将解释每个字段的含义:

  1. sa_handler:
    • 类型:void (*)(int)
    • 这是一个函数指针,用于指定当接收到信号时要执行的处理函数。通常情况下,它会接收一个整数作为参数,表示接收到的信号编号。
  2. sa_sigaction:
    • 类型:void (*)(int, siginfo_t *, void *)
    • 这是一个备用的信号处理函数。如果设置了该函数,它将会覆盖 sa_handler。它接收三个参数,分别是接收到的信号编号、一个指向 siginfo_t 结构体的指针(其中包含了关于信号的额外信息)、以及一个 ucontext_t 结构体指针,它包含了当前线程的上下文信息。
  3. sa_mask:
    • 类型:sigset_t
    • 这是一个信号集,它指定了在执行信号处理函数时需要被屏蔽的信号。在信号处理函数执行期间,这些被屏蔽的信号将会被暂时阻塞,以防止它们再次被触发。
  4. sa_flags:
    • 类型:int
    • 用于指定一些标志来影响信号处理的行为,例如 SA_RESTART 可以在系统调用被信号中断后自动重启。
  5. sa_restorer:
    • 类型:void (*)(void)
    • 这是一个已弃用的字段,一般情况下不需要设置。

返回值

  • 成功:0
  • 失败:-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
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void signal_handler(int signo) {
printf("Received signal %d\n", signo);
}

int main() {
struct sigaction new_action, old_action;

// 设置信号处理函数
new_action.sa_handler = signal_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;

// 安装信号处理函数
if (sigaction(SIGINT, &new_action, &old_action) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

// 无限循环等待信号
while (1) {}

return 0;
}

在上面的例子中,程序安装了一个处理 SIGINT 信号的处理函数 signal_handler。当程序接收到 SIGINT 信号时,将调用 signal_handler 函数来处理。请注意,signal_handler 可以是任何你定义的函数来处理信号。

注意事项

  • SIGKILLSIGSTOP 信号不能被捕获或者忽略。
  • 在信号处理函数中,尽量避免使用不可重入的函数,因为信号可能会在任何时候中断程序的正常执行。

sigaddset (添加信号到信号集)

简短描述

sigaddset 函数用于将指定的信号添加到信号集中。

函数原型
1
2
3
#include <signal.h>

int sigaddset(sigset_t *set, int signum);
参数
  • set:指向目标信号集的指针。
  • signum:要添加到信号集中的信号编号。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 sigaddset 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <signal.h>

int main() {
sigset_t set;

// 初始化一个空的信号集
sigemptyset(&set);

// 将 SIGINT 添加到信号集中
if (sigaddset(&set, SIGINT) != 0) {
perror("sigaddset");
return 1;
}

printf("SIGINT added to signal set.\n");

return 0;
}

在这个例子中,我们首先创建了一个 sigset_t 类型的变量 set

然后,我们使用 sigemptyset 函数将 set 初始化为空的信号集。

接着,我们使用 sigaddset 函数将 SIGINT 信号添加到 set 中。

sigemptyset (初始化空的信号集)

简短描述

sigemptyset 函数用于初始化一个空的信号集。

函数原型
1
2
3
#include <signal.h>

int sigemptyset(sigset_t *set);
参数
  • set:指向要初始化的信号集的指针。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回 -1,并设置 errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 sigemptyset 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <signal.h>

int main() {
sigset_t set;

// 初始化一个空的信号集
if (sigemptyset(&set) != 0) {
perror("sigemptyset");
return 1;
}

printf("Signal set initialized.\n");

return 0;
}

在这个例子中,我们首先创建了一个 sigset_t 类型的变量 set

然后,我们使用 sigemptyset 函数将 set 初始化为空的信号集。

pthread_sigmask (操作信号屏蔽集)

简短描述

pthread_sigmask 函数用于检查和修改线程的信号屏蔽集(针对于当前线程)。

函数原型
1
2
3
#include <signal.h>

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
参数
  • how:操作模式,可以是以下值之一:
    • SIG_BLOCK:将 set 中的信号添加到当前信号屏蔽集中。
    • SIG_UNBLOCK:从当前信号屏蔽集中移除 set 中的信号。
    • SIG_SETMASK:用 set 中的信号集替换当前信号屏蔽集。
  • set:指向要操作的信号集的指针。
  • oldset:如果不为 NULL,将存储先前的信号屏蔽集。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回一个正的错误代码。
使用方法

以下是一个简单的例子,演示了如何使用 pthread_sigmask 函数:

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
#include <stdio.h>
#include <signal.h>
#include <pthread.h>

void* thread_function(void* arg) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);

// 将 SIGINT 添加到线程的信号屏蔽集中
pthread_sigmask(SIG_BLOCK, &set, NULL);

printf("Thread is now blocking SIGINT\n");

// 在此之后,线程将不会响应 SIGINT 信号

return NULL;
}

int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);

// 在主线程中,可以响应 SIGINT 信号

pthread_join(thread, NULL);

return 0;
}

在这个例子中,我们首先创建了一个新的线程,并在该线程中使用 pthread_sigmaskSIGINT 信号添加到线程的信号屏蔽集中。

这样,该线程将不会响应 SIGINT 信号。

在主线程中,我们没有修改信号屏蔽集,所以主线程可以响应 SIGINT 信号。

sigwait (等待指定信号)

简短描述

sigwait 函数用于等待指定的信号。

POSIX 标准建议在调用 sigwait 等待信号前,进程中所有线程都应屏蔽该信号,以保证只有 sigwait 的调用者获得该信号。

如果不屏蔽的话,在调用 sigwait 之前调用 pthread_kill 会出现 User defined signal 1.

函数原型
1
2
3
#include <signal.h>

int sigwait(const sigset_t *set, int *sig);
参数
  • set:指向信号集的指针,指定要等待的信号。
  • sig:如果成功,将包含接收到的信号编号。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回错误代码。
使用方法

以下是一个简单的例子,演示了如何使用 sigwait 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <signal.h>

int main() {
sigset_t set;
int sig;

// 初始化信号集并将 SIGINT 添加到其中
sigemptyset(&set);
sigaddset(&set, SIGINT);

printf("Waiting for SIGINT...\n");

// 等待信号
if (sigwait(&set, &sig) != 0) {
perror("sigwait");
return 1;
}

printf("Received signal: %d\n", sig);

return 0;
}

在这个例子中,我们首先创建了一个 sigset_t 类型的变量 set

然后,我们使用 sigemptyset 函数将 set 初始化为空的信号集,并使用 sigaddset 函数将 SIGINT 信号添加到 set 中。

接着,我们调用 sigwait 函数等待信号的到来。

一旦收到信号,sig 将包含信号的编号,然后我们可以进行相应的处理。

pthread_kill 函数

简短描述

pthread_kill 函数用于向指定的线程发送信号。

函数原型
1
2
3
#include <signal.h>

int pthread_kill(pthread_t thread, int sig);
参数
  • thread:目标线程的标识符。
  • sig:要发送的信号编号。
返回值
  • 如果成功,返回 0。
  • 如果出现错误,返回错误代码。
使用方法

以下是一个简单的例子,演示了如何使用 pthread_kill 函数:

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
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

void* thread_function(void* arg) {
while (1) {
printf("Thread is running...\n");
sleep(1);
}

return NULL;
}

int main() {
pthread_t thread;

// 创建一个新线程
pthread_create(&thread, NULL, thread_function, NULL);

// 在主线程中发送信号给新线程
if (pthread_kill(thread, SIGINT) != 0) {
perror("pthread_kill");
return 1;
}

pthread_join(thread, NULL);

return 0;
}

在这个例子中,我们首先创建了一个新的线程,并在该线程中循环打印一条消息。

在主线程中,我们使用 pthread_kill 函数向新线程发送了 SIGINT 信号。

这样,新线程会收到信号并相应地做出处理(在这个例子中,会终止线程)。

请注意,pthread_kill 函数用于向线程发送信号,而不是进程。如果要向进程发送信号,可以使用 kill 函数。

其它函数

gethostbyname (通过主机名获取主机的信息)

简短描述

gethostbyname 函数用于通过主机名获取主机的信息,包括 IP 地址。

注意: gethostbyname 是一个旧的函数,不推荐使用。更现代的函数是 getaddrinfo

函数原型
1
2
3
#include <netdb.h>

struct hostent *gethostbyname(const char *name);
参数
  • name:要查询的主机名。
返回值
  • 如果成功,返回指向 struct hostent 结构的指针,其中包含了与主机名相关的信息,包括 IP 地址。
  • 如果出现错误,返回 NULL,并设置 h_errno 来指示错误类型。
struct hostent

hostent 结构体是用于存储主机信息的数据结构,通常用于获取主机的域名解析信息。它在网络编程中很常用。

以下是 hostent 结构体的定义:

1
2
3
4
5
6
7
struct hostent {
char *h_name; // 主机名
char **h_aliases; // 主机的别名列表,以NULL结尾的字符串数组
int h_addrtype; // 地址类型,通常是 AF_INET 或 AF_INET6
int h_length; // 地址的字节长度
char **h_addr_list; // 指向一个指针数组,数组中的每个指针都指向一个地址(二进制形式)
};

下面是对 hostent 结构体成员的详细解释:

  1. char *h_name
    • 这是主机的正式域名。通常情况下,它是主机的正式域名,例如 “www.example.com“。
  2. char **h_aliases
    • 这是一个指向指针数组的指针,指向一个主机的别名列表。通常情况下,它包括了其他可能被用来指代相同主机的域名。列表的末尾会以一个空指针(NULL)作为结束标志。
  3. int h_addrtype
    • 这是地址的类型,通常是 AF_INET(IPv4)或 AF_INET6(IPv6)。
  4. int h_length
    • 这是地址的字节长度。对于IPv4地址,通常是4,对于IPv6地址,通常是16。
  5. char **h_addr_list
    • 这是一个指向指针数组的指针,数组中的每个指针都指向一个主机地址(以二进制形式)。通常,它指向一个或多个IPv4或IPv6地址。

注意

  • h_addr_list 中的地址以二进制形式存储。要将其转换为可读的点分十进制字符串,可以使用 inet_ntoainet_ntop 函数。
  • 在实际编程中,通常会使用函数如 gethostbynamegetaddrinfo 来填充并返回一个 hostent 结构体。这样可以根据域名获取主机的相关信息。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct hostent *host = gethostbyname("www.example.com");
if (host != NULL) {
printf("Official Name: %s\n", host->h_name);

char **alias = host->h_aliases;
while (*alias != NULL) {
printf("Alias: %s\n", *alias);
alias++;
}

printf("Address Type: %d\n", host->h_addrtype);
printf("Address Length: %d\n", host->h_length);

char **address = host->h_addr_list;
while (*address != NULL) {
char *ip = inet_ntoa(*(struct in_addr*)*address);
printf("IP Address: %s\n", ip);
address++;
}
}

这个示例演示了如何使用 gethostbyname 函数获取主机信息并访问 hostent 结构体的各个成员。

h_errno

h_errno 是一个全局的整型变量,通常用于存储与网络主机操作相关的错误码。它是在标准C库 <netdb.h> 中定义的一个外部变量。

当网络相关的操作(比如域名解析)发生错误时,系统会将相应的错误码设置到 h_errno 中,以便程序员可以检查错误类型并采取相应的措施。

以下是一些可能的错误码:

  • HOST_NOT_FOUND:找不到主机。
  • NO_DATA:主机存在,但无相应的记录(例如,找不到IPv6记录)。
  • NO_RECOVERY:非可恢复错误。
  • TRY_AGAIN:可恢复错误,通常表示需要重试操作。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <netdb.h>

int main() {
struct hostent *host = gethostbyname("nonexistent.example.com");

if (host == NULL) {
if (h_errno == HOST_NOT_FOUND) {
printf("Host not found\n");
} else if (h_errno == NO_DATA) {
printf("No data record found\n");
} else if (h_errno == NO_RECOVERY) {
printf("Non-recoverable error\n");
} else if (h_errno == TRY_AGAIN) {
printf("Temporary error; please try again later\n");
} else {
printf("Unknown error\n");
}
}

return 0;
}

在上述示例中,如果 gethostbyname 函数返回空指针,我们检查 h_errno 的值以确定出现了什么类型的错误,并输出相应的错误信息。

使用方法

以下是一个简单的例子,演示了如何使用 gethostbyname 函数获取主机的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <netdb.h>

int main() {
const char *hostname = "www.example.com";
struct hostent *host_info;

// 使用 gethostbyname 获取主机信息
host_info = gethostbyname(hostname);

if (host_info == NULL) {
herror("gethostbyname");
return 1;
}

printf("Official name: %s\n", host_info->h_name);
printf("IP Address: %s\n", inet_ntoa(*(struct in_addr *)host_info->h_addr_list[0]));

return 0;
}

在这个例子中,我们首先指定了要查询的主机名为 “www.example.com“。

然后,我们使用 gethostbyname 函数来获取与该主机名相关的信息。

如果成功,gethostbyname 将返回一个指向 struct hostent 结构的指针,其中包含了主机的各种信息。

我们通过 h_name 字段获取官方名称,通过 h_addr_list 数组获取IP地址信息,并使用 inet_ntoa 函数将其转换为字符串格式。

getaddrinfo (将主机名和服务名解析为一个或多个套接字地址,替代gethostbyname)

简短描述

getaddrinfo 函数用于将主机名和服务名解析为一个或多个套接字地址。

函数原型
1
2
3
4
5
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
参数
  • node:要解析的主机名或IP地址字符串。
  • service:要解析的服务名或端口号字符串。
  • hints:一个 struct addrinfo 结构的指针,用于指定解析的选项,可以设置为 NULL 使用默认选项。
  • res:一个指向 struct addrinfo* 的指针,用于存储解析结果。
返回值
  • 如果成功,返回0。
  • 如果出现错误,返回非零值,可以使用 gai_strerror 函数将错误码转换为字符串。
使用方法

以下是一个简单的例子,演示了如何使用 getaddrinfo 函数来解析主机名和服务名:

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
#include <stdio.h>
#include <netdb.h>

int main() {
const char *hostname = "www.example.com";
const char *service = "http";

struct addrinfo hints;
struct addrinfo *result, *rp;

// 设置 hints 结构
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // 允许 IPv4 或 IPv6
hints.ai_socktype = SOCK_STREAM; // 流套接字

// 使用 getaddrinfo 解析主机名和服务名
int status = getaddrinfo(hostname, service, &hints, &result);

if (status != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 1;
}

// 遍历解析结果
for (rp = result; rp != NULL; rp = rp->ai_next) {
char host[NI_MAXHOST], port[NI_MAXSERV];

// 获取主机名和端口号
getnameinfo(rp->ai_addr, rp->ai_addrlen, host, NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV);

printf("Host: %s, Port: %s\n", host, port);
}

// 释放解析结果的内存
freeaddrinfo(result);

return 0;
}

在这个例子中,我们首先指定了要解析的主机名和服务名。

然后,我们定义了一个 struct addrinfo 结构的变量 hints,并设置了一些解析选项,例如允许 IPv4 或 IPv6、使用流套接字等。

接着,我们使用 getaddrinfo 函数来解析主机名和服务名,将结果存储在 result 中。

我们使用一个循环遍历解析结果,并通过 getnameinfo 函数获取主机名和端口号,并打印出来。

最后,我们通过 freeaddrinfo 函数释放了解析结果的内存。

gethostbyaddr (通过 IP 地址获取主机的信息)

简短描述

gethostbyaddr 函数用于通过 IP 地址获取主机的信息,包括主机名。

注意: gethostbyaddr 是一个旧的函数,不推荐使用。更现代的函数是 getnameinfo

函数原型
1
2
3
#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
参数
  • addr:指向包含要查询的IP地址的结构的指针。
  • len:指定了 addr 结构的长度。
  • type:指定了 addr 结构的类型,通常为 AF_INET
返回值
  • 如果成功,返回指向 struct hostent 结构的指针,其中包含了与IP地址相关的信息,包括主机名。
  • 如果出现错误,返回 NULL,并设置 h_errno 来指示错误类型。
使用方法

以下是一个简单的例子,演示了如何使用 gethostbyaddr 函数通过IP地址获取主机的信息:

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
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

int main() {
struct in_addr addr;
const char *ip_address = "93.184.216.34"; // www.example.com 的IP地址

// 将点分十进制的IP地址转换为网络字节序的整数形式
if (inet_aton(ip_address, &addr) == 0) {
perror("inet_aton");
return 1;
}

struct hostent *host_info;

// 使用 gethostbyaddr 获取主机信息
host_info = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET);

if (host_info == NULL) {
herror("gethostbyaddr");
return 1;
}

printf("Official name: %s\n", host_info->h_name);

return 0;
}

在这个例子中,我们首先将点分十进制的IP地址字符串转换为网络字节序的整数形式。

然后,我们使用 gethostbyaddr 函数来获取与该IP地址相关的信息。

如果成功,gethostbyaddr 将返回一个指向 struct hostent 结构的指针,其中包含了主机的各种信息。

我们通过 h_name 字段获取官方名称。

希望这个例子能帮到你理解 gethostbyaddr 函数的基本用法!如果你有任何其他问题,随时问吧。

getnameinfo (将套接字地址结构转换为主机名和服务名,替代gethostbyaddr)

简短描述

getnameinfo 函数用于将套接字地址结构转换为主机名和服务名。

函数原型
1
2
3
4
#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);
参数
  • addr:指向包含套接字地址信息的结构的指针。
  • addrlenaddr 结构的长度。
  • host:指向存储主机名的缓冲区的指针。
  • hostlen:缓冲区的大小,即最多接受的字符数。
  • serv:指向存储服务名的缓冲区的指针。
  • servlen:缓冲区的大小,即最多接受的字符数。
  • flags:用于控制转换的标志,通常可以设置为0。
返回值
  • 如果成功,返回0。
  • 如果出现错误,返回非零值,可以使用 gai_strerror 函数将错误码转换为字符串。
使用方法

以下是一个简单的例子,演示了如何使用 getnameinfo 函数将套接字地址转换为主机名和服务名:

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
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

int main() {
struct sockaddr_in sa;
char host[NI_MAXHOST], serv[NI_MAXSERV];

// 设置套接字地址信息
sa.sin_family = AF_INET;
sa.sin_port = htons(80);
sa.sin_addr.s_addr = inet_addr("93.184.216.34");

// 使用 getnameinfo 获取主机名和服务名
int status = getnameinfo((struct sockaddr *)&sa, sizeof(sa), host, NI_MAXHOST, serv, NI_MAXSERV, 0);

if (status != 0) {
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(status));
return 1;
}

printf("Host: %s, Service: %s\n", host, serv);

return 0;
}

在这个例子中,我们首先定义了一个 struct sockaddr_in 结构 sa,并设置了其中的成员,包括地址族、端口号和IP地址。

然后,我们使用 getnameinfo 函数将 sa 结构转换为主机名和服务名。

转换成功后,主机名将存储在 host 中,服务名将存储在 serv 中。

最后,我们将它们打印出来。