listen(), connect(), accept() 三者的关系

listen(), connect(), accept() 三者的关系

socket 编程流程

listen(), connect(), accept() 三者的关系_第1张图片

listen()函数

#include           
#include 

int listen(int sockfd, int backlog);

在网络通信中, 客户端通常处于主动的一方, 而服务器则是被动的一方, 服务器是被连接的, 所以他要时刻准备着被连接, 所以就需要调用 listen() 来监听, 等着被连接.

listen() 函数的主要作用就是将 socket() 函数得到的 sockfd 变成一个被动监听的套接字, 用来被动等待客户端的连接, 而参数 backlog 的作用就是设置连接队列的长度

三次握手,建立连接不是 listen() 函数完成的, 而是内核完成的, listen() 函数只是将 sockfd 和 backlog 告诉内核, 然后就返回了

之后, 如果有客户端通过 connect() 发起连接请求, 内核就会通过三次握手建立连接, 然后将建立好的连接放到一个队列中, 这个队列称为: 已完成连接队列

connect()函数

#include 
#include 

int connect(int sockfd,
            const struct sockaddr *addr,
            socklen_t addrlen);

通常客户端通过 connect() 函数来向服务端主动发起连接, 但是建立连接也不是这个函数完成的, 而是由内核完成的, 这个函数仅仅是通知内核通过三次握手建立连接, 然后将结果返回给这个函数.

这个函数默认会一直阻塞, 直到内核连接建立成功或者超时失败才返回(但一般这个过程很快)

所以说, 服务器端通过 listen() 函数来通知内核建立连接, 客户端通过 connect() 函数来通知内核建立连接

因此, 在 listen() 之后, 连接就已经建立好了, 建立好的连接存储在已完成连接队列

这里还需要再分析一下 listen() 函数的第二个参数 backlog, 实际上, 内核为每一个监听套接字维护两个队列

  • 未完成连接队列

    其中存储着尚未建立连接的套接字

  • 已完成连接队列

    存储着已经完成连接的套接字

一般认为 backlog 参数的大小是这两个队列大小之和, 收到客户端的连接请求之后, 内核创建一个套接字存储在未完成连接队列中, 来进行三次握手建立连接

连接建立完成以后, 这个套接字就加到已完成连接队列的队尾, 服务器从已完成连接队列中取走一个, 又空出一个位置, 然后已经完成连接的套接字由补充进来, 就这样实现动态平衡

所以说, 如果在 listen 之后不进行 accept , connect 也是会成功返回的, 其实此时连接就已经建立好了

可以验证一下

服务器端

#include 
#include 

#include 
#include 
#include 
#include 

const int port = 9999;

int main( )
{
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == -1)
    {
        perror("sock");
        return -1;
    }
    int ret = bind(sock, (struct sockaddr *)&server, 
                   sizeof(struct sockaddr_in));
    if(ret == -1)
    {
        perror("bind");
        close(sock);
        return -1;
    }
    ret = listen(sock, 10);
    if(ret == -1)
    {
        perror("listen");
        close(sock);
        return -1;
    }
    sleep(10);

    system("netstat -anp | grep 9999");

    while(1);

    return 0;
}

客户端

#include 
#include 

#include 
#include 
#include 
#include 
#include 

const int port = 9999;
const char* server_ip = "192.168.2.128";

int main()
{
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(server_ip);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == -1)
    {
        perror("socket");
        return -1;
    }

    int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        close(sock);
        return -1;
    }

    system("netstat -anp | grep 9999");

    while(1);

    return 0;
}

先运行服务器, 再运行客户端, 结果如下

listen(), connect(), accept() 三者的关系_第2张图片

listen(), connect(), accept() 三者的关系_第3张图片

此时已经处于 Establish 状态, 连接已经建立好了

accept()函数

#include           
#include 

int accept(int sockfd, 
           struct sockaddr *addr, 
           socklen_t *addrlen);

所以, accept() 函数的作用就是在已完成连接队列中取出一个已经建立好的连接

如果这个队列中已经没有已完成连接的套接字, 那么 accept() 就会一直阻塞, 直到取得一个已经建立连接的套接字

如果服务器来不及调用 accept() 取走队列中已经建立好的连接, 导致队列中的连接满了, 会怎么样呢 ?

这取决于内核的具体实现, 在Linux中会延时建立连接.

你可能感兴趣的:(网络,Linux)