TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
一、tcp协议头部项解释
TCP 头部包含了用于管理两个端点之间通信的控制信息。
-
源端口号(Source Port
- 16位,标识发送方的端口号。
-
目的端口号(Destination Port)
- 16位,标识接收方的端口号。
-
序列号(Sequence Number)
- 32位,用于为字节流中的每个字节进行编号,确保数据的有序传输。
-
确认号(Acknowledgment Number)
- 32位,期望收到的下一个字节的序列号,用作对已接收数据的确认。
-
数据偏移(Data Offset)
- 4位,指示 TCP 头部的长度(以32位字为单位),因为 TCP 头部可能包含可变数量的选项字段。
-
保留(Reserved)
- 6位,保留供将来使用,当前必须设置为0。
-
控制位(Control Bits)
- 6位,用于控制TCP的不同功能,包括:
- URG(紧急指针有效)
- ACK(确认号有效)
- PSH(接收方应尽快将缓冲区数据推送给应用程序)
- RST(重置连接)
- SYN(同步序列开始,用于建立连接)
- FIN(结束一个连接)
- 6位,用于控制TCP的不同功能,包括:
-
窗口大小(Window Size)
- 16位,用于流量控制,指示接收端还能接收多少字节的数据。
-
校验和(Checksum)
- 16位,用于检测头部和数据的完整性。
-
紧急指针(Urgent Pointer)
- 16位,仅当URG控制位被设置时使用,指示紧急数据的结束位置。
-
选项(Options)
- 可变长度,用于提供额外的信息或配置,如最大报文段长度(MSS)、窗口缩放因子、选择性确认(SACK)等。
二、tcp工作在那一层
传输层
三、windows平台C++实现demo
服务端代码
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib") // 指定链接Winsock库
#define PORT 8888 // 服务器监听的端口
int main() {
WSADATA wsaData;
SOCKET serverSocket, clientSocket;
struct sockaddr_in serverAddr, clientAddr;
int clientAddrLen = sizeof(clientAddr);
char buffer[1024];
// 初始化Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
std::cerr << "Could not create socket: " << WSAGetLastError() << std::endl;
return 1;
}
// 设置服务器地址信息
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
// 绑定套接字
if (bind(serverSocket, (PSOCKADDR)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Bind failed with error: " << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 开始监听
listen(serverSocket, 5);
// 接受客户端连接
clientSocket = accept(serverSocket, (PSOCKADDR)&clientAddr, &clientAddrLen);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "Accept failed: " << WSAGetLastError() << std::endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 接收数据
int iResult = recv(clientSocket, buffer, sizeof(buffer), 0);
if (iResult > 0) {
std::cout << "Bytes received: " << iResult << std::endl;
std::cout << "Message: " << buffer << std::endl;
}
// 发送响应数据
const char* response = "Hello, client!";
iResult = send(clientSocket, response, (int)strlen(response), 0);
if (iResult == SOCKET_ERROR) {
std::cerr << "Send failed: " << WSAGetLastError() << std::endl;
}
// 关闭套接字
closesocket(clientSocket);
closesocket(serverSocket);
// 清理Winsock
WSACleanup();
return 0;
}
客户端代码
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib") // 指定链接Winsock库
#define IP "127.0.0.1" // 服务器IP地址
#define PORT 8888 // 服务器端口
int main() {
WSADATA wsaData;
SOCKET clientSocket;
struct sockaddr_in serverAddr;
char buffer[1024];
// 初始化Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "Could not create socket: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 设置服务器地址信息
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(IP);
serverAddr.sin_port = htons(PORT);
// 连接到服务器
if (connect(clientSocket, (PSOCKADDR)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Connect failed: " << WSAGetLastError() << std::endl;
closesocket(clientSocket);
WSACleanup();
return 1;
}
// 发送数据
const char* message = "Hello, server!";
int iResult = send(clientSocket, message, (int)strlen(message), 0);
if (iResult == SOCKET_ERROR) {
std::cerr << "Send failed: " << WSAGetLastError() << std::endl;
}
// 接收响应数据
iResult = recv(clientSocket, buffer, sizeof(buffer), 0);
if (iResult > 0) {
std::cout << "Bytes received: " << iResult << std::endl;
std::cout << "Message: " << buffer << std::endl;
}
// 关闭套接字
closesocket(clientSocket);
// 清理Winsock
WSACleanup();
return 0;
}
上述代码,只是一个最简单的实力。未考虑多线程,多个客户端同时向服务端连接情况。
涉及的结构体和API
数据结构:
1. SOCKET:用于标识一个通信端点的句柄。
2. sockaddr_in:用于存储IPv4地址信息的结构体,包括地址族(sin_family)、IP地址(sin_addr)和端口号(sin_port)。
3. WSADATA:用于存储与Winsock DLL的版本信息相关的数据。
Winsock API:
1. WSAStartup:初始化Winsock服务,加载Winsock DLL,并设置应用程序的版本号。
2. socket:创建一个端点(即套接字)。
3. bind:将一个本地地址绑定到套接字上。
4. listen:使套接字成为监听套接字,等待进入连接。
5. accept:接受一个连接请求,返回一个新的套接字用于通信。
6. connect:在客户端上,用于连接到服务器的套接字。
7. send:发送数据到连接的套接字。
8. recv:从连接的套接字接收数据。
9. closesocket:关闭套接字,释放资源。
10. WSACleanup:清除Winsock使用的所有资源,与WSAStartup相对应。
其它:
1. inet_addr:将点分十进制的IP地址转换为网络字节顺序的整数。
2. htons:将主机字节顺序的16位整数转换为网络字节顺序。
3. htonl:将主机字节顺序的32位整数转换为网络字节顺序。
概念:
- 端口号(PORT):用于标识特定的服务或进程。
- IP地址:标识网络中的设备位置。
- 字节序转换:由于网络通信使用大端字节序,因此需要将主机字节序转换为网络字节序。
四、连接的建立过程-三次握手
第一次:客户端:向服务端发送SYN包
第二次:服务端:向客户端SYN、ACK包
第三次:客户端:向服务端ACK包
注:在建立连接时,第三次握手是可以携带数据的。
五、连接的断开过程-四次挥手
如果是客户端想要断开连接:
客户端:发送FIN包
服务端:发送ACK包
服务端:发送FIN包
客户端:发送ACK包
如果是服务端想要断开连接
服务端:发送FIN包
客户端:发送ACK包
客户端:发送FIN包
服务端:发送ACK包
六、SYN攻击
当服务器处于listen监听状态时,所有的客户端都可以向他发送连接请求。
此时,如果有人恶意连接,即持续不断的发送大量的SYN包给服务器,此时服务器会忙不过来。待连接队列会满。正常需要连接的普通客户端的SYN包可能无法被处理,称之为SYN攻击