我正在 Linux 上用 C 语言调试一个非常基本的 tcp 服务器。我在调用 Accept() 的行之前停止了执行。令我惊讶的是,当客户端发送 SYN 时,tcpdump 显示服务器以 SYN-ACK 进行响应(客户端很容易用最终 ACK 进行回复)。
ss 命令确实显示应用程序已经在监听绑定的端口。
我知道我已经调用了listen(),所以应用程序将监听绑定的端口。但是,按照相同的语义,应该在服务器接受连接之前调用accept()。
在listen()手册页中,它读到(斜体是我的):
Listen() 将 sockfd 引用的套接字标记为被动套接字,即,作为将用于使用accept(2) 接受传入连接请求的套接字。
虽然accept()手册页说:
它提取侦听套接字的挂起连接队列上的第一个连接请求
由此可见,应该在建立连接之前调用accept()。
我在这里缺少什么?如果这是标准行为,是否可以向我指出主要来源?或者它只是特定于实现的?
下面是我正在使用的代码。如果我在调用listen()之前停止其执行,则使用netcat会显示发送的SYN并用RST进行回复。但是如果我在执行listen()后执行相同的操作,tcpdump将显示服务器用SYN-ACK进行回复。
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
void error(const char* message) {
printf("%s %s\n", message, strerror(errno));
}
int main(int argc, char** argv) {
const int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd == -1 ) {
error("Socket error:");
return 1;
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if ( bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) == - 1 ) {
error("Bind error:");
return 1;
}
if ( listen(sockfd, 5) == -1 ) {
error("Listen error: ");
return 1;
}
printf("Ready.\n");
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr);
char response[512];
while (1) {
const int connfd = accept(sockfd, (struct sockaddr*) &cliaddr, &cliaddrlen);
if ( connfd == -1 ) {
printf("Accept error: %s\n", strerror(errno));
return 1;
}
const pid_t pid = fork();
if ( pid == -1 ) {
printf("Fork error: %s\n", strerror(errno));
continue;
}
if ( pid == 0 ) {
close(sockfd);
char buffer[16];
inet_ntop(AF_INET, &cliaddr.sin_addr, buffer, 16);
printf("Connection from %s accepted.\n", buffer);
while ( 1 ) {
int nread = read(connfd, response, 512);
if ( nread == -1 ) {
printf("%s\n", strerror(errno));
}
if (nread == 1 && response[0] == '\n') {
break;
}
write(connfd, response, nread);
//write(STDIN_FILENO, response, nread);
}
printf("Good bye!\n");
close(connfd);
return 0;
}
close(connfd);
wait(NULL);
}
return 0;
}
Listen() 的“backlog”整数参数(您在其中指定的参数
5
)决定可以接受多少个连接;达到该限制后,新的 SYN将被忽略或拒绝。它允许这些连接排队,例如在单线程服务器中,当 BSD 套接字 API 最初设计时很可能会找到(如果我的历史正确的话,它早于线程)。服务器可能必须先 fork() 才能接受下一个客户端,或者如果它是一个简单的协议(例如基本的 Whois 或 QotD 服务器),它甚至可能决定在同一线程中立即为该请求提供服务,然后再到达该客户端。下一个客户。
如果服务器根本没有响应,客户端很快就会达到连接超时;但是一旦建立了连接,超时时间可能会更长(例如,SMTP 定义了五分钟的超时时间,直到出现问候语)。话虽如此,我不知道在创建此 API 时什么样的延迟才是“正常”的,因此对此有一些猜测。
您无法区分用户空间应用程序的工作和内核的支持工作,在这种情况下,这也是应用程序层和传输层(及更低层)之间的分离。内核负责 TCP 握手,更一般地说,负责建立 TCP 级连接。应用程序的调用要求
listen()
它开始对入站传输层连接请求执行此操作。在用户空间方面,应用程序所在的连接套接字accepts()
为应用程序层提供服务,至少就应用程序需要知道的而言。不,不是你的意思是“在建立 TCP连接之前”。
accept()
提供处理应用程序级连接请求。用户空间通常看不到低于该值的任何内容。此外,推迟握手的完成有何目的呢?