栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

写入关闭的本地TCP套接字不会失败

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

写入关闭的本地TCP套接字不会失败

这是Linux手册页关于

write
和的内容
EPIPE

   EPIPE  fd is connected to a pipe or socket whose reading end is closed.          When this happens the writing process will also receive  a  SIG-          PIPE  signal.  (Thus, the write return value is seen only if the          program catches, blocks or ignores this signal.)

当Linux使用a

pipe
或a时
socketpair
,它可以并且将检查对的 读取结束 ,这两个程序将演示:

void test_socketpair () {    int pair[2];    socketpair(PF_LOCAL, SOCK_STREAM, 0, pair);    close(pair[0]);    if (send(pair[1], "a", 1, MSG_NOSIGNAL) < 0) perror("send");}void test_pipe () {    int pair[2];    pipe(pair);    close(pair[0]);    signal(SIGPIPE, SIG_IGN);    if (write(pair[1], "a", 1) < 0) perror("send");    signal(SIGPIPE, SIG_DFL);}

Linux之所以能够做到这一点,是因为内核对管道或连接对的另一端具有先天的知识。但是,使用时

connect
,有关套接字的状态由协议栈维护。您的测试演示了这种行为,但是下面是一个在单个线程中完成所有操作的程序,类似于上面的两个测试:

int a_sock = socket(PF_INET, SOCK_STREAM, 0);const int one = 1;setsockopt(a_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));struct sockaddr_in a_sin = {0};a_sin.sin_port = htons(4321);a_sin.sin_family = AF_INET;a_sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);bind(a_sock, (struct sockaddr *)&a_sin, sizeof(a_sin));listen(a_sock, 1);int c_sock = socket(PF_INET, SOCK_STREAM, 0);fcntl(c_sock, F_SETFL, fcntl(c_sock, F_GETFL, 0)|O_NONBLOCK);connect(c_sock, (struct sockaddr *)&a_sin, sizeof(a_sin));fcntl(c_sock, F_SETFL, fcntl(c_sock, F_GETFL, 0)&~O_NONBLOCK);struct sockaddr_in s_sin = {0};socklen_t s_sinlen = sizeof(s_sin);int s_sock = accept(a_sock, (struct sockaddr *)&s_sin, &s_sinlen);struct pollfd c_pfd = { c_sock, POLLOUT, 0 };if (poll(&c_pfd, 1, -1) != 1) perror("poll");int erropt = -1;socklen_t errlen = sizeof(erropt);getsockopt(c_sock, SOL_SOCKET, SO_ERROR, &erropt, &errlen);if (erropt != 0) { errno = erropt; perror("connect"); }puts("P|Recv-Q|Send-Q|Local Address|Foreign Address|State|");char cmd[256];snprintf(cmd, sizeof(cmd), "netstat -tn | grep ':%hu ' | sed 's/  */|/g'",         ntohs(s_sin.sin_port));puts("before close on client"); system(cmd);close(c_sock);puts("after close on client"); system(cmd);if (send(s_sock, "a", 1, MSG_NOSIGNAL) < 0) perror("send");puts("after send on server"); system(cmd);puts("end of test");sleep(5);

如果运行上面的程序,您将获得类似于以下的输出:

P|Recv-Q|Send-Q|Local Address|Foreign Address|State|before close on clienttcp|0|0|127.0.0.1:35790|127.0.0.1:4321|ESTABLISHED|tcp|0|0|127.0.0.1:4321|127.0.0.1:35790|ESTABLISHED|after close on clienttcp|0|0|127.0.0.1:35790|127.0.0.1:4321|FIN_WAIT2|tcp|1|0|127.0.0.1:4321|127.0.0.1:35790|CLOSE_WAIT|after send on serverend of test

这表明

write
套接字转换到
CLOSED
状态花费了一个时间。为了找出发生这种情况的原因,事务的TCP转储可能很有用:

16:45:28 127.0.0.1 > 127.0.0.1 .809578 IP .35790 > .4321: S 1062313174:1062313174(0) win 32792 <mss 16396,sackOK,timestamp 3915671437 0,nop,wscale 7> .809715 IP .4321 > .35790: S 1068622806:1068622806(0) ack 1062313175 win 32768 <mss 16396,sackOK,timestamp 3915671437 3915671437,nop,wscale 7> .809583 IP .35790 > .4321: . ack 1 win 257 <nop,nop,timestamp 3915671437 3915671437> .840364 IP .35790 > .4321: F 1:1(0) ack 1 win 257 <nop,nop,timestamp 3915671468 3915671437> .841170 IP .4321 > .35790: . ack 2 win 256 <nop,nop,timestamp 3915671469 3915671468> .865792 IP .4321 > .35790: P 1:2(1) ack 2 win 256 <nop,nop,timestamp 3915671493 3915671468> .865809 IP .35790 > .4321: R 1062313176:1062313176(0) win 0

前三行代表三向握手。第四行是

FIN
客户端发送到服务器的数据包,第五行是
ACK
来自服务器的确认收据的数据包。第六行是服务器尝试将
PUSH
设置了标志的1字节数据发送到客户端。最后一行是客户端
RESET
数据包,该数据包将释放连接的TCP状态,这就是为什么第三条
netstat
命令在上述测试中未产生任何输出的原因。

因此,服务器直到尝试向其发送一些数据后才知道客户端将重置连接。进行重置的原因是因为客户端调用了

close
,而不是其他名称。

服务器无法确定客户端实际发出了什么系统调用,它只能遵循TCP状态。例如,我们可以将

close
呼叫替换为
shutdown

//close(c_sock);shutdown(c_sock, SHUT_WR);

shutdown
和之间的区别在于,
close
shutdown
仅控制连接状态,同时
close
还控制表示套接字的 文件描述符
的状态。一个
shutdown
不会
close
是套接字。

shutdown
更改后的输出将有所不同:

P|Recv-Q|Send-Q|Local Address|Foreign Address|State|before close on clienttcp|0|0|127.0.0.1:4321|127.0.0.1:56355|ESTABLISHED|tcp|0|0|127.0.0.1:56355|127.0.0.1:4321|ESTABLISHED|after close on clienttcp|1|0|127.0.0.1:4321|127.0.0.1:56355|CLOSE_WAIT|tcp|0|0|127.0.0.1:56355|127.0.0.1:4321|FIN_WAIT2|after send on servertcp|1|0|127.0.0.1:4321|127.0.0.1:56355|CLOSE_WAIT|tcp|1|0|127.0.0.1:56355|127.0.0.1:4321|FIN_WAIT2|end of test

TCP转储也将显示不同的内容:

17:09:18 127.0.0.1 > 127.0.0.1 .722520 IP .56355 > .4321: S 2558095134:2558095134(0) win 32792 <mss 16396,sackOK,timestamp 3917101399 0,nop,wscale 7> .722594 IP .4321 > .56355: S 2563862019:2563862019(0) ack 2558095135 win 32768 <mss 16396,sackOK,timestamp 3917101399 3917101399,nop,wscale 7> .722615 IP .56355 > .4321: . ack 1 win 257 <nop,nop,timestamp 3917101399 3917101399> .748838 IP .56355 > .4321: F 1:1(0) ack 1 win 257 <nop,nop,timestamp 3917101425 3917101399> .748956 IP .4321 > .56355: . ack 2 win 256 <nop,nop,timestamp 3917101426 3917101425> .764894 IP .4321 > .56355: P 1:2(1) ack 2 win 256 <nop,nop,timestamp 3917101442 3917101425> .764903 IP .56355 > .4321: . ack 2 win 257 <nop,nop,timestamp 3917101442 3917101442>17:09:23 .786921 IP .56355 > .4321: R 2:2(0) ack 2 win 257 <nop,nop,timestamp 3917106464 3917101442>

请注意,最后一次重置是在最后一个

ACK
数据包之后5秒钟进行的。此重置是由于程序未正确关闭套接字而关闭的。
ACK
重置之前从客户端到服务器的数据包与以前不同。这表明客户端未使用
close
。在TCP中,该
FIN
指示实际上表示没有更多数据要发送。但是,由于TCP连接是双向的,因此接收
FIN
假定的服务器的客户端仍可以接收数据。在上述情况下,客户端实际上确实接受数据。

无论客户端是使用

close
还是
SHUT_WR
发出
FIN
,在两种情况下,都可以
FIN
通过在服务器套接字上轮询可读事件来检测的到来。如果在调用
read
结果之后是
0
,那么您知道
FIN
到达了,那么您可以使用该信息来做您想做的事情。

struct pollfd s_pfd = { s_sock, POLLIN|POLLOUT, 0 };if (poll(&s_pfd, 1, -1) != 1) perror("poll");if (s_pfd.revents|POLLIN) {    char c;    int r;    while ((r = recv(s_sock, &c, 1, MSG_DONTWAIT)) == 1) {}    if (r == 0) {  }    else if (errno == EAGAIN) {  }    else {  perror("recv"); }}

现在,它是平凡的事实,如果服务器的问题

SHUT_WR
shutdown
它试图做一个写之前,它实际上将得到
EPIPE
错误。

shutdown(s_sock, SHUT_WR);if (send(s_sock, "a", 1, MSG_NOSIGNAL) < 0) perror("send");

相反,如果您希望客户端指示立即重置服务器,则可以通过启用linger选项(

0
在调用之前有一个较长的超时时间)来强制大多数TCP堆栈上执行此操作
close

struct linger lo = { 1, 0 };setsockopt(c_sock, SOL_SOCKET, SO_LINGER, &lo, sizeof(lo));close(c_sock);

通过上述更改,程序的输出变为:

P|Recv-Q|Send-Q|Local Address|Foreign Address|State|before close on clienttcp|0|0|127.0.0.1:35043|127.0.0.1:4321|ESTABLISHED|tcp|0|0|127.0.0.1:4321|127.0.0.1:35043|ESTABLISHED|after close on clientsend: Connection reset by peerafter send on serverend of test

send
这种情况下获取的即时错误,但它不是
EPIPE
,它是
ECONNRESET
。TCP转储也反映了这一点:

17:44:21 127.0.0.1 > 127.0.0.1 .662163 IP .35043 > .4321: S 498617888:498617888(0) win 32792 <mss 16396,sackOK,timestamp 3919204411 0,nop,wscale 7> .662176 IP .4321 > .35043: S 497680435:497680435(0) ack 498617889 win 32768 <mss 16396,sackOK,timestamp 3919204411 3919204411,nop,wscale 7> .662184 IP .35043 > .4321: . ack 1 win 257 <nop,nop,timestamp 3919204411 3919204411> .691207 IP .35043 > .4321: R 1:1(0) ack 1 win 257 <nop,nop,timestamp 3919204440 3919204411>

RESET
数据包在三向握手完成后立即发送。但是,使用此选项有其危险。如果到达时另一端的套接字缓冲区中有未读的数据,则
RESET
该数据将被清除,从而导致数据丢失。
RESET
通常在请求/响应样式协议中使用强制发送。当请求的发送者收到对请求的整个响应时,可以知道不会丢失任何数据。然后,对于请求发送方来说,
RESET
在连接上强制发送a是安全的。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/400973.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号