我正在 Linux 上用 C 语言调试一个非常基本的 tcp 服务器。我在调用 Accept() 的行之前停止了执行。令我惊讶的是,当客户端发送 SYN 时,tcpdump 显示服务器使用 SYN-ACK 进行响应(客户端很容易用最终 ACK 进行回复)。
ss 命令确实显示应用程序已经在监听绑定的端口。
我知道我已经调用了listen(),所以应用程序将监听绑定的端口。但是,按照相同的语义,应该在服务器接受连接之前调用accept()。
在listen()手册页中它读到(斜体是我的):
虽然accept()手册页说:listen() 将 sockfd 引用的套接字标记为被动套接字,即,作为将用于使用accept(2)接受传入连接请求的套接字。
它提取监听套接字的待处理连接队列中的第一个连接请求
由此可知,应该在建立连接之前调用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;
}
如果服务器根本没有响应,客户端很快就会达到连接超时;但一旦建立连接,超时可能会更长(例如,SMTP 定义超时为
五分钟,直到出现问候语)。话虽这么说,我不知道在创建此 API 时什么样的延迟是“正常”的,因此对此有一些猜测。 listen() 的“backlog”整数参数决定了可以接受多少个连接;达到该限制后,新的 SYN
将被忽略或拒绝。