浙江省住房和城乡建设厅干部学校网站,网站导航栏按钮,dwcc2017怎么做网站,wordpress七牛图床1.什么是TCP的状态转换
TCP#xff08;Transmission Control Protocol#xff0c;传输控制协议#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议。在 TCP 连接的生命周期中#xff0c;连接的状态会随着不同阶段的通信而发生变化#xff0c;这些变化被称为状…1.什么是TCP的状态转换
TCPTransmission Control Protocol传输控制协议是一种面向连接的、可靠的、基于字节流的传输层协议。在 TCP 连接的生命周期中连接的状态会随着不同阶段的通信而发生变化这些变化被称为状态转换。
在TCP进行三次握手或者四次挥手的过程中通信的服务器和客户端内部会发送状态上的变化发生的状态变化在程序中是看不到的这个状态的变化也不需要程序猿去维护但是在某些情况下进行程序的调试会去查看相关的状态信息先来看三次握手过程中的状态转换。 2.三次握手的状态转换
在第一次握手之前服务器端必须先启动并且已经开始了监听- 服务器端先调用了 listen() 函数, 开始监听- 服务器启动监听前后的状态变化: 没有状态 --- LISTEN
当服务器监听启动之后由客户端发起的三次握手过程中状态转换如下
第一次握手 客户端 操作 调用 connect() 函数发送 SYN 包同步包。状态变化 CLOSED - SYN_SENT 服务器 操作 监听状态下收到客户端的 SYN 包。状态变化 LISTEN - SYN_RCVD
第二次握手 服务器 操作 向客户端发送 SYN-ACK 包同步确认包确认收到客户端的 SYN 包并请求建立连接。状态变化 SYN_RCVD保持不变 客户端 操作 收到服务器的 SYN-ACK 包确认连接请求。状态变化 SYN_SENT - ESTABLISHED
第三次握手 客户端 操作 向服务器发送 ACK 包确认包确认服务器的 SYN-ACK 包。状态变化 ESTABLISHED保持不变 服务器 操作 收到客户端的 ACK 包确认建立连接。状态变化 SYN_RCVD - ESTABLISHED
三次握手完成之后客户端和服务器都变成了同一种状态这种状态叫ESTABLISHED表示双向连接已经建立 可以通信了。在通过过程中正常的通信状态就是 ESTABLISHED。
根据上面图片和描述可以更直观地看到 TCP 三次握手过程中客户端和服务器的状态转换。通过三次握手客户端和服务器从初始状态逐步进入 ESTABLISHED 状态完成连接建立。 客户端 CLOSED - SYN_SENT - ESTABLISHED 服务器 LISTEN - SYN_RCVD - ESTABLISHED
3.四次挥手的状态转换
关于四次挥手对于客户端和服务器哪段先断开连接没有要求根据实际情况处理即可。下面根据上图中的实例描述一下四次挥手过程中TCP的状态转换上图中主动断开连接的一方是客户端
第一次挥手 客户端 操作 调用 close() 函数将 TCP 协议中的 FIN 设置为 1请求与服务器断开连接。状态变化 ESTABLISHED - FIN_WAIT_1 服务器 操作 收到客户端的 FIN 包表示客户端请求断开连接。状态变化 ESTABLISHED - CLOSE_WAIT
第二次挥手 服务器 操作 回复 ACK 包同意断开连接的请求。状态变化 CLOSE_WAIT保持不变 客户端 操作 收到服务器的 ACK 包确认服务器已同意断开连接。状态变化 FIN_WAIT_1 - FIN_WAIT_2
第三次挥手 服务器 操作 调用 close() 函数发送 FIN 包给客户端请求断开连接。状态变化 CLOSE_WAIT - LAST_ACK 客户端 操作 收到服务器的 FIN 包确认断开连接请求。状态变化 FIN_WAIT_2 - TIME_WAIT
第四次挥手 客户端 操作 回复 ACK 包给服务器确认断开连接。状态变化 TIME_WAIT - CLOSED 服务器 操作 收到客户端的 ACK 包确认客户端已同意断开连接。状态变化 LAST_ACK - CLOSED
根据上面图片和描述可以更直观地看到 TCP 四次挥手过程中客户端和服务器的状态转换。通过四次挥手客户端和服务器从 ESTABLISHED 状态逐步进入 CLOSED 状态完成连接断开。 客户端 ESTABLISHED - FIN_WAIT_1 - FIN_WAIT_2 - TIME_WAIT - CLOSED 服务器 ESTABLISHED - CLOSE_WAIT - LAST_ACK - CLOSED
4.TCP状态转换图
在下图中同样是描述TCP通信过程中的客户端和服务器端的状态转图片中主要关注两条主线红色实线客户端状态和绿色虚线服务器端状态。黑色实线表示的是一些特殊情况下的状态切换在此不做分析。因为三次握手是由客户端发起的据此分析红色的实线表示的客户端的状态绿色虚线表示的是服务器端的状态。 客户端状态转换 第一次握手 操作 发送 SYN 包。状态变化 CLOSED - SYN_SENT 第二次握手 操作 收到服务器回复的 SYN-ACK 包。状态变化 SYN_SENT - ESTABLISHED 主动断开连接第一次挥手 操作 发送 FIN 包请求断开连接。状态变化 ESTABLISHED - FIN_WAIT_1 第二次挥手 操作 收到服务器的 ACK 包确认服务器同意断开连接。状态变化 FIN_WAIT_1 - FIN_WAIT_2 第三次挥手 操作 收到服务器的 FIN 包确认断开连接请求。状态变化 FIN_WAIT_2 - TIME_WAIT 第四次挥手 操作 回复 ACK 包给服务器确认断开连接。等待 2 倍报文时长后连接关闭。状态变化 TIME_WAIT - CLOSED
服务器端状态转换 启动监听 操作 服务器启动监听等待客户端连接。状态变化 CLOSED - LISTEN 第一次握手 操作 收到客户端的 SYN 包。状态变化 LISTEN - SYN_RCVD 第三次握手 操作 收到客户端的 ACK 包确认连接建立。状态变化 SYN_RCVD - ESTABLISHED 收到断开连接请求第一次挥手 操作 收到客户端的 FIN 包请求断开连接。状态变化 ESTABLISHED - CLOSE_WAIT 第三次挥手 操作 发送 FIN 包请求与客户端断开连接。状态变化 CLOSE_WAIT - LAST_ACK 第四次挥手 操作 收到客户端的 ACK 包确认断开连接。状态变化 LAST_ACK - CLOSED
TIME_WAIT 状态的作用
在 TCP 通信中主动断开连接的一方在收到被动断开连接一方发送的 FIN 包和最终的 ACK 包即第三次挥手完成后必须处于 TIME_WAIT 状态并持续 2 倍 MSLMaximum Segment Lifetime时间。这么做的目的是 确保 ACK 包能被正确接收 如果最终的 ACK 包丢失服务器会重传 FIN 包客户端在 TIME_WAIT 状态期间可以重新发送 ACK 包。 防止旧连接影响新连接 TIME_WAIT 状态确保旧连接在消失前有足够的时间让延迟的报文在网络中完全消失避免旧连接的数据影响新连接。
5.Linux查看网络状态相关命令
$ netstat 参数
$ netstat -apn | grep 关键字
参数:
-a (all)显示所有选项
-p 显示建立相关链接的程序名
-n 拒绝显示别名能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服务状态
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
6.半关闭
TCP连接只有一方发送了FIN另一方没有发出FIN包仍然可以在一个方向上正常发送数据这中状态可以称之为半关闭或者半连接。当四次挥手完成两次的时候就相当于实现了半关闭在程序中只需要在某一端直接调用 close() 函数即可。套接字通信默认是双工的也就是双向通信如果进行了半关闭就变成了单工数据只能单向流动了。比如下面的这个例子
服务器端:
调用了close() 函数因此不能发数据只能接收数据 关闭了服务器端的写操作现在只能进行读操作 – 变成了读端客户端:
没有调用close()客户端和服务器的连接还保持着
客户端可以给服务器发送数据也可以接收服务器发送的数据 但是服务器已经丧失了发送数据的能力因此客户端也只能发送数据接收不到数据 – 变成了写端
半关闭的工作机制
TCP 提供了一种半关闭机制通过调用 shutdown() 函数来实现。shutdown() 函数允许应用程序分别关闭一个 TCP 连接的读或写操作而不是完全关闭连接。
#include sys/socket.h
int shutdown(int sockfd, int how);
sockfd套接字描述符。
howSHUT_RD关闭读取功能。SHUT_WR关闭写入功能。SHUT_RDWR同时关闭读取和写入功能。
半关闭的示例
以下是一个示例展示了客户端如何实现半关闭通知服务器它已经完成了数据发送但仍然可以接收来自服务器的数据。
//服务端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int opt 1;int addrlen sizeof(address);char buffer[BUFFER_SIZE] {0};// 创建套接字if ((server_fd socket(AF_INET, SOCK_STREAM, 0)) 0) {perror(socket failed);exit(EXIT_FAILURE);}// 设置端口复用if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, opt, sizeof(opt))) {perror(setsockopt);close(server_fd);exit(EXIT_FAILURE);}address.sin_family AF_INET;address.sin_addr.s_addr INADDR_ANY;address.sin_port htons(PORT);// 绑定套接字if (bind(server_fd, (struct sockaddr *)address, sizeof(address)) 0) {perror(bind failed);close(server_fd);exit(EXIT_FAILURE);}// 监听套接字if (listen(server_fd, 3) 0) {perror(listen);close(server_fd);exit(EXIT_FAILURE);}// 接受连接if ((new_socket accept(server_fd, (struct sockaddr *)address, (socklen_t *)addrlen)) 0) {perror(accept);close(server_fd);exit(EXIT_FAILURE);}// 接收来自客户端的数据read(new_socket, buffer, BUFFER_SIZE);printf(Server received: %s\n, buffer);// 发送数据给客户端const char *response Hello from server;send(new_socket, response, strlen(response), 0);// 继续接收来自客户端的数据memset(buffer, 0, BUFFER_SIZE);read(new_socket, buffer, BUFFER_SIZE);printf(Server received: %s\n, buffer);close(new_socket);close(server_fd);return 0;
}
//客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sock 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] {0};// 创建套接字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(PORT);// 将地址转换为二进制形式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;}// 发送数据给服务器const char *message Hello from client;send(sock, message, strlen(message), 0);// 半关闭关闭写入功能但仍保持读取功能shutdown(sock, SHUT_WR);// 接收来自服务器的响应read(sock, buffer, BUFFER_SIZE);printf(Client received: %s\n, buffer);// 尝试再发送数据会失败因为写入功能已关闭const char *another_message This will not be sent;ssize_t bytes_sent send(sock, another_message, strlen(another_message), 0);if (bytes_sent -1) {perror(send after shutdown);}close(sock);return 0;
}