TCP是一种面向连接、端到端可靠的协议,它被设计用于在互联网上传输数据和确保成功传递数据和消息。本节来介绍一下TCP中的三次握手和四次挥手。
文章目录
- 1 TCP头部格式
- 2 wireshark抓包分析
- 2.1 SEQ和ACK
- 2.2 三次握手
- 2.3 四次挥手
- 3 程序
1 TCP头部格式
TCP头部占据TCP段的前20个字节,其中包含端到端TCP套接字的参数和状态。如下图所示:
下面来逐个解释一下这些字段:
- 源端口(
Source port
):16位,用于标识源端口号(发送方的TCP端口) - 目标端口(
Destination port
):16位,用于标识目标端口号(接收方的TCP端口) - 序列号(
Sequence number
):32位,指示在TCP会话期间发送了多少数据。当建立新的TCP连接时,初始序列号是一个随机值。 - 确认号(``Acknowledgment Number`):32位,由接收方用于请求下一个TCP段。如果设置了ACK控制位,该字段包含段发送方期望接收的下一个序列号的值。一旦建立了连接,这个字段总是被发送。
- 数据偏移(
Data offset
):4位,显示头部中32位字的数量,也称为头部长度 - 保留数据(
Reserved data
):6位,保留字段,始终设置为零 - 控制位标志(
Control bit Flags
):TCP使用9位控制标志来管理特定情况下的数据流,例如建立连接、发送数据和终止连接URG
: 与后面的紧急指针字段相关,当设置了此位时,数据应被视为优先于其他数据。ACK
: 与ACK
相关,确认字段用于指示已成功接收到的数据量,如果设置了此字段,说明发送方期望接收方继续发送下一个TCP段PSH
: 推送功能,表示发送方希望接收方立即传输数据,而不必等到整个TCP段的数据都准备好再传输RST
: 重置连接,仅在存在无法恢复的错误时使用SYN
: 同步序列号,此标志用于设置初始序列号FIN
: 完成位用于结束TCP连接,因为TCP是全双工连接,所以双方都必须使用FIN位来结束连接
- 窗口(
Window
):16位,指定接收方愿意接收多少字节 - 校验和(
Checksum
):16位,用于对头部和数据进行错误检查 - 紧急指针(
Urgent Pointer
):如果设置了URG控制标志,该值表示与序列号的偏移,指示最后一个紧急数据字节 - 选项(
Options
):可选,长度可为0~320位之间的任意大小
2 wireshark抓包分析
程序流程:服务端监听本地环回地址127.0.0.1的12345端口,客户端则连接这个端口,连接上后服务端发送一个Hello, World!
给客户端。
先来了解一下SEQ
和ACK
的概念:
2.1 SEQ和ACK
客户端和服务器之间建立TCP连接时会进行三次握手。先来理解一下SEQ
、ACK
的概念:
- 序列号(
SEQ
): 表示发送方发送的数据的起始位置。每发送一个新的数据段,序列号就会递增。 - 确认号(
ACK
): 表示接收方期望下次收到的数据的序列号。当接收方收到数据后,它会发送一个带有确认号的ACK,告诉发送方它已成功接收到了特定序列号之前的所有数据。 - 下一个期望的ACK: 当接收方收到一段数据时,
ACK
表示已成功接收的数据的下一个期望的序列号。因此,下一个期望的ACK
号通常是上一个ACK
号加上接收到的数据的长度。
2.2 三次握手
1、客户端发送SYN
给服务端
- 在wireshark中
SEQ
使用相对0的值,为的是方便分析,所以这里是0
从上图中可以看到SYN
标识被设置:
2.服务端回复带有SEQ
和ACK
的SYN-ACK
数据包
如下图所示:
3.客户端向服务器发送一个带有ACK
号的数据包,确认服务器的序列号
如下图所示:
此时双方的SEQ
已同步,以上就是三次握手的内容。下面客户端和服务器可以独立地发送和接收数据。
4.服务器向客户端发送“Hello, World!”
5.客户端向服务器发送一个ACK号,确认服务器的消息
上一个ACK
号为1,长度为13,因此ACK
号将为13+1=14。
2.3 四次挥手
接着上面的抓包来看,在程序中,服务端发送完“Hello, World!”后就关闭了客户端的socket。
1.服务端发送FIN
给客户端
如下图所示:
2.客户端向服务器发送一个ACK号,确认服务器的FIN
请求
如下图所示:
3.TCP是一种全双工连接,因此,客户端也向服务器发送一条消息以关闭连接
- 前面的图中最后一行是红色的
RST
是我不小心直接关闭了程序,下面的图为重新抓的包,注意看最后两条即可
如下图所示:
4.服务器向客户端发送一个ACK号,确认客户端的FIN请求
如下图所示:
3 程序
本文的代码使用Windows环境下的网络编程库,所以需要在链接库中增加ws2_32
。
服务端
// Server.c
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
int main() {
WSADATA wsaData;
SOCKET listenSocket, clientSocket;
struct sockaddr_in serverAddr, clientAddr;
int addrLen = sizeof(clientAddr);
char buffer[1024] = "Hello, World!";
// Initialize Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed\n");
return 1;
}
// Create a socket
if ((listenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
fprintf(stderr, "Socket creation failed\n");
WSACleanup();
return 1;
}
// Set up server address information
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(12345);
// Bind the socket
if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
fprintf(stderr, "Bind failed\n");
closesocket(listenSocket);
WSACleanup();
return 1;
}
// Listen for incoming connections
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
fprintf(stderr, "Listen failed\n");
closesocket(listenSocket);
WSACleanup();
return 1;
}
printf("Server listening on 127.0.0.1:12345\n");
// Accept a connection from a client
if ((clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &addrLen)) == INVALID_SOCKET) {
fprintf(stderr, "Accept failed\n");
closesocket(listenSocket);
WSACleanup();
return 1;
}
// Send data to the client
send(clientSocket, buffer, strlen(buffer), 0);
printf("Data sent to the client\n");
// Clean up
closesocket(clientSocket);
closesocket(listenSocket);
WSACleanup();
return 0;
}
客户端
// Client.c
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
int main() {
WSADATA wsaData;
SOCKET clientSocket;
struct sockaddr_in serverAddr;
char buffer[1024];
// Initialize Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed\n");
return 1;
}
// Create a socket
if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
fprintf(stderr, "Socket creation failed\n");
WSACleanup();
return 1;
}
// Set up server address information
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(12345);
// Connect to the server
if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
fprintf(stderr, "Connection failed\n");
closesocket(clientSocket);
WSACleanup();
return 1;
}
// Receive data from the server
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
buffer[bytesRead] = '\0'; // Null-terminate the received data
printf("Received data from server: %s\n", buffer);
} else {
fprintf(stderr, "Error receiving data\n");
}
while(1)
{
if(recv(clientSocket, buffer, sizeof(buffer), 0) == 0)
{
break;
}
}
// Clean up
closesocket(clientSocket);
WSACleanup();
return 0;
}