우리 모두는 TCP가 연결 지향적이고 안정적인 바이트 스트림 기반 전송 계층 통신 프로토콜이라는 것을 알고 있습니다.
여기서 말하는 "연결 지향"이란 연결을 설정하고, 연결을 사용하고, 연결을 해제해야 한다는 의미입니다.
연결 설정은 잘 알려진 TCP 3방향 핸드셰이크를 의미합니다.
그리고 연결을 사용하여 데이터 전송은 1회의 전송과 1회의 확인의 형태로 수행됩니다.
또 하나는 Release Connection인데, 이는 우리의 공통 TCP 4파동입니다.
TCP 웨이브 4번 익숙하실 텐데, wave 3번을 보신 적 있으신가요? 그리고 두 번 흔들기는 어떨까요?
다 보셨나요? 네 번의 악수는 어떻습니까?
오늘의 주제는 단순한 호기심이나 상식에 관한 것이 아닙니다.
네 가지 물결부터 시작하여 실용적인 지식을 얻으세요.
TCP Four Waves에 대한 간략한 리뷰입니다.
파됩니다. 데이터 전송이 완료되면 클라이언트든 서버든 적극적으로 4개의 웨이브를 시작하여 연결을 해제할 수 있습니다.
그림처럼 4개의 웨이브가 클라이언트에 의해 시작된다고 가정하면 active party입니다. 서버는 클라이언트의 웨이브 요청을 수동적으로 수신하는데, 이를 passive party라고 합니다.
클라이언트와 서버는 처음에 설정됨
상태. ESTABLISHED
状态。
第一次挥手:一般情况下,主动方执行close()
或 shutdown()
方法,会发个FIN报文
出来,表示"我不再发送数据了"。
第二次挥手:在收到主动方的FIN
报文后,被动方立马回应一个ACK
,意思是"我收到你的FIN了,也知道你不再发数据了"。
上面提到的是主动方不再发送数据了。但如果这时候,被动方还有数据要发,那就继续发。注意,虽然第二次和第三次挥手之间,被动方是能发数据到主动方的,但主动方能不能正常收就不一定了,这个待会说。
第三次挥手:在被动方在感知到第二次挥手之后,会做了一系列的收尾工作,最后也调用一个 close()
, 这时候就会发出第三次挥手的 FIN-ACK
close()
또는 FIN 메시지
가 나오며 "🎜더 이상 데이터를 보내지 않습니다🎜"라는 메시지가 나타납니다. 🎜🎜🎜두 번째 웨이브🎜: 활성 파티의 FIN
메시지 후 수동적 당사자는 즉시 ACK
는 "FIN을 받았고 더 이상 데이터를 보내지 않을 것이라는 것도 알고 있습니다."를 의미합니다. 🎜🎜위에서 언급한 내용은 🎜active party🎜가 더 이상 데이터를 보내지 않는다는 것입니다. 하지만 이때 🎜passive party🎜에 보낼 데이터가 아직 남아 있다면 계속해서 보내세요. 참고로, 2차 웨이브와 3차 웨이브 사이에는 패시브 측에서 액티브 측으로 데이터를 보낼 수 있지만, 액티브 측에서 정상적으로 수신할 수 있을지는 확실하지 않습니다. 🎜🎜🎜세 번째 물결🎜: 패시브 파티가 두 번째 물결을 감지한 후 일련의 마무리 작업을 수행하고 마지막으로 닫기( )
, 세 번째 웨이브가 발행됩니다. FIN-ACK
. 🎜네 번째 물결: 활성 파티는 ACK
, 수신됨을 의미합니다. ACK
,意思是收到了。
其中第一次挥手和第三次挥手,都是我们在应用程序中主动触发的(比如调用close()
方法),也就是我们平时写代码需要关注的地方。
第二和第四次挥手,都是内核协议栈自动帮我们完成的,我们写代码的时候碰不到这地方,因此也不需要太关心。
另外不管是主动还是被动,每方发出了一个 FIN
和一个ACK
。也收到了一个 FIN
和一个ACK
。这一点大家关注下,待会还会提到。
不一定。一般情况下,通过对socket
执行 close()
或 shutdown()
方法会发出FIN
。但实际上,只要应用程序退出,不管是主动退出,还是被动退出(因为一些莫名其妙的原因被kill
了), 都会发出 FIN
close()
method), 이것이 우리가 하는 것입니다. 코드를 작성할 때 주의할 점이 있습니다. 🎜🎜두 번째와 네 번째 웨이브는 커널 프로토콜 스택에 의해 자동으로 수행됩니다. 코드를 작성할 때 이 부분은 건드릴 수 없으므로 크게 신경 쓸 필요가 없습니다. 🎜🎜또한, 활성 또는 수동 여부에 관계없이 각 당사자는 FIN
및 확인
. 또한 FIN
및 ACK
. 🎜 이 점 꼭 주의하시고 나중에 언급하겠습니다. 🎜🎜소켓
실행close()
또는 shutdown()
메소드는 FIN
. 그러나 실제로 애플리케이션이 종료되는 한 🎜active🎜exit 또는 🎜passive🎜exit인지 여부는 알 수 없는 이유로 인해 kill
), 🎜 🎜 82, 82호);배경: rgb(248, 248, 248);">FIN. 🎜FIN은 "더 이상 데이터를 보내지 않습니다"를 의미하므로
shutdown()
关闭读不会给对方发FIN, 关闭写才会发FIN。
根据上面的四次挥手图,可以看出,FIN-WAIT-2
是主动方那边的状态。
处于这个状态的程序,一直在等第三次挥手的FIN
。而第三次挥手需要由被动方在代码里执行close()
发出。
因此当机器上FIN-WAIT-2
状态特别多,那一般来说,另外一台机器上会有大量的 CLOSE_WAIT
。需要检查有大量的 CLOSE_WAIT
的那台机器,为什么迟迟不愿调用close()
关闭连接。
所以,如果机器上FIN-WAIT-2
状态特别多,一般是因为对端一直不执行close()
메서드는 세 번째 웨이브를 발행합니다.
"코드가 성공적으로 전송되면 데이터가 전송됩니까?" 전에 작성된 기사. 》, 소스 코드의 관점에서 일반적인 상황에서 프로그램은 close()
; close()
的时候;
如果当前连接对应的socket
的接收缓冲区有数据,会发RST
。
如果发送缓冲区有数据,那会等待发送完,再发第一次挥手的FIN
。
大家知道,TCP是全双工通信,意思是发送数据的同时,还可以接收数据。
Close()
的含义是,此时要同时关闭发送和接收消息的功能。
也就是说,虽然理论上,第二次和第三次挥手之间,被动方是可以传数据给主动方的。
但如果 主动方的四次挥手是通过 close()
触发的,那主动方是不会去收这个消息的。而且还会回一个 RST
현재 연결이 소켓
의
FIN
.
우리 모두 알다시피 TCP는
전이중 통신
Close()
는 메시지 보내기 및 받기 기능을 동시에 꺼야 함을 의미합니다. 🎜🎜즉, 🎜이론적으로는🎜 2차와 3차 웨이브 사이에도 패시브 당사자가 액티브 당사자에게 데이터를 전송할 수 있습니다. 🎜🎜그러나 활성 파티의 4파동이 통과되면close()
가 트리거되면 활성 파티는 이 메시지를 받지 않습니다. 그리고 RST
. 이 연결을 직접 종료하세요. 🎜🎜🎜🎜close()는 TCP 4파동을 트리거합니다🎜🎜🎜🎜🎜둘 다 아닙니다. 앞에서 언급했듯이닫기()
는 동시에메시지 보내기 및 받기 기능을 끄세요. Close()
的含义是,要同时关闭发送和接收消息的功能。
那如果能做到只关闭发送消息,不关闭接收消息的功能,那就能继续收消息了。这种 half-close
的功能,通过调用shutdown()
方法就能做到。
int shutdown(int sock, int howto);
其中 howto 为断开方式。有以下取值:
SHUT_RD:关闭读。这时应用层不应该再尝试接收数据,内核协议栈中就算接收缓冲区收到数据也会被丢弃。
SHUT_WR:关闭写。如果发送缓冲区中还有数据没发,会将将数据传递到目标主机。
SHUT_RDWR:关闭读和写。相当于
그런 다음 메시지 보내기만 끄면, 메시지 수신 기능을 끄지 않으면 메시지를 계속 받을 수 있습니다. 이close()
반 닫기
함수,shutdown()
메소드 완료됩니다.int send( SOCKET s,const char* buf,int len,int flags);여기서 방법은 연결 해제 방법입니다. 다음 값을 사용할 수 있습니다:
SHUT_RD: 읽기를 닫습니다. 이때 애플리케이션 계층은 더 이상 데이터 수신을 시도해서는 안 됩니다. 수신 버퍼가 커널 프로토콜 스택에서 데이터를 수신하더라도 폐기됩니다.
SHUT_RDWR: 읽기 및 쓰기를 끕니다. 닫기()
.
不管主动关闭方调用的是close()
还是shutdown()
,对于被动方来说,收到的就只有一个FIN
。
被动关闭方就懵了,"我怎么知道对方让不让我继续发数据?"
其实,大可不必纠结,该发就发。
第二次挥手和第三次挥手之间,如果被动关闭方想发数据,那么在代码层面上,就是执行了 send()
方法。
int send( SOCKET s,const char* buf,int len,int flags);
send()
会把数据拷贝到本机的发送缓冲区。如果发送缓冲区没出问题,都能拷贝进去,所以正常情况下,send()
一般都会返回成功。
![tcp_sendmsg 逻辑](https://cdn.jsdelivr.net/gh/xiaobaiTech/image/tcp_sendmsg 逻辑.png)
然后被动方内核协议栈会把数据发给主动关闭方。
如果上一次主动关闭方调用的是shutdown(socket_fd, SHUT_WR)
。那此时,主动关闭方不再发送消息,但能接收被动方的消息,一切如常,皆大欢喜。
마지막 activecloser 호출이 닫기()
. 그런 다음 close()
。那主动方在收到被动方的数据后会直接丢弃,然后回一个RST
。
针对第二种情况。
被动方内核协议栈收到了RST
,会把连接关闭。但内核连接关闭了,应用层也不知道(除非被通知)。
此时被动方应用层接下来的操作,无非就是读或写。
如果是读,则会返回RST
的报错,也就是我们常见的Connection reset by peer
。
如果是写,那么程序会产生SIGPIPE
信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。
总结一下,当被动关闭方 recv()
返回EOF
时,说明主动方通过 close()
或 shutdown(fd, SHUT_WR)
활성 측
RST
. 🎜🎜🎜두 번째 상황입니다. 🎜🎜패시브 측면🎜커널 스택🎜수신 RST
는 연결을 종료합니다. 그러나 커널 연결은 닫혀 있으며 애플리케이션 계층은 (알려지지 않는 한) 알지 못합니다. 🎜🎜이때 패시브 🎜애플리케이션 계층🎜의 다음 작업은 🎜읽기 또는 쓰기🎜에 지나지 않습니다. 🎜피어에 의한 연결 재설정
. 🎜🎜🎜🎜작성하면 프로그램은 SIGPIPE
신호, 애플리케이션 계층 코드는 처리되지 않은 경우 신호를 캡처하고 처리할 수 있습니다. 기본값 프로세스가 종료되고 비정상적으로 종료됩니다. 🎜🎜recv()
반환EOF
, 활성 파티가 close()
또는 shutdown(fd, SHUT_WR)
은 첫 번째 웨이브를 시작합니다. 🎜如果此时被动方执行两次 send()
。send()
。
第一次send()
, 一般会成功返回。
第二次send()
时。如果主动方是通过 shutdown(fd, SHUT_WR)
发起的第一次挥手,那此时send()
还是会成功。如果主动方通过 close()
发起的第一次挥手,那此时会产生SIGPIPE
信号,进程默认会终止,异常退出。不想异常退出的话,记得捕获处理这个信号。
第三次挥手,是由被动方主动触发的,比如调用close()
。
如果由于代码错误或者其他一些原因,被动方就是不执行第三次挥手。
这时候,主动方会根据自身第一次挥手的时候用的是 close()
还是 shutdown(fd, SHUT_WR)
send()
, 一般会成功返回。🎜send()
时。如果主动方是通过종료(fd, SHUT_WR) 发起적第一次挥手,那此时보내기( )
还是会成功。如果主动方通过 close()
发起的第一次挥手,那此时会产生SIGPIPE
信号,进程默认会终止,异常退ude. 🎜close()
还是 종료(fd, SHUT_WR )
,다른 이동 방식은 없습니다.🎜如果是 shutdown(fd, SHUT_WR)
,说明主动方其实只关闭了写,但还可以读,此时会一直处于 FIN-WAIT-2
, 死等被动方的第三次挥手。
如果是 close()
, 说明主动方读写都关闭了,这时候会处于 FIN-WAIT-2
一段时间,这个时间由 net.ipv4.tcp_fin_timeout
控制,一般是 60s
,这个值正好跟2MSL
一样 。超过这段时间之后,状态不会变成 `TIME-WAIT`,而是直接变成`CLOSED`。
# cat /proc/sys/net/ipv4/tcp_fin_timeout 60
四次挥手聊完了,那有没有可能出现三次挥手?
是可能的。
我们知道,TCP四次挥手里,第二次和第三次挥手之间,是有可能有数据传输的。第三次挥手的目的是为了告诉主动方,"被动方没有数据要发了"。
所以,在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的。这样就出现了三次挥手。
위에서 언급한 상황은 전송할 데이터가 없으면 2번째와 3번째 사이에 전송할 데이터가 있는 경우입니다. 손을 흔들면 세 번 흔들면 안 되는 걸까요?
은이 아닙니다. TCP에는 지연 확인이라는 기능도 있습니다. 이는 간단히 다음과 같이 이해될 수 있습니다. 수신자는 데이터를 수신한 후 즉시 ACK 확인 패키지에 응답할 필요가 없습니다. 이를 기준으로
데이터 패킷이 전송될 때마다 해당ACK
승인 패킷은 수신자가 승인을 병합할 수 있기 때문입니다. ACK
确认包,因为接收方可以合并确认。
而这个合并确认,放在四次挥手里,可以把第二次挥手、第三次挥手,以及他们之间的数据传输都合并在一起发送。因此也就出现了三次挥手。
前面在四次挥手中提到,关闭的时候双方都发出了一个FIN和收到了一个ACK。
正常情况下TCP连接的两端,是不同IP+端口的进程。
但如果TCP连接的两端,IP+端口是一样的情况下,那么在关闭连接的时候,也同样做到了一端发出了一个FIN,也收到了一个 ACK,只不过正好这两端其实是同一个socket
그리고 이 병합 확인은 4개의 웨이브에 배치되며 두 번째 웨이브, 세 번째 웨이브와 이들 사이의 데이터 전송을 병합하여 함께 보낼 수 있습니다. 그래서 세 개의 파도가 있었습니다.
TCP 파도 두 번front It 마감 시 양측 모두 FIN을 보내고 ACK를 받았다는 4개의 웨이브에서 언급되었습니다.
일반적인 상황에서 TCP 연결의 두 끝은 서로 다른 🎜IP+포트🎜를 사용하여 프로세스됩니다. 🎜🎜하지만 TCP 연결의 양쪽 끝에서 🎜IP + 포트🎜가 동일하면 연결을 닫을 때에도 마찬가지입니다. 🎜한 쪽은 FIN을 보내고 ACK🎜도 받는데 이 둘은 끝 실제로는동일한 소켓
. 🎜🎜🎜🎜TCP가 두 번 파도🎜🎜🎜그리고 이렇게 양쪽 끝이 동일한 🎜IP+포트🎜로 연결되는 것을 🎜TCP 자체 연결🎜이라고 합니다. 🎜是的,你没看错,我也没打错别字。同一个socket确实可以自己连自己,形成一个连接。
上面提到了,同一个客户端socket,自己对自己发起连接请求。是可以成功建立连接的。这样的连接,叫TCP自连接。
下面我们尝试下复现。
注意我是在以下系统进行的实验。在mac
上多半无法复现。
# cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)"
通过nc
命令可以很简单的创建一个TCP自连接
# nc -p 6666 127.0.0.1 6666
上面的 -p
可以指定源端口号。也就是指定了一个端口号为6666
的客户端去连接 127.0.0.1:6666
。
# netstat -nt | grep 6666 tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
整个过程中,都没有服务端参与。可以抓个包看下。
可以看到,相同的socket,自己连自己的时候,握手是三次的。挥手是两次的。
上面这张图里,左右都是同一个客户端,把它画成两个是为了方便大家理解状态的迁移。
我们可以拿自连接的握手状态对比下正常情况下的TCP三次握手。
看了自连接的状态图,再看看下面几个问题。
第一次握手过后,连接状态就变成了SYN_SENT
状态。如果此时又收到了第一次握手的SYN包,那么连接状态就会从SYN_SENT
状态变成SYN_RCVD
。
// net/ipv4/tcp_input.c static int tcp_rcv_synsent_state_process() { // SYN_SENT状态下,收到SYN包 if (th->syn) { // 状态置为 SYN_RCVD tcp_set_state(sk, TCP_SYN_RECV); } }
第二握手过后,连接状态就变为SYN_RCVD
了,此时如果再收到第二次握手的SYN+ACK
包。连接状态会变为ESTABLISHED
。
// net/ipv4/tcp_input.c int tcp_rcv_state_process() { // 前面省略很多逻辑,能走到这就认为肯定有ACK if (true) { // 判断下这个ack是否合法 int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) > 0; switch (sk->sk_state) { case TCP_SYN_RECV: if (acceptable) { // 状态从 SYN_RCVD 转为 ESTABLISHED tcp_set_state(sk, TCP_ESTABLISHED); } } } }
第一次挥手过后,一端状态就会变成 FIN-WAIT-1
。正常情况下,是要等待第二次挥手的ACK
。但实际上却等来了 一个第一次挥手的 FIN
包, 这时候连接状态就会变为CLOSING
。
// net/ static void tcp_fin(struct sock *sk) { switch (sk->sk_state) { case TCP_FIN_WAIT1: tcp_send_ack(sk); // FIN-WAIT-1状态下,收到了FIN,转为 CLOSING tcp_set_state(sk, TCP_CLOSING); break; } }
这可以说是隐藏剧情了。
CLOSING
很少见,除了出现在自连接关闭外,一般还会出现在TCP两端同时关闭连接的情况下。
处于CLOSING
状态下时,只要再收到一个ACK
,就能进入 TIME-WAIT
状态,然后等个2MSL
,连接就彻底断开了。这跟正常的四次挥手还是有些差别的。大家可以滑到文章开头的TCP四次挥手再对比下。
可能大家会产生怀疑,这是不是nc
这个软件本身的bug
。
那我们可以尝试下用strace
看看它内部都做了啥。
# strace nc -p 6666 127.0.0.1 6666 // ... socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) // ...
无非就是以创建了一个客户端socket
句柄,然后对这个句柄执行 bind
, 绑定它的端口号是6666
,然后再向 127.0.0.1:6666
发起connect
方法。
我们可以尝试用C语言
去复现一遍。
下面的代码,只用于复现问题。直接跳过也完全不影响阅读。
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #include <strings.h> int main() { int lfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ]; int n = 0, i = 0, ret = 0 ; printf("This is a client \n"); /*Step 1: 创建客户端端socket描述符cfd*/ cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd == -1) { perror("socket error"); exit(1); } int flag=1,len=sizeof(int); if( setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } bzero(&clie_addr, sizeof(clie_addr)); clie_addr.sin_family = AF_INET; clie_addr.sin_port = htons(6666); inet_pton(AF_INET,"127.0.0.1", &clie_addr.sin_addr.s_addr); /*Step 2: 客户端使用bind绑定客户端的IP和端口*/ ret = bind(cfd, (struct sockaddr* )&clie_addr, sizeof(clie_addr)); if(ret != 0) { perror("bind error"); exit(2); } /*Step 3: connect链接服务器端的IP和端口号*/ bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(6666); inet_pton(AF_INET,"127.0.0.1", &serv_addr.sin_addr.s_addr); ret = connect(cfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)); if(ret != 0) { perror("connect error"); exit(3); } /*Step 4: 向服务器端写数据*/ while(1) { fgets(buf, sizeof(buf), stdin); write(cfd, buf, strlen(buf)); n = read(cfd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n);//写到屏幕上 } /*Step 5: 关闭socket描述符*/ close(cfd); return 0; }
保存为 client.c
文件,然后执行下面命令,会发现连接成功。
# gcc client.c -o client && ./client This is a client
# netstat -nt | grep 6666 tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
说明,这不是nc的bug。事实上,这也是内核允许的一种情况。
自连接一般不太常见,但遇到了也不难解决。
解决方案比较简单,只要能保证客户端和服务端的端口不一致就行。
事实上,我们写代码的时候一般不会去指定客户端的端口,系统会随机给客户端分配某个范围内的端口。而这个范围,可以通过下面的命令进行查询
# cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999
也就是只要我们的服务器端口不在32768-60999
这个范围内,比如设置为8888
。就可以规避掉这个问题。
另外一个解决方案,可以参考golang
标准网络库的实现,在连接建立完成之后判断下IP和端口是否一致,如果遇到自连接,则断开重试。
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { // 如果是自连接,这里会重试 for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ { if err == nil { fd.Close() } fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) } // ... } func selfConnect(fd *netFD, err error) bool { // 判断是否端口、IP一致 return l.Port == r.Port && l.IP.Equal(r.IP) }
前面提到的TCP
自连接是一个客户端自己连自己的场景。那不同客户端之间是否可以互联?
答案是可以的,有一种情况叫TCP同时打开。
大家可以对比下,TCP同时打开在握手时的状态变化,跟TCP自连接是非常的像。
比如SYN_SENT
状态下,又收到了一个SYN
,其实就相当于自连接里,在发出了第一次握手后,又收到了第一次握手的请求。结果都是变成 SYN_RCVD
。
在 SYN_RCVD
状态下收到了 SYN+ACK
,就相当于自连接里,在发出第二次握手后,又收到第二次握手的请求,结果都是变成 ESTABLISHED
。他们的源码其实都是同一块逻辑。
分别在两个控制台下,分别执行下面两行命令。
while true; do nc -p 2224 127.0.0.1 2223 -v;done while true; do nc -p 2223 127.0.0.1 2224 -v;done
上面两个命令的含义也比较简单,两个客户端互相请求连接对方的端口号,如果失败了则不停重试。
执行后看到的现象是,一开始会疯狂失败,重试。一段时间后,连接建立完成。
# netstat -an | grep 2223 Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:2224 127.0.0.1:2223 ESTABLISHED tcp 0 0 127.0.0.1:2223 127.0.0.1:2224 ESTABLISHED
期间抓包获得下面的结果。
可以看到,这里面建立连接用了四次交互。因此可以说这是通过"四次握手"建立的连接。
而且更重要的是,这里面只涉及两个客户端,没有服务端。
看到这里,不知道大家有没有跟我一样,被刷新了一波认知,对socket
有了重新的认识。
在以前的观念里,建立连接,必须要有一个客户端和一个服务端,并且服务端还要执行一个listen()
和一个accept()
。而实际上,这些都不是必须的。
그럼 다음에는 면접관이 물어보시죠"아니요listen()
, TCP가 연결을 설정할 수 있습니까?"listen()
, TCP能建立连接吗?", 我想大家应该知道该怎么回答了。
但问题又来了,只有两个客户端,没有listen()
,为什么能建立TCP
连接?
如果大家感兴趣,我们以后有机会再填上这个坑。
四次挥手中,不管是程序主动执行close()
,还是进程被杀,都有可能发出第一次挥手FIN
包。如果机器上FIN-WAIT-2
状态特别多,一般是因为对端一直不执行close()
方法发出第三次挥手。
Close()
会同时关闭发送和接收消息的功能。shutdown()
, 모두가 어떻게 대답했는지 알아야 한다고 생각합니다. . 하지만 문제가 다시 발생합니다. 클라이언트는 두 개뿐이고 listen()
, 왜 TCP
연결?
요약 span>
close()
또는 프로세스가 종료되면 첫 번째 웨이브를 발행할 수 있습니다FIN
패키지. 컴퓨터에 있는 경우 82); background: rgb(248, 248, 248);">FIN-WAIT-2여러 상태가 있습니다. 일반적으로 피어가 close()
메소드는 세 번째 웨이브를 보냅니다. Close()
는 메시지 보내기와 받기를 끄고
아니요듣기
, 두 클라이언트 간에 연결을 설정할 수도 있습니다. 이러한 상황을 TCP 동시 열기라고 하며, 이는 4방향 핸드셰이크가 생성됩니다. listen
,两个客户端之间也能建立连接。这种情况叫TCP同时打开,它由四次握手产生。
今天提到的,不管是两次挥手,还是自连接,或是TCP同时打开什么的。
咋一看,可能对日常搬砖没什么用,实际上也确实没什么用。
并且在面试上大概率也不会被问到。
毕竟一般面试官也不在意茴字有几种写法。
这篇文章的目的,主要是想从另外一个角度让大家重新认识下socket
。原来TCP
소켓
. 원본TCP는 자체적으로 연결할 수 있으며, 두 클라이언트도 서버 없이 연결할 수 있습니다. 🎜🎜이건 정말 정말 예상치 못한 일이에요. 🎜🎜🎜위 내용은 活久见!TCP两次挥手,你见过吗?那四次握手呢?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!