From POSIX.1-2008/2013 documentation of shutdown():
从POSIX.1-2008 / 2013下载shutdown()的文档:
int shutdown(int socket, int how);
int shutdown(int socket,int how);
...
...
The
shutdown()
function shall cause all or part of a full-duplex connection on the socket associated with the file descriptor socket to be shut down.shutdown()函数将导致与文件描述符套接字关联的套接字上的全双工连接的全部或部分关闭。
The
shutdown()
function takes the following arguments:shutdown()函数采用以下参数:
socket
Specifies the file descriptor of the socket.socket指定套接字的文件描述符。
how
Specifies the type of shutdown. The values are as follows:how指定关闭的类型。值如下:
SHUT_RD
Disables further receive operations.- SHUT_RD禁用进一步的接收操作。
SHUT_WR
Disables further send operations.- SHUT_WR禁用进一步的发送操作。
SHUT_RDWR
Disables further send and receive operations.- SHUT_RDWR禁用进一步的发送和接收操作。
...
...
The manual page for shutdown(2)
says pretty much the same thing.
关机(2)的手册页几乎是一样的。
The
shutdown()
call causes all or part of a full-duplex connection on the socket associated with sockfd to be shut down. Ifhow
isSHUT_RD
, further receptions will be disallowed. Ifhow
isSHUT_WR
, further transmissions will be disallowed. Ifhow
isSHUT_RDWR
, further receptions and transmissions will be disallowed.shutdown()调用会导致与sockfd关联的套接字上的全双工连接全部或部分关闭。如果SHUT_RD如何,将不允许进一步接收。如果SHUT_WR如何,则不允许进一步传输。如果SHUT_RDWR如何,将不允许进一步的接收和传输。
But I think I am able to receive data even after a shutdown(sockfd, SHUT_RD)
call. Here is the test I orchestrated and the results I observed.
但我认为即使在关闭(sockfd,SHUT_RD)调用之后我也能够接收数据。这是我精心策划的测试和我观察到的结果。
------------------------------------------------------
Time netcat (nc) C (a.out) Result Observed
------------------------------------------------------
0 s listen - -
2 s connect() -
4 s send "aa" - -
6 s - recv() #1 recv() #1 receives "aa"
8 s - shutdown() -
10 s send "bb" - -
12 s - recv() #2 recv() #2 receives "bb"
14 s - recv() #3 recv() #3 returns 0
16 s - recv() #4 recv() #4 returns 0
18 s send "cc" - -
20 s - recv() #5 recv() #5 receives "cc"
22 s - recv() #6 recv() #6 returns 0
------------------------------------------------------
Here is a brief description of the above table.
以下是上表的简要说明。
- Time: Time elapsed (in seconds) since the beginning of the test.
- 时间:自测试开始以来经过的时间(以秒为单位)。
- netcat (nc): Steps performed via netcat (nc). Netcat was made to listen on port 8888 and accept TCP connections from my C program compiled to ./a.out. Netcat plays the role of the server here. It sends three messages "aa", "bb" and "cc" to the C program after 4s, 10s and 18s, respectively, have elapsed.
- netcat(nc):通过netcat(nc)执行的步骤。 Netcat用于侦听端口8888并接受来自编译为./a.out的C程序的TCP连接。 Netcat在这里扮演服务器的角色。它分别在4s,10s和18s之后向C程序发送三条消息“aa”,“bb”和“cc”。
- C (a.out): Steps performed by my C program compiled to ./a.out. It performs 6 recv() calls after 6s, 12s, 14s, 16s, 20s and 22s have elapsed.
- C(a.out):由我的C程序执行的步骤编译为./a.out。它在经过6s,12s,14s,16s,20s和22s之后执行6次recv()调用。
-
Result observed: The result observed in the output of the C program. It shows that it is able to recv() the message "bb" that was sent after
shutdown()
completed successfully. See rows for "12 s" and "20 s". - 观察结果:在C程序的输出中观察到的结果。它表明它能够recv()在shutdown()成功完成后发送的消息“bb”。查看“12 s”和“20 s”的行。
Here is the C program (client program).
这是C程序(客户端程序)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
int main()
{
struct addrinfo hints, *ai;
int sockfd;
int ret;
ssize_t bytes;
char buffer[1024];
/* Select TCP/IPv4 address only. */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((ret = getaddrinfo("localhost", "8888", &hints, &ai)) == -1) {
printf("getaddrinfo() error: %s\n", gai_strerror(ret));
return EXIT_FAILURE;
}
if ((sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
printf("socket() error: %s\n", strerror(errno));
return EXIT_FAILURE;
}
/* Connect to localhost:8888. */
sleep(2);
if ((connect(sockfd, ai->ai_addr, ai->ai_addrlen)) == -1) {
printf("connect() error: %s\n", strerror(errno));
return EXIT_FAILURE;
}
freeaddrinfo(ai);
/* Test 1: Receive before shutdown. */
sleep(4);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #1 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
sleep(2);
if (shutdown(sockfd, SHUT_RD) == -1) {
printf("shutdown() error: %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("shutdown() complete\n");
/* Test 2: Receive after shutdown. */
sleep (4);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #2 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
/* Test 3. */
sleep (2);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #3 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
/* Test 4. */
sleep (2);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #4 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
/* Test 5. */
sleep (4);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #5 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
/* Test 6. */
sleep (2);
bytes = recv(sockfd, buffer, 1024, 0);
printf("recv() #6 returned %d bytes: %.*s\n", (int) bytes, (int) bytes, buffer);
}
The above code was saved in a file named foo.c
.
上面的代码保存在名为foo.c的文件中。
Here is a tiny shell script that compiles and runs the above program and invokes netcat (nc
) to listen on port 8888 and respond to the client with messages aa
, bb
and cc
at specific intervals as per the table shown above. The following shell script is saved in a file called run.sh
.
这是一个小的shell脚本,它编译并运行上面的程序,并调用netcat(nc)监听端口8888,并按照上面的表格以特定的间隔响应客户端消息aa,bb和cc。以下shell脚本保存在名为run.sh的文件中。
set -ex
gcc -std=c99 -pedantic -Wall -Wextra -D_POSIX_C_SOURCE=200112L foo.c
./a.out &
(sleep 4; printf aa; sleep 6; printf bb; sleep 8; printf cc) | nc -vvlp 8888
When the above shell script is run, the following output is observed.
运行上述shell脚本时,会观察到以下输出。
$ sh run.sh
+ gcc -std=c99 -pedantic -Wall -Wextra -D_POSIX_C_SOURCE=200112L foo.c
+ nc -vvlp 8888
+ sleep 4
listening on [any] 8888 ...
+ ./a.out
connect to [127.0.0.1] from localhost [127.0.0.1] 54208
+ printf aa
+ sleep 6
recv() #1 returned 2 bytes: aa
shutdown() complete
+ printf bb
+ sleep 8
recv() #2 returned 2 bytes: bb
recv() #3 returned 0 bytes:
recv() #4 returned 0 bytes:
+ printf cc
recv() #5 returned 2 bytes: cc
recv() #6 returned 0 bytes:
sent 6, rcvd 0
The output shows that the C program is able to receive messages with recv()
even after it has called shutdown()
. The only behaviour that the shutdown()
call seems to have affected is whether the recv()
call returns immediately or gets blocked waiting for the next message. Normally, before shutdown()
, the recv()
call would wait for a message to arrive. But after the shutdown()
call, recv()
returns 0
immediately when there is no new message.
输出显示C程序即使在调用shutdown()之后也能够使用recv()接收消息。 shutdown()调用似乎影响的唯一行为是recv()调用是立即返回还是被阻塞等待下一条消息。通常,在shutdown()之前,recv()调用将等待消息到达。但是在shutdown()调用之后,当没有新消息时,recv()会立即返回0。
I was expecting all recv()
calls after shutdown()
to fail in some way (say, return -1
) due to the documentation I have quoted above.
由于我上面引用的文档,我期望在shutdown()之后的所有recv()调用以某种方式失败(比如返回-1)。
Two questions:
两个问题:
- Is the behaviour observed in my experiment, i.e.
recv()
call being able to receive new messages sent aftershutdown()
call correct as per the POSIX standard and the manual page forshutdown(2)
that I have quoted above? - 在我的实验中观察到的行为,即recv()调用是否能够接收在shutdown()调用后根据POSIX标准发送的新消息以及我在上面引用的shutdown(2)的手册页?
- Why is it that after
shutdown()
is called,recv()
returns0
immediately instead of waiting for a new message to arrive? - 为什么在调用shutdown()之后,recv()立即返回0而不是等待新消息到达?
1 个解决方案
#1
6
You asked two questions: Is it compliant with the posix standard, and why does recv
return 0 instead of blocking.
你问了两个问题:它是否符合posix标准,为什么recv返回0而不是阻塞。
Standard for shutdown
停机标准
The documentation for shutdown
says:
关机文件说:
The shutdown() function disables subsequent send and/or receive operations on a socket, depending on the value of the how argument.
shutdown()函数禁用套接字上的后续发送和/或接收操作,具体取决于how参数的值。
This appears to imply that no further read
calls will return any data.
这似乎意味着没有进一步的读取调用将返回任何数据。
However the documention for recv
states:
然而,recv的文件说明:
If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0.
如果没有可接收的消息且对等体已执行有序关闭,则recv()将返回0。
Reading these together this could mean that after the remote peer calls shutdown
一起阅读这些可能意味着在远程对等方呼叫关闭之后
- calls to
recv
should return an error if data is available, or - 如果数据可用,则调用recv应该返回错误,或者
- calls to
recv
can continue to return data aftershutdown
if "messages are available to be received". - 如果“可以接收消息”,则在关闭后调用recv可以继续返回数据。
While this is somewhat ambiguous, the first interpretation does not make sense, as it's not clear what purpose an error would serve. Therefore the correct interpretation is the second.
虽然这有点含糊不清,但第一种解释没有意义,因为不清楚错误的用途是什么。因此,正确的解释是第二个。
(Note that any protocol which buffers at any point in the stack might have data in transit which cannot yet be read. The semantics of shutdown
enable you to still receive this data after calling shutdown
.)
(请注意,任何在堆栈中任何位置缓冲的协议都可能包含传输中尚无法读取的数据。关闭语义使您在调用shutdown后仍能接收此数据。)
However this refers to the peer calling shutdown
, rather than the calling process. Should this also apply if the calling process called shutdown
?
然而,这指的是对等调用关闭,而不是调用进程。如果调用进程调用shutdown,这是否也适用?
So is it compliant or what
它是合规的还是什么的
The standard is ambiguous.
标准含糊不清。
If a process calling shutdown(fd, SHUT_RD)
is to be considered equivalent to the peer calling shutdown(fd, SHUT_WR)
then it is compliant.
如果一个进程调用shutdown(fd,SHUT_RD)被认为等同于对等调用shutdown(fd,SHUT_WR),那么它是兼容的。
On the other hand, reading the text strictly, it seems not to be compliant. But then there is no error code for the case where a process calls recv
after shutdown(SHUT_RD)
. The error codes are exhaustive, which implies that this scenario is not an error, so should return 0
as in the corresponding situation where the peer calls shutdown(SHUT_WR)
.
另一方面,严格阅读文本,似乎不符合要求。但是,在关闭(SHUT_RD)之后进程调用recv的情况下没有错误代码。错误代码是详尽的,这意味着此方案不是错误,因此应该返回0,就像对等方调用shutdown(SHUT_WR)的相应情况一样。
Nevertheless, this is the behaviour you want - message in transit can be received if you want them. If you don't want to them don't call recv
.
然而,这是您想要的行为 - 如果您需要,可以接收传输中的消息。如果你不想要他们不要叫recv。
To the extent that this is ambiguous, it should be considered a bug in the standard.
如果这是不明确的,则应将其视为标准中的错误。
Why isn't post-shutdown
recv
data limited to data which was in transit
为什么关闭后的recv数据不限于传输中的数据
In the general case it is not possible to know what data is in transit.
在一般情况下,无法知道传输中的数据。
- In the case of unix sockets, data may be buffered on the receiving side, by the operating system, or on the sending side.
- 在unix套接字的情况下,数据可以在接收方,操作系统或发送方缓冲。
- In the case of TCP, data may be buffered by the receiving process, by the operating system, by the network card hardware buffer, packets may be in transit at intermediate routers, buffered by the sending network card hardware, by sending operating system or sending process.
- 在TCP的情况下,数据可以由接收过程,操作系统,网卡硬件缓冲器缓冲,分组可以在中间路由器中传输,由发送网卡硬件缓冲,通过发送操作系统或发送处理。
Background
背景
-
posix provides an api for uniformly interacting with different types of streams, including anonymous pipes, named pipes, and IPv4 and IPv6 TCP and UDP sockets... and raw Ethernet, and Token Ring and IPX/SPX, and X.25 and ATM...
posix提供了一个api,用于与不同类型的流统一交互,包括匿名管道,命名管道,IPv4和IPv6 TCP和UDP套接字......以及原始以太网,令牌环和IPX / SPX,以及X.25和ATM。 ..
-
Therefore posix provides a set of functionality which broadly covers the main capabilities of most streaming and packet-based protocols.
因此,posix提供了一组功能,广泛涵盖了大多数流和基于数据包的协议的主要功能。
-
However not every capability is supported by ever protocol
然而,并非所有协议都支持所有功能
From a design point of view, if a caller requests an operation which is not supported by the underlying protocol, there are a number of options:
从设计的角度来看,如果调用者请求底层协议不支持的操作,则有许多选项:
-
Enter an error state, and forbid any further operations on the file descriptor.
输入错误状态,并禁止对文件描述符执行任何进一步操作。
-
Return an error from the call, but otherwise disregard it.
从通话中返回错误,但忽略它。
-
Return success, and do the nearest thing that makes sense.
返回成功,并做最接近的事情。
-
Implement some sort of wrapper or filler to provide the missing functionality.
实现某种包装器或填充器以提供缺少的功能。
The first two options are precluded by the posix standard. Clearly the third option has been chosen by Linux developers.
posix标准排除了前两个选项。显然,Linux开发人员已经选择了第三个选项。
#1
6
You asked two questions: Is it compliant with the posix standard, and why does recv
return 0 instead of blocking.
你问了两个问题:它是否符合posix标准,为什么recv返回0而不是阻塞。
Standard for shutdown
停机标准
The documentation for shutdown
says:
关机文件说:
The shutdown() function disables subsequent send and/or receive operations on a socket, depending on the value of the how argument.
shutdown()函数禁用套接字上的后续发送和/或接收操作,具体取决于how参数的值。
This appears to imply that no further read
calls will return any data.
这似乎意味着没有进一步的读取调用将返回任何数据。
However the documention for recv
states:
然而,recv的文件说明:
If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0.
如果没有可接收的消息且对等体已执行有序关闭,则recv()将返回0。
Reading these together this could mean that after the remote peer calls shutdown
一起阅读这些可能意味着在远程对等方呼叫关闭之后
- calls to
recv
should return an error if data is available, or - 如果数据可用,则调用recv应该返回错误,或者
- calls to
recv
can continue to return data aftershutdown
if "messages are available to be received". - 如果“可以接收消息”,则在关闭后调用recv可以继续返回数据。
While this is somewhat ambiguous, the first interpretation does not make sense, as it's not clear what purpose an error would serve. Therefore the correct interpretation is the second.
虽然这有点含糊不清,但第一种解释没有意义,因为不清楚错误的用途是什么。因此,正确的解释是第二个。
(Note that any protocol which buffers at any point in the stack might have data in transit which cannot yet be read. The semantics of shutdown
enable you to still receive this data after calling shutdown
.)
(请注意,任何在堆栈中任何位置缓冲的协议都可能包含传输中尚无法读取的数据。关闭语义使您在调用shutdown后仍能接收此数据。)
However this refers to the peer calling shutdown
, rather than the calling process. Should this also apply if the calling process called shutdown
?
然而,这指的是对等调用关闭,而不是调用进程。如果调用进程调用shutdown,这是否也适用?
So is it compliant or what
它是合规的还是什么的
The standard is ambiguous.
标准含糊不清。
If a process calling shutdown(fd, SHUT_RD)
is to be considered equivalent to the peer calling shutdown(fd, SHUT_WR)
then it is compliant.
如果一个进程调用shutdown(fd,SHUT_RD)被认为等同于对等调用shutdown(fd,SHUT_WR),那么它是兼容的。
On the other hand, reading the text strictly, it seems not to be compliant. But then there is no error code for the case where a process calls recv
after shutdown(SHUT_RD)
. The error codes are exhaustive, which implies that this scenario is not an error, so should return 0
as in the corresponding situation where the peer calls shutdown(SHUT_WR)
.
另一方面,严格阅读文本,似乎不符合要求。但是,在关闭(SHUT_RD)之后进程调用recv的情况下没有错误代码。错误代码是详尽的,这意味着此方案不是错误,因此应该返回0,就像对等方调用shutdown(SHUT_WR)的相应情况一样。
Nevertheless, this is the behaviour you want - message in transit can be received if you want them. If you don't want to them don't call recv
.
然而,这是您想要的行为 - 如果您需要,可以接收传输中的消息。如果你不想要他们不要叫recv。
To the extent that this is ambiguous, it should be considered a bug in the standard.
如果这是不明确的,则应将其视为标准中的错误。
Why isn't post-shutdown
recv
data limited to data which was in transit
为什么关闭后的recv数据不限于传输中的数据
In the general case it is not possible to know what data is in transit.
在一般情况下,无法知道传输中的数据。
- In the case of unix sockets, data may be buffered on the receiving side, by the operating system, or on the sending side.
- 在unix套接字的情况下,数据可以在接收方,操作系统或发送方缓冲。
- In the case of TCP, data may be buffered by the receiving process, by the operating system, by the network card hardware buffer, packets may be in transit at intermediate routers, buffered by the sending network card hardware, by sending operating system or sending process.
- 在TCP的情况下,数据可以由接收过程,操作系统,网卡硬件缓冲器缓冲,分组可以在中间路由器中传输,由发送网卡硬件缓冲,通过发送操作系统或发送处理。
Background
背景
-
posix provides an api for uniformly interacting with different types of streams, including anonymous pipes, named pipes, and IPv4 and IPv6 TCP and UDP sockets... and raw Ethernet, and Token Ring and IPX/SPX, and X.25 and ATM...
posix提供了一个api,用于与不同类型的流统一交互,包括匿名管道,命名管道,IPv4和IPv6 TCP和UDP套接字......以及原始以太网,令牌环和IPX / SPX,以及X.25和ATM。 ..
-
Therefore posix provides a set of functionality which broadly covers the main capabilities of most streaming and packet-based protocols.
因此,posix提供了一组功能,广泛涵盖了大多数流和基于数据包的协议的主要功能。
-
However not every capability is supported by ever protocol
然而,并非所有协议都支持所有功能
From a design point of view, if a caller requests an operation which is not supported by the underlying protocol, there are a number of options:
从设计的角度来看,如果调用者请求底层协议不支持的操作,则有许多选项:
-
Enter an error state, and forbid any further operations on the file descriptor.
输入错误状态,并禁止对文件描述符执行任何进一步操作。
-
Return an error from the call, but otherwise disregard it.
从通话中返回错误,但忽略它。
-
Return success, and do the nearest thing that makes sense.
返回成功,并做最接近的事情。
-
Implement some sort of wrapper or filler to provide the missing functionality.
实现某种包装器或填充器以提供缺少的功能。
The first two options are precluded by the posix standard. Clearly the third option has been chosen by Linux developers.
posix标准排除了前两个选项。显然,Linux开发人员已经选择了第三个选项。