TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP协议族中,TCP协议负责在两个网络节点之间建立可靠的连接,并保证数据包的顺序传输和数据的完整性。
1.TCP三次握手
TCP三次握手(Three-way Handshake)是TCP/IP协议中用于建立TCP连接的过程。这个过程确保了两个通信节点的初始化序列号(ISN)是同步的,同时也交换了彼此的初始窗口大小等参数。三次握手的过程如下:
-
第一次握手(SYN):
- 客户端发送一个SYN(同步序列编号)标志的TCP段,以便开始一个新的连接。这个段中包含客户端的初始序列号(client_isn)。
-
第二次握手(SYN-ACK):
- 服务器接收到客户端的SYN请求后,需要确认客户端的SYN,同时自己也发送一个SYN请求。服务器在响应中包含它自己的初始序列号(server_isn),并将确认序号设置为客户端的初始序列号加1,即
client_isn + 1
。
- 服务器接收到客户端的SYN请求后,需要确认客户端的SYN,同时自己也发送一个SYN请求。服务器在响应中包含它自己的初始序列号(server_isn),并将确认序号设置为客户端的初始序列号加1,即
-
第三次握手(ACK):
- 客户端收到服务器的SYN-ACK响应后,再次发送一个确认响应,这个响应中包含确认序号,设置为服务器的初始序列号加1,即
server_isn + 1
。
- 客户端收到服务器的SYN-ACK响应后,再次发送一个确认响应,这个响应中包含确认序号,设置为服务器的初始序列号加1,即
完成三次握手后,TCP连接就建立成功了,接下来就可以开始数据的传输了。
这个握手过程保证了双方都知道对方已经准备好接收和发送数据,同时也防止了因网络中遗留的、失效的连接请求而错误地建立连接。
在代码中实现TCP三次握手通常不需要手动编写底层的网络通信代码,因为TCP三次握手是由操作系统和网络协议栈自动完成的。当你使用高级编程语言(如Python、Java、C++等)中的套接字(Socket)库时,底层的TCP握手过程是由操作系统负责的。
在C语言中,使用套接字API(通常称为BSD套接字API)可以实现TCP客户端和服务器。以下是一个简单的例子,展示了如何在C语言中实现TCP服务器和客户端。
TCP服务器 (server.c
):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};const char* hello = "Hello from server";// 创建socket文件描述符if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定socket到地址if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听是否有客户端连接if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}// 接受客户端连接if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 读取数据read(new_socket, buffer, 1024);printf("Message from client: %s\n", buffer);// 发送数据send(new_socket, hello, strlen(hello), 0);printf("Hello message sent\n");// 关闭连接close(new_socket);close(server_fd);return 0;
}
TCP客户端 (client.c
):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {int sock = 0;struct sockaddr_in serv_addr;const char* hello = "Hello from client";char buffer[1024] = {0};// 创建socket文件描述符if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8080);// 将IPv4地址从文本转换为二进制形式if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {printf("\nInvalid address/ Address not supported \n");return -1;}// 连接到服务器if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}// 发送数据send(sock, hello, strlen(hello), 0);printf("Hello message sent\n");// 读取数据read(sock, buffer, 1024);printf("Message from server: %s\n", buffer);// 关闭连接close(sock);return 0;
}
在上述代码中,服务器和客户端都使用套接字API来创建TCP连接。服务器首先创建一个套接字并绑定到指定的地址和端口,然后监听是否有客户端连接。客户端创建一个套接字并连接到服务器的地址和端口。连接成功后,双方可以通过套接字发送和接收数据。
要编译和运行这些程序,你需要有一个C编译器,如GCC。可以使用以下命令编译服务器和客户端:gcc -o server server.c gcc -o client client.c
然后,首先运行服务器:./server
在另一个终端中运行客户端:./client
客户端和服务器将通过TCP三次握手建立连接,然后客户端发送一个消息给服务器,服务器回复一个消息给客户端,最后双方关闭连接。
2.TCP四次挥手
TCP四次挥手(Four-way Handshake)是TCP/IP协议中用于终止一个TCP连接的过程。当一个连接的双方完成数据传输后,它们需要通过四次挥手来优雅地关闭连接。这个过程确保了所有在传输中的数据都被正确地处理,并且双方都同意断开连接。
四次挥手的过程如下:
-
第一次挥手(FIN):
- 连接的一端(假设是客户端)发送一个FIN(结束)标志的TCP段,表示它已经完成发送数据,并希望关闭到另一端(服务器)的连接。
-
第二次挥手(ACK):
- 服务器接收到这个FIN请求后,发送一个ACK(确认)响应,确认序号为收到FIN的序号加1。此时,服务器仍然可以发送数据,但客户端不再发送数据。
-
第三次挥手(FIN):
- 一段时间后,服务器也完成了数据发送,发送一个FIN段给客户端,请求关闭连接。
-
第四次挥手(ACK):
- 客户端收到服务器的FIN请求后,发送一个ACK响应,确认序号为收到服务器FIN的序号加1。在发送完这个ACK后,客户端会等待足够长的时间(通常是2倍的最大段生命周期MSL),以确保服务器收到确认包。
完成四次挥手后,TCP连接就正式关闭了。在这个过程中,每个方向的连接都是独立关闭的,这就是为什么需要四个步骤来完全终止连接。
需要注意的是,在四次挥手过程中,主动关闭连接的一端(如客户端)在发送完最后一个ACK后进入TIME_WAIT状态,而不是立即关闭连接。这是为了确保对方能够收到最后的ACK确认包,如果对方没有收到,会重新发送FIN请求。TIME_WAIT状态持续的时间通常是2倍的最大段生命周期MSL,这是一个保守的等待时间,确保在网络中遗留的TCP段都已经过期。
TCP连接中如何处理同时的双向关闭?
在TCP连接中,如果双方同时发送FIN包来关闭连接,这种情况被称为同时关闭(Simultaneous Close)。这种情况下,四次挥手的过程会稍微有所不同,但基本的原理是相同的:确保双方都同意关闭连接,并且所有在传输中的数据都被正确地处理。
同时关闭的过程如下:
-
双方同时发送FIN包:
- 连接的双方都发送一个FIN包给对方,表示自己已经完成了数据的发送,并希望关闭连接。
-
双方同时接收对方的FIN包,并回应ACK:
- 每一方收到对方的FIN包后,发送一个ACK包作为响应。这时,每个方向上的数据传输都已经关闭,但是双方都还在等待对方的最终确认。
-
双方再次发送ACK包:
- 在接收到对方的ACK包后,每一方都再次发送一个ACK包作为最终确认。这个ACK包的目的是确认对方的FIN包,以及告知对方自己的ACK包已经被接收。
-
双方关闭连接:
- 在发送完最后一个ACK包后,双方都可以关闭连接。在关闭连接之前,每一方都会等待足够长的时间(通常是2倍的最大段生命周期MSL),以确保对方能够收到最后的ACK包。
在同时关闭的情况下,连接的双方都会进入TIME_WAIT状态,以确保所有的TCP段都已经过期,从而避免了旧的连接信息干扰新的连接。TIME_WAIT状态对于防止网络中的旧连接信息(例如延迟的TCP段)对后续连接造成影响是非常重要的。
在实际的网络编程中,同时关闭的情况并不常见,因为通常一方(如客户端)会主动发起关闭,而另一方(如服务器)则会响应这个关闭请求。然而,了解同时关闭的处理方式对于理解TCP协议的完整性和健壮性是很有帮助的。
在四次挥手过程中,如果网络出现问题,应该如何处理?
在TCP四次挥手过程中,如果网络出现问题,TCP协议提供了多种机制来处理这些情况,确保连接能够可靠地关闭。以下是一些可能的问题及其处理方式:
-
丢失FIN包:
- 如果一方发送的FIN包在网络上丢失,发送方会等待接收方的ACK确认。如果在一定时间内没有收到确认,发送方会重新发送FIN包。
-
丢失ACK包:
- 如果一方发送的ACK包在网络上丢失,接收方会等待对方的下一个TCP包。如果对方也处于等待状态,那么双方可能会陷入死锁。为了解决这个问题,TCP使用了一个超时重传机制,发送方在等待ACK超时后会重新发送FIN包。
-
连接中的一方崩溃:
- 如果连接中的一方崩溃,另一方将不会收到预期的ACK包。在这种情况下,发送方会重传FIN包,但由于对方已经崩溃,重传不会得到响应。最终,发送方会认为对方已经关闭,并在超时后关闭自己的连接。
-
网络分区:
- 如果网络分区导致双方无法通信,每一方都可能认为对方已经崩溃。在这种情况下,每一方都可能开始关闭连接的过程,但由于无法通信,四次挥手无法完成。当网络恢复时,可能会出现重复的FIN包和ACK包,但TCP协议能够处理这种情况,确保连接最终能够正确关闭。
-
延迟的TCP段:
- 在连接关闭后,如果网络中仍然有延迟的TCP段,这些段可能会干扰后续的连接。为了防止这种情况,TCP在关闭连接后进入TIME_WAIT状态,等待足够长的时间以确保所有的TCP段都已经过期。
TCP协议的设计考虑了网络的不可靠性,并通过上述机制来确保连接的可靠性。这些机制包括超时重传、序列号和确认号的使用、以及TIME_WAIT状态等。因此,即使在网络出现问题的情况下,TCP仍然能够尽可能地保证连接的正确关闭。
在TCP连接关闭过程中,如何处理并发的多个连接请求?
在TCP连接关闭过程中,服务器通常需要处理并发的多个连接请求。这是通过多线程或多进程技术来实现的,允许服务器同时处理多个客户端的连接和关闭请求。以下是一些处理并发连接请求的方法:
-
多线程:
- 服务器可以为每个新的连接创建一个线程。当客户端连接到服务器时,服务器接受连接并创建一个新的线程来处理该连接。这样,每个连接都有自己的线程,可以在独立的上下文中进行通信和关闭。
-
多进程:
- 类似于多线程,服务器可以为每个连接创建一个新的进程。这种方法在操作系统级别提供了隔离,但相对于多线程,进程之间的通信开销更大。
-
非阻塞IO和事件驱动:
- 使用非阻塞IO和事件驱动的编程模型,如使用epoll(Linux)、kqueue(BSD)、IOCP(Windows)等,可以允许服务器在一个线程或进程中处理多个连接。这种模型通过在事件发生时(如数据到达、连接建立或关闭)进行回调,从而实现高效的并发处理。
-
线程池或进程池:
- 为了避免频繁创建和销毁线程或进程的开销,服务器可以使用线程池或进程池。预先创建一定数量的线程或进程,然后将它们分配给新的连接。这种方法可以平衡资源使用和性能。
-
异步IO:
- 异步IO允许服务器发起IO操作(如接收数据、发送数据、接受连接等)而无需等待操作完成。这样,服务器可以在单个线程或进程中处理多个连接,而不会因为等待IO操作而阻塞。
在处理并发的多个连接请求时,服务器还需要维护连接的状态,确保每个连接的正确关闭。这通常涉及到连接池的管理、连接超时的处理、以及异常情况下的资源清理等。
总之,处理并发的多个连接请求需要服务器具备高效的网络IO模型和资源管理策略。选择合适的方法取决于服务器的具体需求、性能目标以及运行环境。
结语
以上就是TCP三次握手,四次挥手的知识,本次分享到此结束。
最后的最后,还请大家点点赞,点点关注,谢谢大家!