首页 » SEO优化 » phpsigpipe技巧_一个有关tcp的异常有意思的问题

phpsigpipe技巧_一个有关tcp的异常有意思的问题

访客 2024-11-21 0

扫一扫用手机浏览

文章目录 [+]

假设以了局景:

在tcp建立连接后,先主动关闭其做事端,之后再在客户端下对其socket进行写操作,正常思维都会认为,这个写操作肯定会返回缺点吧?

phpsigpipe技巧_一个有关tcp的异常有意思的问题

还真不一定。

phpsigpipe技巧_一个有关tcp的异常有意思的问题
(图片来自网络侵删)

本日在写代码时就碰着了这个问题,还纠结了挺久的,末了翻了下linux内核源码,才确定了答案。

先用下面的程序仿照下这个场景:

#include <arpa/inet.h>#include <assert.h>#include <netinet/in.h>#include <signal.h>#include <stdio.h>#include <strings.h>#include <sys/socket.h>#include <sys/types.h>#include <unistd.h>int tcp_connect { int sockfd, err; struct sockaddr_in addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(\"大众127.0.0.1\"大众); addr.sin_port = htons(9999); err = connect(sockfd, (struct sockaddr )&addr, sizeof(addr)); assert(err == 0); return sockfd;}int main(int argc, char argv) { int n; int sockfd = tcp_connect; signal(SIGPIPE, SIG_IGN); // 防止write触发SIGPIPE,便于测试 printf(\"大众请于5秒钟内关闭做事端...\n\公众); sleep(5); // write 1 n = write(sockfd, \"大众hello\n\公众, 6); if (n == -1) { perror(\"大众第一次write失落败\"大众); return -1; } assert(n == 6); printf(\公众第一次write成功!\n\"大众); sleep(1); // 确保客户端收到tcp的reset // write 2 n = write(sockfd, \"大众world\n\"大众, 6); if (n == -1) { perror(\公众第二次write失落败\"大众); return -1; } assert(n == 6); printf(\"大众第二次write成功!\n\公众); return 0;}

这段程序代表客户端,做事端就用ncat来仿照。

下面是实行流程:

先打开一个terminal,用ncat开一个做事端:

$ ncat -l 9999

再打开另一个terminal,编译上面的程序,然后实行:

$ gcc main.c$ ./a.out请于5秒钟内关闭做事端...第一次write成功!第二次write失落败: Broken pipe

当客户端提示关闭做事端时,要切换到对应的terminal,关闭做事端。

从上面的输出可以看到,之后的两次写,第一次成功了,第二次才失落败。

奇怪吧。

我们用tcpdump抓包看下,第一次是否是真的写成功了:

$ sudo tcpdump -i any -n# port 9999 1 17:59:07.812599 IP 127.0.0.1.51614 > 127.0.0.1.9999: Flags [S], seq 1076934668, win 65495, options [mss 65495,sackOK,TS val 134308422 ecr 0,nop,wscale 7], length 0 2 17:59:07.812648 IP 127.0.0.1.9999 > 127.0.0.1.51614: Flags [S.], seq 3833531274, ack 1076934669, win 65483, options [mss 65495,sackOK,TS val 134308422 ecr 134308422,nop,wscale 7], length 0 3 17:59:07.812691 IP 127.0.0.1.51614 > 127.0.0.1.9999: Flags [.], ack 1, win 512, options [nop,nop,TS val 134308422 ecr 134308422], length 0 4 17:59:09.832579 IP 127.0.0.1.9999 > 127.0.0.1.51614: Flags [F.], seq 1, ack 1, win 512, options [nop,nop,TS val 134310442 ecr 134308422], length 0 5 17:59:09.835181 IP 127.0.0.1.51614 > 127.0.0.1.9999: Flags [.], ack 2, win 512, options [nop,nop,TS val 134310445 ecr 134310442], length 0 6 17:59:12.813697 IP 127.0.0.1.51614 > 127.0.0.1.9999: Flags [P.], seq 1:7, ack 2, win 512, options [nop,nop,TS val 134313423 ecr 134310442], length 6 7 17:59:12.813735 IP 127.0.0.1.9999 > 127.0.0.1.51614: Flags [R], seq 3833531276, win 0, length 0

还真是成功了,看上面第6个包,发送的数据长度是6,即:我们代码中的hello\n。

这里大概阐明下tcpdump的输出:

前三个包是tcp的三次握手,完成之后代表tcp建立连接成功。

第四个包是我们在关闭做事端时,做事端发给客户真个fin包,表示关闭连接要求。

第五个包是客户端发给做事真个tcp层的ack,表示已经收到fin包。

第六个包是客户端发给做事真个hello\n字符串。

第七个包是做事真个tcp层发给客户真个reset包,由于此时做事真个socket已经关闭了。

由tcpdump的输出可以确定,第一次write的确是写成功了,但为什么呢?明明做事真个socket都已经关闭了,为什么还可以发送呢?并且为什么第一次可以发送,第二次就弗成了呢?

来看下内核源码是怎么做的:

// net/ipv4/tcp_input.cint tcp_sendmsg_locked(struct sock sk, struct msghdr msg, size_t size){ ... err = -EPIPE; if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto do_error; ... // 省略这部分是tcp发送数据的代码 ... return copied + copied_syn; ...do_error: ... return err;}EXPORT_SYMBOL_GPL(tcp_sendmsg_locked);

该方法便是tcp发的方法。

由上可见,只有当socket发生缺点时,或者我们关闭了socket的send端,上面的write方法才会返回缺点,其他情形下,write的数据都会正常发送。

由tcp的干系知识我们可以知道,当做事端发送fin给客户端时,客户真个socket进入了CLOSE_WAIT状态,即:等待客户真个程序关闭其socket。

也便是说,fin并没有使客户真个socket发生缺点,也并没有关闭客户端socket的send端(但是关闭了客户端socket的receive端),以是第一次write就成功的将数据发送出去了。

那第二次write为什么失落败呢?

看上面tcpdump的输出就知道了,当第一次write之后,做事真个操作系统收到数据,创造其对应的socket已经关闭了,以是就发送了个reset包给客户端。

客户端在收到reset包后,实行了下面的代码:

// net/ipv4/tcp_input.cvoid tcp_reset(struct sock sk){ ... switch (sk->sk_state) { ... case TCP_CLOSE_WAIT: sk->sk_err = EPIPE; break; ... } ... tcp_done(sk); ...}

由上可见,sk->sk_err被设置为了EPIPE,实在,不才面的tcp_done方法里,也关闭了socket的send端,不过这个已经影响不大了。

以是,在我们第二次调用write时,当实行到tcp_sendmsg_locked方法时,就直接跳到了do_error,即:返回err给用户。

至此,就完美阐明了,为什么会有上述奇怪的征象。

实在,我们不用看代码,仔细想想tcp的细节,也是可以理解,操作系统为什么会有这样的行为。

在第一次write之前,我们的socket收到fin包,进入到CLOSE_WAIT状态,此时,实在并不能解释做事端已经完备关闭了连接,它还有可能是发送fin包,只是为了关闭其send端,但它还是可以读的,以是我们理应也可以连续写。

这样想就更随意马虎明白些了吧。

不过,从源码角度看这个问题,还是来的更实在些。

如果有对tcp源码有兴趣的同学,可以看下我之前写的tcp源码剖析系列文章:

TCP/IP 状态转换图及源码剖析文章列表

完。

技能原创及架构实践文章,欢迎通过公众年夜众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建办法

标签:

相关文章

语言枚举类型,探索人类语言多样性的奥秘

语言是人类交流的重要工具,也是人类文明发展的重要标志。随着全球化进程的不断推进,各种语言枚举类型应运而生。本文将从语言枚举类型的定...

SEO优化 2024-12-29 阅读1 评论0

语言栏消失,科技变革下的挑战与机遇

近年来,随着科技的飞速发展,智能手机、平板电脑等移动设备的普及,语言栏这一功能已经成为了我们日常生活中不可或缺的一部分。近期有消息...

SEO优化 2024-12-29 阅读1 评论0

语言混合现象的多元魅力与挑战

语言混合作为一种跨文化交流的现象,逐渐成为世界范围内语言学研究的热点。它不仅丰富了语言的多样性,也反映了全球化背景下人类社会的交流...

SEO优化 2024-12-29 阅读1 评论0

语言是思想的载体,介绍语言与思想的关系

在人类文明的进程中,语言一直扮演着至关重要的角色。它不仅是人们沟通交流的工具,更是承载着人类思想的载体。自古以来,人们就深知语言与...

SEO优化 2024-12-29 阅读1 评论0