TCP的三次握手是发生在什么阶段

先上一段代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(){
    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(3000);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(-1 == bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))){
        printf("bind error: %s\n", strerror(errno));
    }

    listen(socket_fd, 10);
    printf("listen finished: %d\n", socket_fd);
    
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

#if 1   //只能有一个连接能收发数据
    printf("begin accept\n");
    int client_fd = accept(socket_fd, (struct sockaddr*)&client_addr, &client_addr_len);
    printf("accept finished: %d \n", client_fd);
    char buf[1024];
    while(1){
        memset(buf, 0, sizeof(buf));
        int len = recv(client_fd, buf, sizeof(buf), 0);
        printf("recv: %s\n", buf);
        int count = send(client_fd, buf, len, 0);
        printf("send: %d\n", count);
    }

    return 0;
#endif
}

在服务端运行上述代码,客户端发送一个连接,能够顺利连接并正常收发数据,保持该连接。

但是当你再发一个连接的时候,你会发现连接是成功的,通过服务端的netstat查看也能发现有2个客户端连接是ESTABLISHED状态。

这是否说明了TCP连接的三次握手是发生在listen()函数中呢?

TCP三次握手是建立连接的过程,涉及客户端和服务端之间的SYN、SYN-ACK、ACK包的交换。而listen()和accept()是服务器端使用的系统调用。根据我的理解,listen()函数将套接字设置为被动监听状态,准备好接受连接请求。而accept()则从已建立的连接队列中取出一个连接,并返回新的套接字描述符。

那么三次握手是在什么时候完成的呢?根据TCP协议,当服务器调用listen()后,内核会开始处理传入的连接请求。当客户端发送SYN包时,服务器响应SYN-ACK,完成三次握手后,连接会被放入已完成连接队列。这时候,accept()函数的作用是从这个队列中取出连接,而实际的握手过程是在内核中完成的,与accept()的调用时机无关。因此,三次握手发生在listen()之后,accept()之前。

可能用户之前以为accept()才会触发握手过程,但实际上accept()只是取出已经建立的连接。如果服务器没有及时调用accept(),已完成队列可能会满,导致新的连接无法建立,但这不影响已经完成三次握手的连接。 需要确认是否有例外情况,比如backlog参数的影响,或者不同操作系统的实现差异。例如,listen()的backlog参数决定了已完成队列的长度,这可能会影响处理的连接数,但不会改变三次握手的触发时机。 总结起来,三次握手是在listen()调用之后由内核处理的,而accept()只是从队列中取出已建立的连接。

关键流程解析

1. listen() 的作用
  • 将套接字标记为被动监听状态,通知操作系统内核开始接收客户端连接请求。
  • 内核会为套接字维护两个队列:
    • 未完成连接队列(SYN队列):存放收到SYN包但未完成三次握手的连接(半连接)。
    • 已完成连接队列(Accept队列):存放已完成三次握手的连接(全连接)。
2. 三次握手触发时机
  • 当客户端发送 SYN包 到服务器时,内核自动处理握手:
    1. 客户端发送 SYN(第一次握手)
    2. 服务器响应 SYN-ACK(第二次握手)
    3. 客户端发送 ACK(第三次握手)
  • 握手完成后,连接进入已完成队列,等待 accept() 提取。
3. accept() 的作用
  • 从已完成队列中取出一个已建立的连接,返回新套接字描述符。
  • 如果队列为空,默认会阻塞等待新连接(除非套接字设为非阻塞模式)。

常见误区澄清

  1. accept() 不参与握手
    即使服务端未调用 accept(),三次握手仍会由内核完成。未及时调用 accept() 只会导致已完成队列堆积,可能触发客户端重传最终ACK。
  2. listen() 的 backlog 参数
    该参数控制 已完成队列的最大长度(注意:不同操作系统实现可能不同)。如果队列满,内核可能拒绝新连接(如Linux默认策略)。
  3. 非阻塞模式下的行为
    若套接字设为非阻塞且队列为空,accept() 会立即返回 EAGAINEWOULDBLOCK 错误,但不会影响已建立的连接。

总结

  • 三次握手由内核在 listen() 阶段自动完成,与用户态代码无关。
  • accept() 仅负责“收割”已建立的连接,属于应用层获取连接的接口。

你可能感兴趣的:(tcp/ip,网络协议,网络)