为什么客户端调用shutdown(sockfd, SHUT_RD)后,客户端程序中的recv()可以接收到发送给客户端的消息?
Why can recv() in the client program receive messages sent to the client after the client has invoked shutdown(sockfd, SHUT_RD)?
来自 POSIX.1-2008/2013 documentation of shutdown():
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.
The shutdown()
function takes the following arguments:
socket
Specifies the file descriptor of the socket.
how
Specifies the type of shutdown. The values are as follows:
SHUT_RD
Disables further receive operations.
SHUT_WR
Disables further send operations.
SHUT_RDWR
Disables further send and receive operations.
...
手册页
shutdown(2)
说的差不多。
The shutdown()
call causes all or part of a full-duplex connection
on the socket associated with sockfd to be shut down. If how
is
SHUT_RD
, further receptions will be disallowed. If how
is SHUT_WR
,
further transmissions will be disallowed. If how
is SHUT_RDWR
,
further receptions and transmissions will be disallowed.
但我认为即使在
shutdown(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
------------------------------------------------------
这里简单介绍一下上面的table.
- 时间: 自测试开始以来经过的时间(以秒为单位)。
- netcat (nc): 通过 netcat (nc) 执行的步骤。 Netcat 是用来听的
在端口 8888 上并接受来自编译为的 C 程序的 TCP 连接
./a.out. Netcat 在这里扮演服务器的角色。它发送三个
在 4 秒、10 秒和 18 秒后向 C 程序发送消息 "aa"、"bb" 和 "cc",
分别过去了。
- C (a.out): 我的 C 程序执行的步骤编译为 ./a.out。它
在 6s、12s、14s、16s、20s 和 22s 之后执行 6 次 recv() 调用
过去了。
- 观察到的结果:C程序输出中观察到的结果。
它表明它能够 recv() 已发送的消息 "bb"
shutdown()
成功完成后。查看“12 s”行和
“20 秒”。
这里是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);
}
以上代码保存在名为 foo.c
.
的文件中
这是一个很小的 shell 脚本,它编译并 运行 上面的程序和
调用 netcat (nc
) 监听 8888 端口并响应客户端
根据 table 在特定时间间隔发送消息 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
当上面的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
输出显示 C 程序能够接收消息
recv()
即使在调用 shutdown()
之后。唯一的行为
shutdown()
调用似乎影响了 recv()
立即呼叫 returns 或被阻止等待下一条消息。
通常,在 shutdown()
之前,recv()
调用会等待一个
消息到达。但是在 shutdown()
调用之后,recv()
returns 0
当没有新消息时立即。
我原以为 shutdown()
之后的所有 recv()
调用都会以某种方式失败(比如,
return -1
) 由于我在上面引用的文档。
两个问题:
- 是在我的实验中观察到的行为,即
recv()
能够接收 shutdown()
调用正确后发送的新消息
我拥有的 POSIX 标准和 shutdown(2)
的手册页
上面引用?
- 为什么调用
shutdown()
后,recv()
returns0
,而不是等待新消息到达?
你问了两个问题:是否符合posix标准,为什么recv
return 0而不是阻塞
关机标准
shutdown
的文档说:
The shutdown() function disables subsequent send and/or receive operations on a socket, depending on the value of the how argument.
这似乎意味着进一步的 read
调用不会 return 任何数据。
但是 recv
的文档指出:
If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0.
一起阅读这些内容可能意味着在远程对等方调用 shutdown
之后
- 如果数据可用,调用
recv
应该 return 一个错误,或者
- 如果 "messages are available to be received".
,对 recv
的调用可以在 shutdown
之后继续 return 数据
虽然这有点模棱两可,但第一种解释没有意义,因为不清楚错误的用途。所以正确的解释是第二种。
(请注意,在堆栈中的任何位置进行缓冲的任何协议都可能具有传输中的数据,这些数据尚无法读取。shutdown
的语义使您能够在调用 shutdown
.)
然而,这是指对等方调用 shutdown
,而不是调用进程。如果调用进程调用 shutdown
,这是否也适用?
那么合规还是什么
标准不明确。
如果进程调用 shutdown(fd, SHUT_RD)
被认为等同于 peer 调用 shutdown(fd, SHUT_WR)
那么它是兼容的。
另一方面,严格看文,好像不太符合。但是对于进程在 shutdown(SHUT_RD)
之后调用 recv
的情况,没有错误代码。错误代码是详尽的,这意味着这种情况不是错误,所以应该 return 0
就像对等方调用 shutdown(SHUT_WR)
.
的相应情况一样
不过,这就是您想要的行为 - 如果您需要,可以接收传输中的消息。如果你不想他们不要打电话给 recv
。
在某种程度上这是模棱两可的,它应该被认为是标准中的一个错误。
为什么 post-shutdown
recv
数据不限于传输中的数据
在一般情况下,无法知道传输中的数据是什么。
- 在 unix 套接字的情况下,数据可能在接收方、操作系统或发送方进行缓冲。
- 在TCP的情况下,数据可能被接收进程、操作系统、网卡硬件缓冲区缓冲,数据包可能在中间路由器传输,被发送网卡硬件缓冲,通过发送操作系统或发送进程。
背景
posix 提供了一个 api 用于与不同类型的流进行统一交互,包括匿名管道、命名管道以及 IPv4 和 IPv6 TCP 和 UDP 套接字...和原始以太网、令牌环和 IPX/SPX、X.25 和 ATM...
因此 posix 提供了一组功能,广泛涵盖了大多数流媒体和基于数据包的协议的主要功能。
然而,并非所有协议都支持所有功能
从设计的角度来看,如果调用者请求底层协议不支持的操作,有多种选择:
进入错误状态,并禁止对文件描述符进行任何进一步的操作。
Return 调用错误,否则忽略它。
Return 成功,做最近有意义的事情。
实施某种包装器或填充器以提供缺少的功能。
posix 标准排除了前两个选项。显然,Linux 开发人员选择了第三个选项。
来自 POSIX.1-2008/2013 documentation of shutdown():
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.The
shutdown()
function takes the following arguments:
socket
Specifies the file descriptor of the socket.
how
Specifies the type of shutdown. The values are as follows:
SHUT_RD
Disables further receive operations.SHUT_WR
Disables further send operations.SHUT_RDWR
Disables further send and receive operations....
手册页
shutdown(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)
打电话。这是我精心策划的测试
我观察到的结果。
------------------------------------------------------
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
------------------------------------------------------
这里简单介绍一下上面的table.
- 时间: 自测试开始以来经过的时间(以秒为单位)。
- netcat (nc): 通过 netcat (nc) 执行的步骤。 Netcat 是用来听的 在端口 8888 上并接受来自编译为的 C 程序的 TCP 连接 ./a.out. Netcat 在这里扮演服务器的角色。它发送三个 在 4 秒、10 秒和 18 秒后向 C 程序发送消息 "aa"、"bb" 和 "cc", 分别过去了。
- C (a.out): 我的 C 程序执行的步骤编译为 ./a.out。它 在 6s、12s、14s、16s、20s 和 22s 之后执行 6 次 recv() 调用 过去了。
- 观察到的结果:C程序输出中观察到的结果。
它表明它能够 recv() 已发送的消息 "bb"
shutdown()
成功完成后。查看“12 s”行和 “20 秒”。
这里是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);
}
以上代码保存在名为 foo.c
.
这是一个很小的 shell 脚本,它编译并 运行 上面的程序和
调用 netcat (nc
) 监听 8888 端口并响应客户端
根据 table 在特定时间间隔发送消息 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
当上面的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
输出显示 C 程序能够接收消息
recv()
即使在调用 shutdown()
之后。唯一的行为
shutdown()
调用似乎影响了 recv()
立即呼叫 returns 或被阻止等待下一条消息。
通常,在 shutdown()
之前,recv()
调用会等待一个
消息到达。但是在 shutdown()
调用之后,recv()
returns 0
当没有新消息时立即。
我原以为 shutdown()
之后的所有 recv()
调用都会以某种方式失败(比如,
return -1
) 由于我在上面引用的文档。
两个问题:
- 是在我的实验中观察到的行为,即
recv()
能够接收shutdown()
调用正确后发送的新消息 我拥有的 POSIX 标准和shutdown(2)
的手册页 上面引用? - 为什么调用
shutdown()
后,recv()
returns0
,而不是等待新消息到达?
你问了两个问题:是否符合posix标准,为什么recv
return 0而不是阻塞
关机标准
shutdown
的文档说:
The shutdown() function disables subsequent send and/or receive operations on a socket, depending on the value of the how argument.
这似乎意味着进一步的 read
调用不会 return 任何数据。
但是 recv
的文档指出:
If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0.
一起阅读这些内容可能意味着在远程对等方调用 shutdown
- 如果数据可用,调用
recv
应该 return 一个错误,或者 - 如果 "messages are available to be received". ,对
recv
的调用可以在 shutdown
之后继续 return 数据
虽然这有点模棱两可,但第一种解释没有意义,因为不清楚错误的用途。所以正确的解释是第二种。
(请注意,在堆栈中的任何位置进行缓冲的任何协议都可能具有传输中的数据,这些数据尚无法读取。shutdown
的语义使您能够在调用 shutdown
.)
然而,这是指对等方调用 shutdown
,而不是调用进程。如果调用进程调用 shutdown
,这是否也适用?
那么合规还是什么
标准不明确。
如果进程调用 shutdown(fd, SHUT_RD)
被认为等同于 peer 调用 shutdown(fd, SHUT_WR)
那么它是兼容的。
另一方面,严格看文,好像不太符合。但是对于进程在 shutdown(SHUT_RD)
之后调用 recv
的情况,没有错误代码。错误代码是详尽的,这意味着这种情况不是错误,所以应该 return 0
就像对等方调用 shutdown(SHUT_WR)
.
不过,这就是您想要的行为 - 如果您需要,可以接收传输中的消息。如果你不想他们不要打电话给 recv
。
在某种程度上这是模棱两可的,它应该被认为是标准中的一个错误。
为什么 post-shutdown
recv
数据不限于传输中的数据
在一般情况下,无法知道传输中的数据是什么。
- 在 unix 套接字的情况下,数据可能在接收方、操作系统或发送方进行缓冲。
- 在TCP的情况下,数据可能被接收进程、操作系统、网卡硬件缓冲区缓冲,数据包可能在中间路由器传输,被发送网卡硬件缓冲,通过发送操作系统或发送进程。
背景
posix 提供了一个 api 用于与不同类型的流进行统一交互,包括匿名管道、命名管道以及 IPv4 和 IPv6 TCP 和 UDP 套接字...和原始以太网、令牌环和 IPX/SPX、X.25 和 ATM...
因此 posix 提供了一组功能,广泛涵盖了大多数流媒体和基于数据包的协议的主要功能。
然而,并非所有协议都支持所有功能
从设计的角度来看,如果调用者请求底层协议不支持的操作,有多种选择:
进入错误状态,并禁止对文件描述符进行任何进一步的操作。
Return 调用错误,否则忽略它。
Return 成功,做最近有意义的事情。
实施某种包装器或填充器以提供缺少的功能。
posix 标准排除了前两个选项。显然,Linux 开发人员选择了第三个选项。