C++ TCP/IP 网络编程函数
网络编程函数目录
[TOC]
socket(创建套接字)
函数原型
1 |
|
参数
domain
:指定协议族,可以是以下之一:AF_INET
:IPv4 协议族AF_INET6
:IPv6 协议族AF_UNIX
:UNIX 域套接字
type
:指定套接字类型,可以是以下之一:SOCK_STREAM
:提供面向连接的、可靠的数据传输,使用 TCP 协议SOCK_DGRAM
:提供无连接的、不可靠的数据传输,使用 UDP 协议SOCK_RAW
:提供原始网络访问
protocol
:指定协议,通常为 0。根据domain
和type
的组合,操作系统会自动选择合适的协议。
返回值
- 如果函数成功创建了一个套接字,则返回一个非负整数,该整数称为套接字描述符。如果失败,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 socket
函数创建一个 IPv4 的 TCP 套接字:
1 |
|
在这个例子中,我们包含了一些必要的头文件,并在 main
函数中调用了 socket
函数。如果成功创建了套接字,它将返回一个非负整数,表示套接字的描述符。
请注意,在实际的网络编程中,你还需要处理套接字的绑定、监听、连接、发送和接收数据等操作。
bind (绑定地址到 socket)
函数原型
1 |
|
参数
sockfd
:套接字描述符,由socket
函数返回。addr
:指向一个struct sockaddr
结构体的指针,用于指定要绑定的地址信息。addrlen
:addr
结构体的长度。
返回值
- 如果绑定成功,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
bind
函数用于将一个套接字与特定的地址(IP 地址和端口号)绑定在一起,以便在网络上监听特定的地址。
以下是一个简单的例子,演示了如何使用 bind
函数:
1 |
|
在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字。接着,我们设置了一个 struct sockaddr_in
结构体,指定了要绑定的地址信息(IP 地址和端口号)。最后,我们调用 bind
函数将套接字与地址绑定在一起。
请注意,在实际的网络编程中,你可能需要先检查 bind
函数的返回值以确保绑定成功。同时,你还需要在绑定之后进行监听或者接受连接等操作。
listen (监听客户端请求)
函数原型
1 |
|
参数
sockfd
:套接字描述符,由socket
函数返回,并且已经通过bind
函数绑定到一个地址上。backlog
:指定在套接字接受新连接之前,可以排队等待的最大连接数。这个参数的具体含义可能因操作系统而异,但通常是一个正整数。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
listen
函数用于将一个套接字设置为被动监听状态,开始接受新的连接请求。
以下是一个简单的例子,演示了如何使用 listen
函数:
1 |
|
在这个例子中,我们在 bind
函数之后调用了 listen
函数,并将套接字设置为监听状态,最多可以等待 5 个连接请求。
请注意,在实际的网络编程中,你可能需要根据具体需求调整 backlog
参数,以确保你的服务器能够处理预期的连接数。
accpet (接受客户端连接)
函数原型
1 |
|
参数
sockfd
:套接字描述符,处于监听状态的套接字。addr
:指向一个struct sockaddr
结构体的指针,用于存储连接方的地址信息。addrlen
:addr
结构体的长度。
返回值
- 如果成功,返回一个新的套接字描述符,用于与客户端通信。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
accept
函数用于从监听套接字中接受新的连接请求,并返回一个新的套接字,用于与客户端通信。
以下是一个简单的例子,演示了如何使用 accept
函数:
1 |
|
在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,并将其绑定到特定的地址上。然后,我们将套接字设置为监听状态,并等待新的连接请求。
一旦有新的连接请求到达,accept
函数将返回一个新的套接字描述符,用于与客户端通信。我们可以在这个新的套接字上进行读写操作,与客户端进行通信。
请注意,一旦通信结束,记得要关闭相应的套接字。
connect (客户端连接服务器端)
函数原型
1 |
|
参数
sockfd
:套接字描述符,用于与服务器建立连接。addr
:指向一个struct sockaddr
结构体的指针,包含了服务器的地址信息。addrlen
:addr
结构体的长度。
返回值
- 如果成功连接到服务器,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
connect
函数用于与远程服务器建立连接。
以下是一个简单的例子,演示了如何使用 connect
函数:
1 |
|
在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,并将其连接到本地主机的 8080 端口。接着,我们调用 connect
函数与服务器建立连接。
一旦成功连接,我们可以在 sockfd
上进行读写操作,与服务器进行通信。
请注意,一旦通信结束,记得要关闭相应的套接字。
sendto (UDP 发送数据)
简短描述
sendto
函数用于通过已连接或未连接的套接字发送数据。
函数原型
1 |
|
参数
sockfd
:套接字文件描述符,标识要使用的套接字。buf
:指向包含要发送的数据的缓冲区的指针。len
:要发送的字节数。flags
:用于控制发送操作的标志,通常可以设置为0。dest_addr
:指向目标地址信息的指针,对于未连接套接字,用于指定数据的目的地。addrlen
:目标地址结构的长度。
返回值
- 如果成功,返回实际发送的字节数。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 sendto
函数发送数据到指定的目标地址:
1 |
|
在这个例子中,我们首先创建了一个数据报套接字,用于进行数据报通信。
然后,我们设置了目标地址信息,包括目标IP地址和端口号。
接着,我们准备要发送的数据,并使用 sendto
函数将数据发送到指定的目标地址。
sendto
函数返回实际发送的字节数。
最后,我们关闭了套接字以释放资源。
recvfrom (UDP 接收数据)
简短描述
recvfrom
函数用于从已连接或未连接的套接字接收数据,同时可以获取数据的发送者的地址信息。
函数原型
1 |
|
参数
sockfd
:套接字文件描述符,标识要使用的套接字。buf
:指向存储接收数据的缓冲区的指针。len
:缓冲区的大小,即最多接收的字节数。flags
:用于控制接收操作的标志,通常可以设置为0。src_addr
:用于存储发送者地址信息的struct sockaddr
结构的指针,如果不需要获取发送者地址信息,可以将其设置为NULL
。addrlen
:指向存储发送者地址信息长度的整数的指针,如果不需要获取发送者地址信息,可以将其设置为NULL
。
返回值
- 如果成功,返回实际接收的字节数。
- 如果连接关闭,返回0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 recvfrom
函数从套接字接收数据,并获取发送者的地址信息:
1 |
|
在这个例子中,我们首先创建了一个数据报套接字,并将其绑定到本地地址。
然后,我们使用 recvfrom
函数接收从客户端发送过来的数据,并获取了发送者的地址信息。
接收到的数据存储在 buffer
中,recvfrom
函数返回实际接收的字节数。
我们还通过 inet_ntop
函数将发送者的IP地址从二进制转换为点分十进制格式,并输出到屏幕上。
最后,我们关闭了套接字以释放资源。
shutdown (选择性断开输入或输出)
简短描述
shutdown
函数用于关闭套接字的一部分功能,可以选择性地关闭读取或写入功能。
函数原型
1 |
|
参数
sockfd
:套接字文件描述符,标识要使用的套接字。how
:指定要关闭的功能,可以是以下选项之一:SHUT_RD
:关闭套接字的读取功能,不能再从套接字读取数据。SHUT_WR
:关闭套接字的写入功能,不能再向套接字写入数据。(会向对端发送 EOF)SHUT_RDWR
:关闭套接字的读取和写入功能,相当于同时调用SHUT_RD
和SHUT_WR
。
返回值
- 如果成功,返回0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 shutdown
函数关闭套接字的写入功能:
1 |
|
在这个例子中,我们首先创建了一个套接字。
然后,我们可以进行连接到服务器等操作(这里省略了连接的部分)。
接着,我们使用 shutdown
函数关闭了套接字的写入功能,表示我们不会再向套接字写入数据。
之后,我们可以使用套接字进行读取操作,如果已经到达流的末尾,读取操作将返回0。
最后,我们关闭了套接字以释放资源。
close (关闭套接字连接)
函数原型
1 |
|
参数
fd
:文件描述符,可以是套接字描述符、文件描述符等。
返回值
- 如果成功关闭文件描述符,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
close
函数用于关闭一个打开的文件描述符,包括套接字描述符。
以下是一个简单的例子,演示了如何使用 close
函数:
1 |
|
在这个例子中,我们首先获取了一个有效的文件描述符(这里假设它已经通过合适的方式获得),然后调用 close
函数来关闭它。
请注意,如果 close
函数成功执行,它会返回 0,表示文件描述符已经成功关闭。
dup (复制FD)
简单描述
dup
函数用于复制文件描述符。
详细描述
dup
函数可以用于复制一个已存在的文件描述符,创建一个新的文件描述符,这个新的描述符和原来的描述符指向同一个文件。
函数原型
1 |
|
函数参数
oldfd
:要复制的文件描述符。
返回值
- 成功:返回新的文件描述符,即复制后的描述符。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序首先使用 open
函数打开一个文件,得到文件描述符 fd
。然后使用 dup
函数复制这个文件描述符,得到新的文件描述符 new_fd
。接着可以使用这两个描述符进行读取文件操作。
注意事项
- 使用
dup
函数可以创建一个新的文件描述符,指向同一个文件,这样可以在程序中同时使用两个描述符进行读写操作。 - 在不再需要使用这两个描述符时,需要分别调用
close
函数关闭它们,以释放相关资源。
dup2 (复制FD,并指定新fd值)
简单描述
dup2
函数用于复制文件描述符,并且可以指定新的文件描述符的值。
详细描述
dup2
函数和 dup
函数类似,都可以用于复制一个已存在的文件描述符。但 dup2
允许指定新的文件描述符的值,而不是由系统自动分配。
函数原型
1 |
|
函数参数
oldfd
:要复制的文件描述符。newfd
:要设置的新文件描述符的值。
返回值
- 成功:返回新的文件描述符,即复制后的描述符。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序首先使用 open
函数打开一个文件,得到文件描述符 fd
。然后使用 dup2
函数将 fd
复制到指定的新文件描述符 new_fd
。接着可以使用这两个描述符进行读取文件操作。
注意事项
- 使用
dup2
函数可以创建一个新的文件描述符,并且可以指定其值,而不是由系统自动分配。 - 在不再需要使用这两个描述符时,需要分别调用
close
函数关闭它们,以释放相关资源。
getsockopt (获取套接字的选项值)
简短描述
getsockopt
函数用于获取套接字的选项值。
函数原型
1 |
|
参数
sockfd
:套接字文件描述符,标识要使用的套接字。level
:选项所在的协议层级,通常可以设置为SOL_SOCKET
表示在套接字层级操作。optname
:选项的名称,如SO_REUSEADDR
。optval
:指向用于接收选项值的缓冲区的指针。optlen
:指向存储optval
大小的整数的指针。
返回值
- 如果成功,返回0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 getsockopt
函数获取套接字选项值:
1 |
|
在这个例子中,我们首先创建了一个套接字。
然后,我们定义了一个整数变量 option
以及一个 socklen_t
类型的变量 option_len
来存储选项值和选项值的大小。
接着,我们使用 getsockopt
函数来获取 SO_REUSEADDR
选项的值。
获取成功后,选项值将存储在 option
中,我们将其打印出来。
最后,我们关闭了套接字以释放资源。
setsockopt (设置套接字选项值)
简短描述
setsockopt
函数用于设置套接字的选项值。
函数原型
1 |
|
参数
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 |
返回值
- 如果成功,返回0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 setsockopt
函数设置套接字选项值:
1 |
|
在这个例子中,我们首先创建了一个套接字。
然后,我们定义了一个整数变量 option
并将其设置为1,表示启用 SO_REUSEADDR
选项。
接着,我们使用 setsockopt
函数来设置套接字的选项值。
设置成功后,我们打印一条消息。
最后,我们关闭了套接字以释放资源。
write (将数据写入FD)
简短描述
write
函数用于将数据写入文件描述符(通常是文件、管道或套接字)中。
函数原型
1 |
|
参数
fd
:文件描述符,标识要写入的目标文件或其他数据流。buf
:指向要写入的数据的缓冲区的指针。count
:要写入的字节数。
返回值
- 如果成功,返回实际写入的字节数(可能小于
count
)。 - 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 write
函数将数据写入文件:
1 |
|
在这个例子中,我们首先打开一个名为 “output.txt” 的文件,以便我们可以将数据写入其中。
然后,我们使用 write
函数将 message
中的数据写入文件。strlen(message)
用于计算要写入的字节数。
最后,我们关闭文件以释放资源。
请注意,write
函数返回实际写入的字节数,这可能小于 count
。
read (从 FD 读取数据)
简短描述
read
函数用于从文件描述符(通常是文件、管道或套接字)中读取数据。
函数原型
1 |
|
参数
fd
:文件描述符,标识要读取的源文件或其他数据流。buf
:指向存储读取数据的缓冲区的指针。count
:要读取的最大字节数。
返回值
- 如果成功,返回实际读取的字节数。
- 如果已经到达文件末尾,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 read
函数从文件中读取数据:
1 |
|
在这个例子中,我们首先打开一个名为 “input.txt” 的文件,以便我们可以从中读取数据。
然后,我们使用 read
函数将最多 sizeof(buffer)
个字节的数据读取到缓冲区 buffer
中。
read
函数返回实际读取的字节数。
最后,我们输出所读取的内容,并关闭文件以释放资源。
writev (向文件描述符写入多个非连续缓冲区的数据)
简单描述
writev
函数用于向文件描述符写入多个非连续缓冲区的数据。
详细描述
writev
函数可以用于将多个非连续的缓冲区的数据一次性写入到文件描述符中。这个函数通常用于提高写入效率,尤其是在需要将散布在不同内存区域的数据写入到文件时。
函数原型
1 |
|
函数参数
fd
:目标文件描述符。iov
:一个指向iovec
结构体数组的指针,每个iovec
结构体描述了一个缓冲区的起始地址和长度。iovcnt
:iovec
结构体数组的长度,也就是缓冲区的个数。
返回值
- 成功:返回写入的总字节数。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了两个缓冲区 msg1
和 msg2
,然后通过 struct iovec
结构体数组将它们描述为两个不连续的缓冲区,最后使用 writev
函数一次性将它们写入到标准输出中。
注意事项
- 使用
writev
函数可以减少系统调用的次数,提高写入效率。 struct iovec
结构体中的iov_base
成员指向缓冲区的起始地址,iov_len
成员表示缓冲区的长度。- 可以根据实际需求创建更多的
iovec
结构体来描述多个缓冲区。
readv (从文件描述符读取多个非连续缓冲区的数据)
简单描述
readv
函数用于从文件描述符读取多个非连续缓冲区的数据。
详细描述
readv
函数可以用于将多个非连续的缓冲区的数据一次性读取到文件描述符中。这个函数通常用于提高读取效率,尤其是在需要从文件中读取到不同内存区域时。
函数原型
1 |
|
函数参数
fd
:源文件描述符。iov
:一个指向iovec
结构体数组的指针,每个iovec
结构体描述了一个缓冲区的起始地址和长度。iovcnt
:iovec
结构体数组的长度,也就是缓冲区的个数。
返回值
- 成功:返回读取的总字节数。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了两个缓冲区 buffer1
和 buffer2
,然后通过 struct iovec
结构体数组将它们描述为两个不连续的缓冲区,最后使用 readv
函数一次性将数据从标准输入中读取到这两个缓冲区中。
注意事项
- 使用
readv
函数可以减少系统调用的次数,提高读取效率。 struct iovec
结构体中的iov_base
成员指向缓冲区的起始地址,iov_len
成员表示缓冲区的长度。- 可以根据实际需求创建更多的
iovec
结构体来描述多个缓冲区。
send (在已有套接字上发送数据)
简单描述
send
函数用于在一个已连接的套接字上发送数据。
详细描述
send
函数用于在已连接的套接字上发送数据。它可以用于发送TCP或者UDP套接字上的数据。
函数原型
1 |
|
函数参数
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 进行简要解释:
0
: 默认行为,通常表示阻塞等待直到数据被发送或接收完毕。MSG_DONTWAIT
: 表示以非阻塞方式发送或接收数据,即立即返回,无论是否可以完成操作。MSG_OOB
: 处理带外(out-of-band)数据,用于紧急数据传输。MSG_PEEK
: 接收数据时保留数据,而不从接收队列中移除,使得之后的recv
仍然可以接收到相同的数据。MSG_WAITALL
: 保证在接收数据时接收到指定长度的数据,直到全部数据接收完毕。MSG_NOSIGNAL
: 当远程连接断开时不发送SIGPIPE
信号,而是返回一个错误。MSG_CONFIRM
: 用于sendto
,表示数据报发送时要求确认。MSG_EOR
: 表示接收到数据报的末尾。
请注意,send
和 recv
的 flag 参数可以通过按位或操作(|
)来组合使用,以实现多个选项的组合。例如,如果你想以非阻塞方式发送并且处理带外数据,可以使用 MSG_DONTWAIT | MSG_OOB
。
返回值
- 成功:返回发送的字节数(可能小于请求发送的字节数)。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了一个TCP套接字,连接到服务器,并使用 send
函数发送数据到服务器。
注意事项
send
可能会发送比len
更少的字节。在这种情况下,可以使用循环来保证所有数据都被发送。- 在非阻塞套接字上,
send
可能会返回EAGAIN
或者EWOULDBLOCK
错误,表示当前套接字不可写。此时可以使用select
或者poll
函数等待套接字变得可写后再次尝试发送。
recv (从已连接的套接字中接收数据)
简单描述
recv
函数用于从已连接的套接字中接收数据。
详细描述
recv
函数用于从已连接的套接字中接收数据。它可以用于接收TCP或者UDP套接字上的数据。
函数原型
1 |
|
函数参数
sockfd
:已连接套接字的文件描述符。buf
:指向接收数据的缓冲区的指针。len
:缓冲区的长度。flags
:可选的标志参数,通常可以设置为0。
返回值
- 成功:返回接收到的字节数(可能小于请求接收的字节数)。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了一个TCP套接字,连接到服务器,并使用 recv
函数接收数据。
注意事项
recv
可能会接收比len
更少的字节。在这种情况下,可以使用循环来保证所有数据都被接收。- 在非阻塞套接字上,
recv
可能会返回EAGAIN
或者EWOULDBLOCK
错误,表示当前套接字没有可读数据。此时可以使用select
或者poll
函数等待套接字变得可读后再次尝试接收。
open (打开或创建一个文件,返回FD)
简短描述
open
函数用于打开或创建一个文件,并返回与之关联的文件描述符。
函数原型
1 |
|
参数
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语言中,可以使用八进制数表示权限模式,例如 0644
或 0755
。
示例代码:
1 | int file_descriptor = open("example.txt", O_WRONLY | O_CREAT, 0644); |
上述代码使用 open
函数创建了一个文件,具有所有者读写权限和群组、其他用户只读权限的权限模式。
返回值
- 如果成功,返回与文件相关联的文件描述符(非负整数)。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 open
函数打开或创建一个文件:
1 |
|
在这个例子中,我们首先尝试打开一个名为 “existing_file.txt” 的已存在文件以供读取。
如果成功,open
函数将返回一个与文件关联的文件描述符,可以用于后续的读取操作。
接着,我们尝试创建一个名为 “new_file.txt” 的新文件以供写入。
我们在 flags
参数中使用了 O_WRONLY
(只写)、O_CREAT
(如果不存在则创建)、O_TRUNC
(截断文件)以及 mode
参数来指定文件的权限。
如果成功,open
函数将返回一个与新创建文件关联的文件描述符,可以用于后续的写入操作。
fopen (以指定模式打开文件,返回FILE*)
简短描述
fopen
函数用于以指定的模式打开文件,并返回一个与之关联的文件指针。最后需要使用 fclose 关闭文件
函数原型
1 |
|
参数
pathname
:要打开的文件的路径名。mode
:打开文件的模式字符串,可以是以下之一:"r"
:只读模式,打开文件用于读取。"w"
:只写模式,创建一个新文件或截断一个已存在文件,并打开文件用于写入。"a"
:追加模式,打开文件用于写入,将数据追加到文件末尾。"r+"
:读写模式,打开文件用于读取和写入。"w+"
:读写模式,创建一个新文件或截断一个已存在文件,并打开文件用于读取和写入。"a+"
:读写模式,打开文件用于读取和写入,将数据追加到文件末尾。
返回值
- 如果成功,返回一个指向文件的指针。
- 如果出现错误,返回
NULL
,并设置errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 fopen
函数打开或创建一个文件:
1 |
|
在这个例子中,我们首先尝试以只读模式打开一个名为 “existing_file.txt” 的已存在文件。
如果成功,fopen
函数将返回一个指向文件的指针,可以用于后续的读取操作。
接着,我们尝试创建一个名为 “new_file.txt” 的新文件以供写入。
我们在 mode
参数中使用了 "w"
(只写模式),这将创建一个新文件或截断一个已存在文件,并打开文件用于写入。
如果成功,fopen
函数将返回一个指向新创建文件的指针,可以用于后续的写入操作。
fclose (关闭 fopen 打开的文件)
简短描述
fclose
函数用于关闭一个打开的文件。
函数原型
1 |
|
参数
stream
:要关闭的文件指针。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回
EOF
,并设置errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 fclose
函数关闭一个打开的文件:
1 |
|
在这个例子中,我们首先打开一个名为 “example.txt” 的文件以供写入。
然后,我们使用 fprintf
函数将数据写入文件。
最后,我们使用 fclose
函数关闭文件。如果成功,fclose
函数将返回 0,否则返回 EOF
。
fdopen (转换fd->FILE*)
简单描述
fdopen
函数用于将一个已存在的文件描述符关联到一个 FILE*
流。
详细描述
fdopen
函数允许将一个已经打开的文件描述符转换成一个 FILE*
流,这样就可以使用标准I/O库函数来操作该文件描述符。
函数原型
1 |
|
函数参数
fd
:已存在的文件描述符。mode
:打开模式字符串,可以是"r"
(只读),"w"
(写入),"a"
(追加)等。
返回值
- 成功:返回与给定文件描述符关联的
FILE*
指针。 - 失败:返回
NULL
,并设置errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序首先使用 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 |
|
函数参数
stream
:一个指向已打开的FILE*
流的指针。
返回值
- 成功:返回与给定
FILE*
流关联的文件描述符。 - 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序首先使用 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 |
|
参数
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 | int flags = fcntl(fd, F_GETFL); // 获取当前标志 |
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 | struct flock 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 | int flags = fcntl(fd, F_GETFD); |
6. 获取/设置文件状态标志(F_GETFL、F_SETFL)
1 | int fcntl(int fd, int cmd, ...); |
cmd
参数可以是以下之一:
F_GETFL
: 获取文件状态标志。F_SETFL
: 设置文件状态标志。
1 | int flags = fcntl(fd, F_GETFL); |
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 |
|
在这个例子中,我们首先获取了一个有效的文件描述符(这里假设它已经通过合适的方式获得),然后使用 fcntl
函数将该文件描述符设置为非阻塞模式。
请注意,fcntl
函数的参数 cmd
决定了它的具体行为,同时也决定了是否需要提供额外的参数 arg
。因此,在实际使用时,请根据需要选择正确的 cmd
值,并根据需要提供相应的参数。
setsockopt (设置套接字可选项)
简短描述
setsockopt
函数用于设置套接字选项,可以在套接字创建后动态地调整其行为。
函数原型
1 |
|
参数
sockfd
:套接字描述符,指定要设置选项的套接字。level
:选项所属的协议层次,通常是SOL_SOCKET
(套接字层次)。optname
:要设置的选项名,具体的选项可以是各种套接字选项,比如SO_REUSEADDR
、SO_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
:指向包含选项值的内存区域。optlen
:optval
内存区域的长度。
返回值
- 如果成功设置选项,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 setsockopt
函数:
1 |
|
在这个例子中,我们首先创建了一个 IPv4 的 TCP 套接字,然后使用 setsockopt
函数将 SO_REUSEADDR
选项设置为启用状态。
SO_REUSEADDR
选项允许在套接字关闭后,仍然可以绑定到相同的地址和端口,这在服务器程序经常需要重新启动时很有用。
请注意,我们在设置选项之前,首先创建了一个套接字并检查其是否创建成功。
inet_ntop (netip -> string)
简短描述
inet_ntop
函数用于将网络字节序的二进制地址转换为文本形式的地址表示。
函数原型
1 |
|
参数
af
:地址族,可以是AF_INET
(IPv4)或者AF_INET6
(IPv6)。src
:指向包含二进制地址的内存区域的指针。dst
:指向存储文本形式地址的缓冲区的指针。size
:目标缓冲区dst
的长度。
返回值
- 如果转换成功,返回指向目标缓冲区的指针,即
dst
。 - 如果出现错误,返回
NULL
,并设置errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 inet_ntop
函数:
1 |
|
在这个例子中,我们首先定义了一个包含了二进制表示的 IPv4 地址的字符串 binary_ip
。接着,我们创建一个字符数组 text_ip
用于存储转换后的文本形式地址。
然后,我们调用 inet_ntop
函数将二进制地址转换为文本形式,结果存储在 text_ip
中。
请注意,INET_ADDRSTRLEN
是一个宏定义,用于表示 IPv4 地址的最大长度,通常是 16 个字符。
inet_addr (将点分十进制IP转换为网络序)
简短描述
inet_addr
函数将一个点分十进制的IP地址转换为网络字节序(大端序)的32位整数形式。
函数原型
1 |
|
参数
cp
:指向包含点分十进制IP地址的字符串的指针。
返回值
- 如果成功,返回网络字节序的32位整数形式的IP地址。
- 如果出现错误,返回
INADDR_NONE
(通常是 -1),表示转换失败。
使用方法
以下是一个简单的例子,演示了如何使用 inet_addr
函数:
1 |
|
在这个例子中,我们将一个点分十进制的IP地址字符串 "192.168.1.1"
传递给 inet_addr
函数。
如果转换成功,inet_addr
将返回网络字节序的32位整数形式的IP地址。
请注意,INADDR_NONE
是一个特殊的宏,表示转换失败。
inet_aton (将点分十进制转换为网络序,存到一个变量)
简短描述
inet_aton
函数将一个点分十进制的IP地址转换为网络字节序的32位整数形式。
函数原型
1 |
|
参数
cp
:指向包含点分十进制IP地址的字符串的指针。inp
:指向struct in_addr
结构的指针,用于存储转换后的IP地址。
返回值
- 如果成功,返回非零值。
- 如果出现错误,返回零。
使用方法
以下是一个简单的例子,演示了如何使用 inet_aton
函数:
1 |
|
在这个例子中,我们将一个点分十进制的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 |
|
参数
in
:一个struct in_addr
结构,其中包含了32位整数形式的IP地址。
返回值
- 成功返回一个指向包含点分十进制IP地址的静态缓冲区的指针。
- 失败返回 -1
使用方法
以下是一个简单的例子,演示了如何使用 inet_ntoa
函数:
1 |
|
在这个例子中,我们首先将一个点分十进制的IP地址字符串转换为32位整数形式,然后将其存储在 struct in_addr
结构中。
接着,我们使用 inet_ntoa
函数将这个32位整数形式的IP地址转换为点分十进制的字符串。
请注意,inet_ntoa
返回一个指向静态缓冲区的指针,因此在后续的调用中可能会被覆盖。
进程
fork (创建新进程)
简短描述
fork
函数用于创建一个新的进程,新进程是原进程的副本。
调用 fork 函数后,如果之前有套接字返回的描述符,会被复制,但是不会复制套接字。
函数原型
1 |
|
参数
该函数没有参数。
返回值
- 在父进程中,返回新创建的子进程的PID(进程ID)。
- 在子进程中,返回0。
- 如果出现错误,返回 -1。
使用方法
以下是一个简单的例子,演示了如何使用 fork
函数创建一个新的进程:
1 |
|
在这个例子中,我们调用了 fork
函数来创建一个新的进程。
在父进程中,fork
返回新创建的子进程的PID,而在子进程中,fork
返回0。
因此,我们可以通过判断返回值来确定当前是父进程还是子进程。
在父进程中,我们会得到子进程的PID,而在子进程中,我们得到的PID 就是0。
pipe (进程间通信)
简单描述
pipe
函数用于创建一个管道,使得一个进程的输出可以直接成为另一个进程的输入。
详细描述
pipe
函数创建一个管道,它是一个特殊的文件,可以用于进程间通信。它通常用于父子进程之间或者兄弟进程之间进行通信。
函数原型
1 |
|
函数参数
filedes
:一个包含两个整数的数组,用于存储管道的文件描述符,filedes[0]
用于读取,filedes[1]
用于写入。
返回值
- 成功:0
- 失败:-1,同时设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了一个管道,并通过 fork
创建了一个子进程。子进程关闭了读取端,写入了数据到管道,然后关闭了写入端。父进程关闭了写入端,读取了数据,然后关闭了读取端。这样,子进程写入的数据通过管道传递到了父进程中。
注意事项
- 管道的容量是有限的,如果写入速度过快,可能会导致写入阻塞或者数据丢失。
- 如果没有进程继续读取管道,写入操作可能会导致信号
SIGPIPE
被发送给写入进程,这可能导致程序异常终止。 - 管道是单向的,如果需要双向通信,需要创建两个管道,分别用于不同方向的通信。
wait (等待子进程终止)
简短描述
wait
函数用于等待子进程的终止,并获取子进程的退出状态。
wait
函数的作用是:
- 父进程调用
wait
函数后会一直阻塞,直到它的一个子进程结束。 - 一旦子进程结束,
wait
会返回被等待的子进程的进程号(PID)。 - 同时,子进程的退出状态会被保存在
status
中,以便父进程后续查询。
函数原型
1 |
|
参数
status
:指向一个整数的指针,用于存储子进程的退出状态。
status
是一个整型指针,用于存储子进程的退出状态信息。可以通过一些宏来解析 status
中的信息,比如 WIFEXITED(status)
来检查子进程是否正常退出。
以下是一些常用的宏:
WIFEXITED(status)
:判断子进程是否正常退出。WEXITSTATUS(status)
:获取子进程的返回值(只有在WIFEXITED
返回非零时才有效)。WIFSIGNALED(status)
:判断子进程是否因信号而终止。WTERMSIG(status)
:获取导致子进程终止的信号编号(只有在WIFSIGNALED
返回非零时才有效)。
返回值
- 如果成功,返回终止的子进程的PID(进程ID)。
- 如果出现错误,返回 -1。
使用方法
以下是一个简单的例子,演示了如何使用 wait
函数等待子进程的终止:
1 |
|
在这个例子中,我们首先调用了 fork
函数来创建一个新的进程。
在父进程中,fork
返回新创建的子进程的PID,而在子进程中,fork
返回0。
因此,我们可以通过判断返回值来确定当前是父进程还是子进程。
在子进程中,我们打印出子进程的PID,并调用 exit
函数来退出进程,同时指定退出状态为42。
在父进程中,我们打印出父进程和子进程的PID,并使用 wait
函数等待子进程的终止。
wait
函数会阻塞父进程,直到一个子进程终止为止。
一旦子进程终止,父进程将得到终止的子进程的PID,并将子进程的退出状态存储在 status
变量中。
我们使用 WIFEXITED
宏来检查子进程是否正常退出,如果是,我们可以通过 WEXITSTATUS
宏获取退出状态。
waitpid (等待指定子进程终止)
简短描述
waitpid
函数用于等待指定的子进程的终止,并获取子进程的退出状态。
函数原型
1 |
|
参数
pid
:要等待的子进程的PID。可以使用以下值:-1
:等待任意子进程。0
:等待与当前进程在同一个进程组的任意子进程。- 大于0的值:等待指定PID的子进程。
status
:指向一个整数的指针,用于存储子进程的退出状态。options
:指定等待的行为选项,通常可以设置为0。WNOHANG
:非阻塞模式,即使没有子进程结束,也立即返回,返回0表示没有子进程退出。WUNTRACED
:也等待已经暂停的子进程,但不是终止的子进程。
返回值
- 如果成功,返回终止的子进程的PID(进程ID)。
- 如果出现错误,返回 -1。
使用方法
以下是一个简单的例子,演示了如何使用 waitpid
函数等待指定的子进程的终止:
1 |
|
在上述示例中,父进程创建了一个子进程,然后使用 waitpid
函数非阻塞地等待子进程结束。
一旦子进程终止,父进程将得到终止的子进程的PID,并将子进程的退出状态存储在 status
变量中。
我们使用 WIFEXITED
宏来检查子进程是否正常退出,如果是,我们可以通过 WEXITSTATUS
宏获取退出状态。
I/O 复用
select (监视多个文件描述符)
简单描述
select
函数用于在多个文件描述符上等待可读、可写或出错事件,从而实现非阻塞的 I/O 操作。
详细描述
select
函数允许程序同时监视多个文件描述符,当其中之一变得可读、可写或发生错误时,它将返回并允许程序执行相应的操作。
函数原型
1 |
|
函数参数
nfds
:需要监视的文件描述符集合中的最大值(即最大的文件描述符值加一)。readfds
:包含所有你希望监视可读事件的文件描述符的集合。writefds
:包含所有你希望监视可写事件的文件描述符的集合。exceptfds
:包含所有你希望监视错误事件的文件描述符的集合。timeout
:一个指向struct timeval
结构体的指针,用于设置select
的超时时间。
以下是 timeval
结构体的定义:
1 | struct timeval { |
接下来我将解释每个字段的含义:
tv_sec
:- 类型:
time_t
- 用于存储秒数部分的时间值。它表示了从1970年1月1日00:00:00开始的秒数。
- 类型:
tv_usec
:- 类型:
suseconds_t
- 用于存储微秒部分的时间值。它表示了秒数的小数部分,即一秒的百万分之一。
- 类型:
timeval
结构体通常用于测量时间间隔或者计时器。下面是一个简单的例子,演示如何使用 gettimeofday
函数来获取当前时间:
1 |
|
在上述例子中,我们首先声明了两个 struct timeval
类型的变量 start
和 end
来存储起始时间和结束时间。然后,我们使用 gettimeofday
函数来获取当前的时间戳,分别将其存储到 start
和 end
中。接着,我们模拟了一些工作(这里使用了一个简单的循环),之后再次调用 gettimeofday
获取结束时间。
最后,我们计算了时间差,将其以微秒为单位打印出来。
请注意,在实际的程序中,可能会使用更高级的时间处理函数,例如 clock_gettime
,它提供了更高的精度和更多的选项,特别是在需要精确计时的情况下。
返回值
- 成功:就绪文件描述符的总数。超时返回 0。
- 失败:-1,同时设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序使用 select
监视标准输入,并设置了5秒的超时时间。如果在超时前标准输入变得可读,程序将输出 “Data is available”;如果超时时间到达仍然没有可读事件发生,程序将输出 “Timeout”。
注意事项
select
函数在监视的文件描述符集合上发生变化时返回,如果超时时间设置为0,它将成为一个非阻塞的 I/O 监视工具。select
的效率可能会随着监视的文件描述符数量的增加而下降,因此对于大量文件描述符的情况,可能需要使用更高效的机制,如epoll
或kqueue
。
epoll_create (创建一个 epoll 实例监视多个fd上的事件)
简单描述
epoll_create
函数用于创建一个 epoll 实例,用于高效地监视多个文件描述符上的事件。
详细描述
epoll_create
函数创建一个 epoll 实例,它是一个事件通知机制,可以用于高效地监视多个文件描述符上的可读、可写、出错等事件。
函数原型
1 |
|
函数参数
size
:在内核事件表中的最大监听事件数,但这个参数在新版 Linux 已经不再使用,传入一个大于 0 的值即可。
返回值
- 成功:返回一个 epoll 实例的文件描述符。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序使用 epoll_create
函数创建了一个 epoll 实例,并得到了一个文件描述符 epoll_fd
。然后可以使用这个描述符进行事件监视。
注意事项
epoll_create
创建一个 epoll 实例,用于高效地监视多个文件描述符上的事件。- 在使用完 epoll 实例后,需要使用
close
函数关闭,释放相关资源。
epoll_ctl (增删改要监视的 epoll 实例中的事件)
简单描述
epoll_ctl
函数用于控制 epoll 实例中的事件。
详细描述
epoll_ctl
函数可以用于向 epoll 实例中添加、修改或删除监视的事件。
函数原型
1 |
|
函数参数
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 | struct epoll_event { |
接下来,我将详细解释 epoll_event
结构体的两个字段:
events
:类型:
uint32_t
用于表示关心的事件类型,它可以是以下几个宏的组合:
EPOLLIN
:表示可读事件,当套接字上有数据可读时触发。EPOLLOUT
:表示可写事件,当套接字可写时触发。EPOLLRDHUP
:表示对端关闭连接或者半关闭连接。EPOLLPRI
:表示有紧急数据可读。EPOLLERR
:表示发生错误。EPOLLHUP
:表示发生挂起事件。EPOLLET
:表示将事件设置为边缘触发模式(Edge Triggered)。EPOLLONESHOT
:表示将事件设置为一次性模式,即只触发一次,之后需要重新添加。
data
:- 类型:
epoll_data_t
- 是一个联合体,可以包含用户自定义的数据,通常是一个指针或者一个文件描述符。
1
2
3
4
5
6typedef 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 | struct epoll_event event; |
在上述示例中,我们创建了一个 epoll 实例 epoll_fd
,并将需要监听的事件添加到其中。然后使用 epoll_wait
函数等待就绪事件,一旦事件就绪,就可以根据 events[i].data.fd
和 events[i].events
来处理相应的事件类型。
返回值
- 成功:返回0。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序首先创建了一个 epoll 实例并获得了一个文件描述符 epoll_fd
。然后,通过初始化一个 epoll_event
结构体来指定要监视的事件类型,以及关联的文件描述符。接着使用 epoll_ctl
将标准输入文件描述符添加到 epoll 实例中进行监视。
注意事项
- 使用
epoll_ctl
可以向 epoll 实例中添加、修改或删除监视的事件,可以在程序运行过程中动态地控制需要监视的文件描述符和事件类型。 - 在使用完 epoll 实例后,需要使用
close
函数关闭,释放相关资源。
epoll_wait (等待fd中的事件)
简单描述
epoll_wait
函数用于等待文件描述符上的事件。
详细描述
epoll_wait
函数会阻塞程序的执行,直到指定的文件描述符上发生了指定的事件。
函数原型
1 |
|
函数参数
epfd
:epoll 实例的文件描述符。events
:一个指向epoll_event
结构体数组的指针,用于存储发生的事件。maxevents
:events
数组的最大长度,即最多能够存储多少个事件。timeout
:等待事件的超时时间,以毫秒为单位。传入-1
表示一直等待,传入0
表示立即返回,传入正整数表示等待指定的毫秒数。
返回值
- 成功:返回发生的事件数量。
- 失败:返回-1,并设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序创建了一个 epoll 实例,并将标准输入文件描述符添加到 epoll 实例中进行监视。然后使用 epoll_wait
函数等待事件的发生。当事件发生时,epoll_wait
函数会返回,并将发生的事件存储在 events
数组中。程序可以遍历 events
数组,处理发生的事件。
注意事项
- 使用
epoll_wait
函数可以等待指定的文件描述符上发生指定的事件,并在事件发生时返回,以便程序进行相应的处理。 - 在使用完 epoll 实例后,需要使用
close
函数关闭,释放相关资源。
线程和信号处理
pthread_create (创建一个新线程)
简短描述
pthread_create
函数用于创建一个新的线程。
函数原型
1 |
|
参数
thread
:一个指向pthread_t
结构的指针,用于存储新线程的标识符。attr
:一个指向pthread_attr_t
结构的指针,指定线程的属性。通常情况下可以设置为NULL
,使用默认属性。start_routine
:一个指向线程函数的指针,即新线程将要执行的函数。arg
:传递给start_routine
函数的参数。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回一个正的错误代码。
使用方法
以下是一个简单的例子,演示了如何使用 pthread_create
函数:
1 |
|
在这个例子中,我们首先定义了一个新线程将要执行的函数 thread_function
。
在 main
函数中,我们创建了一个新线程,并将 thread_function
作为线程的入口函数。
然后,我们使用 pthread_join
函数等待新线程结束,以确保在主线程中正确等待新线程的执行。
注意事项
pthread_create
创建一个新线程,并将其加入到调用进程的线程组中。- 使用
pthread_join
可以等待线程结束,以确保主线程在子线程结束后才会继续执行。
pthread_join (等待一个线程结束)
简单描述
pthread_join
函数用于等待一个特定线程的结束。
详细描述
pthread_join
函数会阻塞调用线程(一般是主线程),直到指定的线程结束为止。
函数原型
1 |
|
函数参数
thread
:要等待的线程的ID。retval
:一个指向指针的指针,用于存储被等待线程的返回值。
返回值
- 成功:返回0,表示线程等待成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序创建了一个新的线程,将其执行函数设为 print_message
,并传递了一个字符串作为参数。新线程将从 print_message
函数开始执行。
在 main
函数中,调用了 pthread_join
函数来等待线程结束,同时获取了线程的返回值。
注意事项
pthread_join
用于等待一个特定线程的结束,同时可以获取线程的返回值。- 如果不关心线程的返回值,可以将
retval
参数设置为NULL
。 - 使用
pthread_join
时,调用线程会被阻塞,直到指定的线程结束为止。
pthread_detach (将线程标记为分离状态)
简短描述
pthread_detach
函数用于将一个线程标记为分离状态,使得线程在退出时能够自动清理资源。后台运行,终止后则清除资源。
函数原型
1 |
|
参数
thread
:目标线程的标识符。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回错误代码。
使用方法
以下是一个简单的例子,演示了如何使用 pthread_detach
函数:
1 |
|
在这个例子中,我们首先创建了一个新的线程,并将其标记为分离状态,使得新线程在退出时能够自动清理资源。
新线程将执行 thread_function
函数,然后自动退出。
主线程不会等待新线程结束,而是会继续执行。
请注意,一旦线程被标记为分离状态,就不能再使用 pthread_join
函数等待它的结束了。
pthread_exit (终止调用的线程)
简短描述
pthread_exit
函数用于终止调用线程的执行,并返回一个指定的值。
函数原型
1 |
|
参数
value_ptr
:线程的返回值,可以是任意类型的指针。
返回值
- 无返回值。
使用方法
以下是一个简单的例子,演示了如何使用 pthread_exit
函数:
1 |
|
在这个例子中,我们首先定义了一个新线程将要执行的函数 thread_function
。
在 main
函数中,我们创建了一个新线程,并将 thread_function
作为线程的入口函数。
在 thread_function
中,我们使用 pthread_exit
函数退出线程,并返回一个值(这里是 42
)。
在主线程中,我们使用 pthread_join
函数等待新线程结束,并获取其返回值。
pthread_mutex_init (线程同步,初始化互斥锁)
简单描述
pthread_mutex_init
函数用于初始化互斥锁(Mutex)。
详细描述
互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_init
函数用于创建并初始化互斥锁,使其可用于线程间的同步。
函数原型
1 |
|
函数参数
mutex
:指向pthread_mutex_t
类型的互斥锁对象的指针,用于存储初始化后的互斥锁。attr
:指向pthread_mutexattr_t
类型的属性对象的指针,用于设置互斥锁的属性,通常传入NULL
表示使用默认属性。
返回值
- 成功:返回0,表示互斥锁初始化成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 pthread_mutex_init
初始化了一个互斥锁,并在主线程和子线程中使用该互斥锁来保护临界区,确保多个线程不会同时访问临界区。最后,使用 pthread_mutex_destroy
销毁了互斥锁。
注意事项
- 互斥锁是一种常用的线程同步工具,用于保护共享资源,防止多个线程同时访问。
- 在使用完互斥锁后,应使用
pthread_mutex_destroy
函数来销毁互斥锁,以释放相关资源。
pthread_mutex_destroy (销毁互斥锁)
简单描述
pthread_mutex_destroy
函数用于销毁互斥锁。
详细描述
互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_destroy
函数用于销毁一个已经初始化的互斥锁,释放相关资源。
函数原型
1 |
|
函数参数
mutex
:指向pthread_mutex_t
类型的互斥锁对象的指针。
返回值
- 成功:返回0,表示互斥锁销毁成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 pthread_mutex_init
初始化了一个互斥锁,然后在使用完互斥锁后,通过调用 pthread_mutex_destroy
销毁了它,释放相关资源。
注意事项
- 使用
pthread_mutex_destroy
可以销毁一个已经初始化的互斥锁,释放相关资源。 - 在销毁互斥锁之前,要确保没有线程在使用它。否则,会导致未定义的行为。
pthread_mutex_lock (锁定互斥锁)
简单描述
pthread_mutex_lock
函数用于锁定互斥锁。
详细描述
互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_lock
函数用于锁定一个互斥锁,以确保只有一个线程能够进入临界区。
函数原型
1 |
|
函数参数
mutex
:指向pthread_mutex_t
类型的互斥锁对象的指针。
返回值
- 成功:返回0,表示互斥锁成功锁定。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序创建了两个线程,它们会同时尝试锁定同一个互斥锁。因为互斥锁只允许一个线程进入临界区,所以一个线程会成功锁定,另一个线程会在 pthread_mutex_lock
处阻塞,直到第一个线程解锁互斥锁。
注意事项
- 使用
pthread_mutex_lock
可以锁定一个互斥锁,以确保只有一个线程能够进入临界区。 - 使用完互斥锁后,应该使用
pthread_mutex_unlock
函数来解锁互斥锁,以便其他线程可以进入临界区。
pthread_mutex_unlock (解锁互斥锁)
简单描述
pthread_mutex_unlock
函数用于解锁互斥锁。
详细描述
互斥锁是一种线程同步机制,用于确保在多个线程访问共享资源时,只有一个线程能够访问该资源。pthread_mutex_unlock
函数用于解锁一个互斥锁,以释放对临界区的控制,使其他线程可以访问共享资源。
函数原型
1 |
|
函数参数
mutex
:指向pthread_mutex_t
类型的互斥锁对象的指针。
返回值
- 成功:返回0,表示互斥锁成功解锁。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序创建了两个线程,它们会同时尝试锁定同一个互斥锁。一个线程成功锁定后,执行临界区代码,然后解锁互斥锁,使得另一个线程可以访问临界区。
注意事项
- 使用
pthread_mutex_unlock
可以解锁一个互斥锁,以释放对临界区的控制,使其他线程可以访问共享资源。 - 使用互斥锁时,必须遵守正确的加锁和解锁顺序,以避免死锁等问题。
sem_init (初始化一个信号量)
简单描述
sem_init
函数用于初始化一个信号量。
详细描述
信号量是一种用于线程间或进程间同步的机制,sem_init
用于初始化一个新的信号量,为其分配资源。
函数原型
1 |
|
函数参数
sem
:指向sem_t
类型的信号量对象的指针,用于存储初始化后的信号量。pshared
:指示信号量是在进程间共享还是在线程间共享的参数,通常传入 0 表示在线程间共享(也就是指在一个进程内部共享)。value
:信号量的初始值。
返回值
- 成功:返回0,表示信号量初始化成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 sem_init
初始化了一个信号量,并在程序中使用了它。最后,使用 sem_destroy
销毁了信号量。
注意事项
sem_init
用于初始化一个新的信号量,为其分配资源。- 在使用完信号量后,应该使用
sem_destroy
函数来销毁信号量,以释放相关资源。
sem_destroy (销毁一个信号量)
简单描述
sem_destroy
函数用于销毁一个信号量。
详细描述
信号量是一种用于线程间或进程间同步的机制,sem_destroy
用于销毁一个已经初始化的信号量,释放相关资源。
函数原型
1 |
|
函数参数
sem
:指向sem_t
类型的信号量对象的指针。
返回值
- 成功:返回0,表示信号量销毁成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 sem_init
初始化了一个信号量,并在程序中使用了它。最后,使用 sem_destroy
销毁了信号量。
注意事项
sem_destroy
用于销毁一个已经初始化的信号量,释放相关资源。- 在销毁信号量之前,要确保没有线程在使用它。否则,会导致未定义的行为。
sem_post (信号量加1)
简单描述
sem_post
函数用于增加信号量的值。
详细描述
信号量是一种用于线程间或进程间同步的机制,sem_post
用于增加信号量的值,表示某个资源变得可用。
函数原型
1 |
|
函数参数
sem
:指向sem_t
类型的信号量对象的指针。
返回值
- 成功:返回0,表示操作成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 sem_init
初始化了一个信号量,初始值为0。然后使用 sem_post
增加了信号量的值,表示某个资源变得可用。
注意事项
sem_post
用于增加信号量的值,表示某个资源变得可用。- 在使用信号量时,需要注意控制好资源的可用性,以确保在正确的时机调用
sem_post
函数。
sem_wait (信号量大于0则减1)
简单描述
sem_wait
函数用于等待信号量的值大于0,然后将其减少1。
详细描述
信号量是一种用于线程间或进程间同步的机制,sem_wait
用于等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。
函数原型
1 |
|
函数参数
sem
:指向sem_t
类型的信号量对象的指针。
返回值
- 成功:返回0,表示操作成功。
- 失败:返回一个非零的错误码,表示出现了错误。
使用方法
1 |
|
在上面的例子中,程序使用 sem_init
初始化了一个信号量,初始值为1。然后使用 sem_wait
函数等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。
注意事项
sem_wait
用于等待信号量的值大于0,然后将其减少1,表示消耗了一个资源。- 在使用信号量时,需要注意控制好资源的可用性,以确保在正确的时机调用
sem_wait
函数。
signal (绑定特定信号到行为)
简短描述
signal
函数用于设置信号处理函数,定义了程序在接收到特定信号时的行为。
函数原型
1 |
|
参数
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 |
|
在这个例子中,我们首先定义了一个名为 signal_handler
的自定义信号处理函数。
然后,我们使用 signal
函数来将 SIGINT
信号(由 Ctrl+C 发送)与该处理函数关联起来。
接着,我们进入一个无限循环,等待信号的到来。
当我们在终端中按下 Ctrl+C 时,会发送 SIGINT
信号,导致程序调用了我们定义的信号处理函数。
信号处理函数会打印出接收到的信号编号。
sigaction (处理特定信号函数)
简单描述
sigaction
函数用于设置对信号的处理方式,可以指定信号处理函数或者忽略信号。
详细描述
sigaction
函数允许程序员指定在接收到特定信号时应采取的操作。它允许程序员为每个信号指定一个处理函数,或者忽略信号,或者使用默认的处理方式。
函数原型
1 |
|
函数参数
signum
:要处理的信号的编号。act
:新的信号处理方式,是一个struct sigaction
结构体指针,其中包含了信号处理函数等信息。oldact
:用于存储之前的信号处理方式的结构体指针,可以为NULL
。
下面是 sigaction
结构体的定义:
1 | struct sigaction { |
接下来我将解释每个字段的含义:
sa_handler
:- 类型:
void (*)(int)
- 这是一个函数指针,用于指定当接收到信号时要执行的处理函数。通常情况下,它会接收一个整数作为参数,表示接收到的信号编号。
- 类型:
sa_sigaction
:- 类型:
void (*)(int, siginfo_t *, void *)
- 这是一个备用的信号处理函数。如果设置了该函数,它将会覆盖
sa_handler
。它接收三个参数,分别是接收到的信号编号、一个指向siginfo_t
结构体的指针(其中包含了关于信号的额外信息)、以及一个ucontext_t
结构体指针,它包含了当前线程的上下文信息。
- 类型:
sa_mask
:- 类型:
sigset_t
- 这是一个信号集,它指定了在执行信号处理函数时需要被屏蔽的信号。在信号处理函数执行期间,这些被屏蔽的信号将会被暂时阻塞,以防止它们再次被触发。
- 类型:
sa_flags
:- 类型:
int
- 用于指定一些标志来影响信号处理的行为,例如
SA_RESTART
可以在系统调用被信号中断后自动重启。
- 类型:
sa_restorer
:- 类型:
void (*)(void)
- 这是一个已弃用的字段,一般情况下不需要设置。
- 类型:
返回值
- 成功:0
- 失败:-1,同时设置
errno
来指示错误类型。
使用方法
1 |
|
在上面的例子中,程序安装了一个处理 SIGINT
信号的处理函数 signal_handler
。当程序接收到 SIGINT
信号时,将调用 signal_handler
函数来处理。请注意,signal_handler
可以是任何你定义的函数来处理信号。
注意事项
SIGKILL
和SIGSTOP
信号不能被捕获或者忽略。- 在信号处理函数中,尽量避免使用不可重入的函数,因为信号可能会在任何时候中断程序的正常执行。
sigaddset (添加信号到信号集)
简短描述
sigaddset
函数用于将指定的信号添加到信号集中。
函数原型
1 |
|
参数
set
:指向目标信号集的指针。signum
:要添加到信号集中的信号编号。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 sigaddset
函数:
1 |
|
在这个例子中,我们首先创建了一个 sigset_t
类型的变量 set
。
然后,我们使用 sigemptyset
函数将 set
初始化为空的信号集。
接着,我们使用 sigaddset
函数将 SIGINT
信号添加到 set
中。
sigemptyset (初始化空的信号集)
简短描述
sigemptyset
函数用于初始化一个空的信号集。
函数原型
1 |
|
参数
set
:指向要初始化的信号集的指针。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回 -1,并设置
errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 sigemptyset
函数:
1 |
|
在这个例子中,我们首先创建了一个 sigset_t
类型的变量 set
。
然后,我们使用 sigemptyset
函数将 set
初始化为空的信号集。
pthread_sigmask (操作信号屏蔽集)
简短描述
pthread_sigmask
函数用于检查和修改线程的信号屏蔽集(针对于当前线程)。
函数原型
1 |
|
参数
how
:操作模式,可以是以下值之一:SIG_BLOCK
:将set
中的信号添加到当前信号屏蔽集中。SIG_UNBLOCK
:从当前信号屏蔽集中移除set
中的信号。SIG_SETMASK
:用set
中的信号集替换当前信号屏蔽集。
set
:指向要操作的信号集的指针。oldset
:如果不为NULL
,将存储先前的信号屏蔽集。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回一个正的错误代码。
使用方法
以下是一个简单的例子,演示了如何使用 pthread_sigmask
函数:
1 |
|
在这个例子中,我们首先创建了一个新的线程,并在该线程中使用 pthread_sigmask
将 SIGINT
信号添加到线程的信号屏蔽集中。
这样,该线程将不会响应 SIGINT
信号。
在主线程中,我们没有修改信号屏蔽集,所以主线程可以响应 SIGINT
信号。
sigwait (等待指定信号)
简短描述
sigwait
函数用于等待指定的信号。
POSIX 标准建议在调用 sigwait 等待信号前,进程中所有线程都应屏蔽该信号,以保证只有 sigwait 的调用者获得该信号。
如果不屏蔽的话,在调用 sigwait 之前调用 pthread_kill 会出现 User defined signal 1.
函数原型
1 |
|
参数
set
:指向信号集的指针,指定要等待的信号。sig
:如果成功,将包含接收到的信号编号。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回错误代码。
使用方法
以下是一个简单的例子,演示了如何使用 sigwait
函数:
1 |
|
在这个例子中,我们首先创建了一个 sigset_t
类型的变量 set
。
然后,我们使用 sigemptyset
函数将 set
初始化为空的信号集,并使用 sigaddset
函数将 SIGINT
信号添加到 set
中。
接着,我们调用 sigwait
函数等待信号的到来。
一旦收到信号,sig
将包含信号的编号,然后我们可以进行相应的处理。
pthread_kill 函数
简短描述
pthread_kill
函数用于向指定的线程发送信号。
函数原型
1 |
|
参数
thread
:目标线程的标识符。sig
:要发送的信号编号。
返回值
- 如果成功,返回 0。
- 如果出现错误,返回错误代码。
使用方法
以下是一个简单的例子,演示了如何使用 pthread_kill
函数:
1 |
|
在这个例子中,我们首先创建了一个新的线程,并在该线程中循环打印一条消息。
在主线程中,我们使用 pthread_kill
函数向新线程发送了 SIGINT
信号。
这样,新线程会收到信号并相应地做出处理(在这个例子中,会终止线程)。
请注意,pthread_kill
函数用于向线程发送信号,而不是进程。如果要向进程发送信号,可以使用 kill
函数。
其它函数
gethostbyname (通过主机名获取主机的信息)
简短描述
gethostbyname
函数用于通过主机名获取主机的信息,包括 IP 地址。
注意:
gethostbyname
是一个旧的函数,不推荐使用。更现代的函数是getaddrinfo
。
函数原型
1 |
|
参数
name
:要查询的主机名。
返回值
- 如果成功,返回指向
struct hostent
结构的指针,其中包含了与主机名相关的信息,包括 IP 地址。 - 如果出现错误,返回
NULL
,并设置h_errno
来指示错误类型。
struct hostent
hostent
结构体是用于存储主机信息的数据结构,通常用于获取主机的域名解析信息。它在网络编程中很常用。
以下是 hostent
结构体的定义:
1 | struct hostent { |
下面是对 hostent
结构体成员的详细解释:
char *h_name
:- 这是主机的正式域名。通常情况下,它是主机的正式域名,例如 “www.example.com“。
char **h_aliases
:- 这是一个指向指针数组的指针,指向一个主机的别名列表。通常情况下,它包括了其他可能被用来指代相同主机的域名。列表的末尾会以一个空指针(NULL)作为结束标志。
int h_addrtype
:- 这是地址的类型,通常是
AF_INET
(IPv4)或AF_INET6
(IPv6)。
- 这是地址的类型,通常是
int h_length
:- 这是地址的字节长度。对于IPv4地址,通常是4,对于IPv6地址,通常是16。
char **h_addr_list
:- 这是一个指向指针数组的指针,数组中的每个指针都指向一个主机地址(以二进制形式)。通常,它指向一个或多个IPv4或IPv6地址。
注意:
h_addr_list
中的地址以二进制形式存储。要将其转换为可读的点分十进制字符串,可以使用inet_ntoa
或inet_ntop
函数。- 在实际编程中,通常会使用函数如
gethostbyname
或getaddrinfo
来填充并返回一个hostent
结构体。这样可以根据域名获取主机的相关信息。
示例:
1 | struct hostent *host = gethostbyname("www.example.com"); |
这个示例演示了如何使用 gethostbyname
函数获取主机信息并访问 hostent
结构体的各个成员。
h_errno
h_errno
是一个全局的整型变量,通常用于存储与网络主机操作相关的错误码。它是在标准C库 <netdb.h>
中定义的一个外部变量。
当网络相关的操作(比如域名解析)发生错误时,系统会将相应的错误码设置到 h_errno
中,以便程序员可以检查错误类型并采取相应的措施。
以下是一些可能的错误码:
HOST_NOT_FOUND
:找不到主机。NO_DATA
:主机存在,但无相应的记录(例如,找不到IPv6记录)。NO_RECOVERY
:非可恢复错误。TRY_AGAIN
:可恢复错误,通常表示需要重试操作。
示例:
1 |
|
在上述示例中,如果 gethostbyname
函数返回空指针,我们检查 h_errno
的值以确定出现了什么类型的错误,并输出相应的错误信息。
使用方法
以下是一个简单的例子,演示了如何使用 gethostbyname
函数获取主机的信息:
1 |
|
在这个例子中,我们首先指定了要查询的主机名为 “www.example.com“。
然后,我们使用 gethostbyname
函数来获取与该主机名相关的信息。
如果成功,gethostbyname
将返回一个指向 struct hostent
结构的指针,其中包含了主机的各种信息。
我们通过 h_name
字段获取官方名称,通过 h_addr_list
数组获取IP地址信息,并使用 inet_ntoa
函数将其转换为字符串格式。
getaddrinfo (将主机名和服务名解析为一个或多个套接字地址,替代gethostbyname)
简短描述
getaddrinfo
函数用于将主机名和服务名解析为一个或多个套接字地址。
函数原型
1 |
|
参数
node
:要解析的主机名或IP地址字符串。service
:要解析的服务名或端口号字符串。hints
:一个struct addrinfo
结构的指针,用于指定解析的选项,可以设置为NULL
使用默认选项。res
:一个指向struct addrinfo*
的指针,用于存储解析结果。
返回值
- 如果成功,返回0。
- 如果出现错误,返回非零值,可以使用
gai_strerror
函数将错误码转换为字符串。
使用方法
以下是一个简单的例子,演示了如何使用 getaddrinfo
函数来解析主机名和服务名:
1 |
|
在这个例子中,我们首先指定了要解析的主机名和服务名。
然后,我们定义了一个 struct addrinfo
结构的变量 hints
,并设置了一些解析选项,例如允许 IPv4 或 IPv6、使用流套接字等。
接着,我们使用 getaddrinfo
函数来解析主机名和服务名,将结果存储在 result
中。
我们使用一个循环遍历解析结果,并通过 getnameinfo
函数获取主机名和端口号,并打印出来。
最后,我们通过 freeaddrinfo
函数释放了解析结果的内存。
gethostbyaddr (通过 IP 地址获取主机的信息)
简短描述
gethostbyaddr
函数用于通过 IP 地址获取主机的信息,包括主机名。
注意:
gethostbyaddr
是一个旧的函数,不推荐使用。更现代的函数是getnameinfo
。
函数原型
1 |
|
参数
addr
:指向包含要查询的IP地址的结构的指针。len
:指定了addr
结构的长度。type
:指定了addr
结构的类型,通常为AF_INET
。
返回值
- 如果成功,返回指向
struct hostent
结构的指针,其中包含了与IP地址相关的信息,包括主机名。 - 如果出现错误,返回
NULL
,并设置h_errno
来指示错误类型。
使用方法
以下是一个简单的例子,演示了如何使用 gethostbyaddr
函数通过IP地址获取主机的信息:
1 |
|
在这个例子中,我们首先将点分十进制的IP地址字符串转换为网络字节序的整数形式。
然后,我们使用 gethostbyaddr
函数来获取与该IP地址相关的信息。
如果成功,gethostbyaddr
将返回一个指向 struct hostent
结构的指针,其中包含了主机的各种信息。
我们通过 h_name
字段获取官方名称。
希望这个例子能帮到你理解 gethostbyaddr
函数的基本用法!如果你有任何其他问题,随时问吧。
getnameinfo (将套接字地址结构转换为主机名和服务名,替代gethostbyaddr)
简短描述
getnameinfo
函数用于将套接字地址结构转换为主机名和服务名。
函数原型
1 |
|
参数
addr
:指向包含套接字地址信息的结构的指针。addrlen
:addr
结构的长度。host
:指向存储主机名的缓冲区的指针。hostlen
:缓冲区的大小,即最多接受的字符数。serv
:指向存储服务名的缓冲区的指针。servlen
:缓冲区的大小,即最多接受的字符数。flags
:用于控制转换的标志,通常可以设置为0。
返回值
- 如果成功,返回0。
- 如果出现错误,返回非零值,可以使用
gai_strerror
函数将错误码转换为字符串。
使用方法
以下是一个简单的例子,演示了如何使用 getnameinfo
函数将套接字地址转换为主机名和服务名:
1 |
|
在这个例子中,我们首先定义了一个 struct sockaddr_in
结构 sa
,并设置了其中的成员,包括地址族、端口号和IP地址。
然后,我们使用 getnameinfo
函数将 sa
结构转换为主机名和服务名。
转换成功后,主机名将存储在 host
中,服务名将存储在 serv
中。
最后,我们将它们打印出来。