实验内容
设置一个server和一个client,其中server只取出一个accept连接并原地阻塞,client在执行connect函数之后阻塞,查看server端的状态信息
内核部分参数配置
net.core.somaxconn
全连接队列长度 = min(backlog, 内核参数 net.core.somaxconn)
backlog 由int listen(int sockfd, int backlog)传入
修改方式 echo 1 > /proc/sys/net/core/somaxconn
net.ipv4.tcp_abort_on_overflow
超出处理能力时,对新来的SYN直接回RST,丢弃连接
0:关闭。1:开启
修改方式 echo 1 > /proc/sys/net/ipv4/tcp_abort_on_voerflow
net.ipv4.tcp_max_syn_backlog
SYN_RCVD状态连接的最大数(半连接队列的长度)
半连接队列长度 = min(backlog, 内核参数 net.core.somaxconn, 内核参数tcp_max_syn_backlog)
修改方式 echo 1 > /proc/sys/net/ipv4/tcp_max_syn_backlog
实验代码
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8089 // 端口号
#define BACKLOG 1 // 监听队列长度
int main() {
int listen_fd, conn_fd; // 监听套接字和连接套接字
struct sockaddr_in serv_addr, cli_addr; // 服务端和客户端地址结构
socklen_t cli_len; // 客户端地址长度
char buffer[1024]; // 缓冲区
int n; // 读写字节数
// 创建监听套接字
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket error");
exit(1);
}
// 设置服务端地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定监听套接字到服务端地址
if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind error");
exit(1);
}
// 开始监听
if (listen(listen_fd, 1) == -1) {
perror("listen error");
exit(1);
}
printf("Server is listening on port %d\n", PORT);
while (1) {
// 接受客户端连接请求
cli_len = sizeof(cli_addr);
conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
printf("成功接收!\n");
while(1)
{
}
if (conn_fd == -1) {
perror("accept error");
continue;
}
// printf("Client %s:%d connected\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
// // 读取客户端发送的数据
// n = read(conn_fd, buffer, sizeof(buffer));
// if (n == -1) {
// perror("read error");
// close(conn_fd);
// continue;
// }
// buffer[n] = '\0';
// printf("Received from client: %s\n", buffer);
// // 向客户端发送数据
// n = write(conn_fd, buffer, strlen(buffer));
// if (n == -1) {
// perror("write error");
// close(conn_fd);
// continue;
// }
// printf("Sent to client: %s\n", buffer);
// // 关闭连接套接字
// close(conn_fd);
// printf("Client %s:%d disconnected\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
}
// 关闭监听套接字
close(listen_fd);
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8089 // 端口号
#define SERVER_IP "101.33.199.60" // 服务端IP地址
int main() {
int sock_fd; // 套接字描述符
struct sockaddr_in serv_addr; // 服务端地址结构
char buffer[1024]; // 缓冲区
int n; // 读写字节数
// 创建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket error");
exit(1);
}
// 设置服务端地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 连接到服务端
if (connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("connect error");
exit(1);
}
printf("Connected to server %s:%d\n", SERVER_IP, PORT);
// 从标准输入读取数据
printf("Enter a message: ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符
while(1){}
// 向服务端发送数据
n = write(sock_fd, buffer, strlen(buffer));
if (n == -1) {
perror("write error");
exit(1);
}
printf("Sent to server: %s\n", buffer);
// // 读取服务端发送的数据
// n = read(sock_fd, buffer, sizeof(buffer));
// if (n == -1) {
// perror("read error");
// exit(1);
// }
// buffer[n] = '\0';
// printf("Received from server: %s\n", buffer);
// // 关闭套接字
// close(sock_fd);
return 0;
}
window下的客户端的话要增加2s的实验,因为发送RST报文到windows系统需要时间 , demo如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h> // For using inet_pton() or InetPton()
// Define the server address and port
#define SERVER_ADDR "101.33.199.60" // Change this to the actual Linux server IP address
#define SERVER_PORT 8089
// Define the buffer size and the message to send
#define BUFFER_SIZE 1024
#define MESSAGE "Hello from Windows client!"
int main() {
// Initialize the Winsock library
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
printf("WSAStartup failed: %d\n", result);
return 1;
}
// Create a socket for connecting to the server
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
printf("socket failed: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Set up the server address structure
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
// Use inet_pton() or InetPton() instead of inet_addr()
result = inet_pton(AF_INET, SERVER_ADDR, &(serverAddr.sin_addr));
// result = InetPton(AF_INET, SERVER_ADDR, &(serverAddr.sin_addr));
if (result <= 0) {
printf("inet_pton failed: %ld\n", WSAGetLastError());
closesocket(clientSocket);
WSACleanup();
return 1;
}
// Connect to the server
result = connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (result == SOCKET_ERROR) {
printf("connect failed: %ld\n", WSAGetLastError());
closesocket(clientSocket);
WSACleanup();
return 1;
}
printf("成功连接\n");
Sleep(2000);
// Send a message to the server
result = send(clientSocket, MESSAGE, strlen(MESSAGE), 0);
printf("result : %d\n", result);
if (result == SOCKET_ERROR) {
printf("send failed: %ld\n", WSAGetLastError());
closesocket(clientSocket);
WSACleanup();
return 1;
}
printf("result = : %d", result);
while (1) {}
// Close the socket and clean up the Winsock library
closesocket(clientSocket);
WSACleanup();
return 0;
}
实验步骤
- 运行服务端,让客户端正常连接
- 运行多个客户端,使得tcp的全连接队列满
- 再运行一个tcp客户端,使用tcpdump抓取报文信息
结果演示
查看端口连接情况(这里已经达到步骤b)
ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 2 1 *:8089
查看端口状态
netstat -ano |grep 8089
tcp 2 0 0.0.0.0:8089 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10421 ESTABLISHED off (0.00/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10468 ESTABLISHED off (0.00/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10390 ESTABLISHED off (0.00/0/0)
溢出情况查询(128是之前实验导致的。这里从128开始)
netstat -s | grep overflowed
128 times the listen queue of a socket overflowed
tcpdump报文情况(在满之前都是一样的,Tcp三次握手)
tcpdump port 8089
02:03:34.367641 IP 223.74.237.181.trim > VM-12-11-centos.8089: Flags [S], seq 2201118136, win 64240, options [mss 1424,nop,wscale 8,nop,nop,sackOK], length 0
02:03:34.367700 IP VM-12-11-centos.8089 > 223.74.237.181.trim: Flags [S.], seq 3189354376, ack 2201118137, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
02:03:34.391116 IP 223.74.237.181.trim > VM-12-11-centos.8089: Flags [.], ack 1, win 261, length 0
在Tcp全连接队列已满的情况下,我们再发起一个客户端连接,看看会发生什么
查看端口连接情况(没有变化)
ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 2 1 *:8089
查看端口状态 (多出一个SYN_RECV的状态)
netstat -ano |grep 8089
tcp 2 0 0.0.0.0:8089 0.0.0.0:* LISTEN keepalive (0.12/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10745 SYN_RECV on (4.52/3/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10421 ESTABLISHED off (0.00/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10468 ESTABLISHED off (0.00/0/0)
tcp 0 0 10.0.12.11:8089 223.74.237.181:10390 ESTABLISHED off (0.00/0/0)
溢出情况查询(128是之前实验导致的。这里从128开始)
129 times the listen queue of a socket overflowed
tcpdump报文情况
02:04:28.787681 IP 223.74.237.181.10745 > VM-12-11-centos.8089: Flags [S], seq 3213856928, win 64240, options [mss 1424,nop,wscale 8,nop,nop,sackOK], length 0
02:04:28.787742 IP VM-12-11-centos.8089 > 223.74.237.181.10745: Flags [S.], seq 335259074, ack 3213856929, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
02:04:28.807390 IP 223.74.237.181.10745 > VM-12-11-centos.8089: Flags [.], ack 1, win 261, length 0
02:04:28.807423 IP VM-12-11-centos.8089 > 223.74.237.181.10745: Flags [R], seq 335259075, win 0, length 0
可以看到,当全连接队列满时,服务端收到客户端的SYN请求,仍会发送SYN应答,但当客户端发送第三次握手信息时,由于全连接队列已满故内核会丢掉此次连接。又由于我们配置了RST重置,所以我们会看到服务端发送了一个RST报文。
[root@VM-12-11-centos test_demo]# ./client
connect error: Connection reset by peer
当再次运行客户端时可以发现客户端直接断开了连接