我正在嘗試為我的課程專案進行 Apache 性能基準測驗。但是我遇到了一個奇怪的問題。當我使用單個客戶端與 Apache 服務器建立多個 TCP 連接(例如 100 個)并發送帶有Connection: keep-alive標頭的HTTP 1.1 請求時,我認為可以重用 TCP 連接。但是Apache服務器會主動終止TCP連接,即使HTTP回應頭中包含Connection:Keep-Alive和Keep-Alive:xxx。
這是我的客戶端代碼(我混淆了 IP 地址):
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <sys/time.h>
#define DEBUG
#define MAX_SOCKETS_NUM 100
#define BUF_LEN 4096
int main() {
int i;
struct sockaddr_in server_addr, peer_addr;
int res, len = sizeof(peer_addr);
char *req = "GET /20KB HTTP/1.1\r\n"
"Host: x.x.x.192\r\n"
"Connection: keep-alive\r\n"
"Upgrade-Insecure-Requests: 1\r\n"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\r\n"
"Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Accept-Language: zh-CN,zh;q=0.9\r\n"
"\r\n";
char buf[BUF_LEN 1];
int sockets[MAX_SOCKETS_NUM];
int estb_num = 0;
int estb_map[MAX_SOCKETS_NUM];
struct timeval goal, now, interval;
// create sockets
for (i = 0; i < MAX_SOCKETS_NUM; i ) {
if ((sockets[i] = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) {
printf("Socket %d error!\n", i);
return -1;
}
}
// initialize server_addr
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);
if (inet_pton(AF_INET, "x.x.x.192", &server_addr.sin_addr) <= 0) {
printf("Invalid address/Address not supported\n");
return -1;
}
// connect to the victim server
for (i = 0; i < MAX_SOCKETS_NUM; i ) {
if ((res = connect(sockets[i], (struct sockaddr *)&server_addr, sizeof(server_addr))) == 0) {
#ifdef DEBUG
printf("Socket %d connected immediately.\n", i);
#endif
estb_map[i] = 1;
estb_num ;
}
else if (res == -1) {
if (errno != EINPROGRESS) {
printf("Error occured when connect() is called on socket %d.\n", i);
return -1;
}
#ifdef DEBUG
printf("Socket %d sends SYN packet but the ACK is not received.\n", i);
#endif
}
}
while (1) {
if (estb_num == MAX_SOCKETS_NUM)
break;
for (i = 0; i < MAX_SOCKETS_NUM; i ) {
while (1) {
res = getpeername(sockets[i], (struct sockaddr *)&peer_addr, &len);
if (res == 0) {
estb_num ;
#ifdef DEBUG
printf("Socket %d connects successfully.\n", i);
#endif
break;
}
}
}
}
interval = (struct timeval) {
.tv_sec = 1,
.tv_usec = 0
};
len = strlen(req);
while (1) {
gettimeofday(&now, NULL);
timeradd(&now, &interval, &goal);
#ifdef DEBUG
printf("%ld.%ld\n", goal.tv_sec, goal.tv_usec);
#endif
// send requests
for (i = 0; i < MAX_SOCKETS_NUM; i ) {
res = send(sockets[i], req, len, 0);
if (res == -1) {
printf("socket:%ld errno:%ld\n", i, errno);
}
}
while (1) {
for (i = 0; i < MAX_SOCKETS_NUM; i ) {
res = recv(sockets[i], buf, BUF_LEN, 0);
if (res == 0) {
struct sockaddr_in dbg_addr;
int dgb_addr_len = sizeof(struct sockaddr_in);
getsockname(sockets[i], &dbg_addr, &dgb_addr_len);
printf("socket:%d port:%d\n", i, ntohs(dbg_addr.sin_port));
goto end;
}
else if (res == -1) {
if (errno != EAGAIN)
printf("Error occurs when recv is called.\n");
}
else {
// do nothing because we don't need the response
}
}
gettimeofday(&now, NULL);
if (timercmp(&now, &goal, >) != 0) {
#ifdef DEBUG
printf("%d.%d\n", now.tv_sec, now.tv_usec);
#endif
break;
}
}
}
end:
return 0;
}
客戶端(IP地址為xxx198)的作業流程是:
使用非阻塞套接字與服務器(IP 地址為 xxx192)建立 100 個 tcp 連接。
在 100 個 tcp 連接上發送相同的請求
在這 100 個套接字上重復呼叫非阻塞
recv1 秒轉到2。
程式的一次執行會生成以下輸出:
Socket 0 sends SYN packet but the ACK is not received.
(some lines are omitted)
Socket 99 sends SYN packet but the ACK is not received.
Socket 0 connects successfully.
(some lines are omitted)
Socket 99 connects successfully.
1639450192.343129
1639450192.343155
1639450193.343163
socket:63 port:56804
輸出表明在第二輪請求中(第一輪完成,因為列印了兩個時間戳,但第二輪只列印一個),recv本地埠號為56804的第63個socket上的函式回傳0,表示Apache服務器主動終止 tcp 連接。并且我用tcpdump把客戶端上的所有包都dump出來了,下圖是本地埠號為56804的連接的包跟蹤:

資料包跟蹤顯示了相同的結果,即服務器主動向客戶端發送 tcp FIN資料包以終止 TCP 連接。但是我們可以看到Connection: Keep-Alive和Keep-Alive: timeout=10m, max=1999包含在回應頭中,這意味著 Apache 服務器正確處理了 keep-alive。
服務器運行 Ubuntu 20.04.3 和 Apache 2.4.41。
我很困惑為什么會發生這種情況,為什么 Apache 會關閉保持活動的連接?如果您能幫助我,我將不勝感激,謝謝!
uj5u.com熱心網友回復:
從這個檔案:
主機可以保持空閑連接打開的時間比它指示的時間長,但它應該嘗試至少在指示的時間內保持連接。
大寫字母有key,在檔案中是這樣寫的:
您的情況與“應該”部分匹配。例如,keep-alive 是一種建議——但如果服務器需要這些資源(或配置為打開連接數少于客戶端數量),則可以隨意關閉它們。您的客戶將需要處理這種狀態。
如果所描述的行為取決于您打開的并行套接字的數量(超時除外),則您很可能會遇到服務器上的資源限制 - 無論是顯式配置還是隱式,默認值。
想象一下,如果只需要幾個保持活動的請求來使服務器提供的并發連接數達到飽和,那么 DDOS 攻擊將是多么容易。
另請注意,超時是基于服務器(從發送最后一個資料包開始)和客戶端(從接收最后一個資料包開始)對時間的不同感知
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/385063.html
