accept接受连接

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了accept接受连接相关的知识,希望对你有一定的参考价值。

下面的系统调用从listen监听队列中接受一个连接:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd参数是执行过listen系统调用的socket
addr参数是用来获取被接受连接的远端socket地址
addrlen参数是socket地址的长度
acceept成功时返回一个新的连接socket,该socket唯一的标识了被接受的这个连接,服务器可通过读写该socket来与被接受的对应客户端进行通信。accept失败时返回-1,并设置errno

现在考虑如下情况:
如果监听队列中处于ESTABLISHED状态的连接对应的客户端出现了网络异常(比如掉线),那么服务器对这个连接执行的accept是否成功?

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>

#define BACKLOG     5
#define SLEEPTIME   20

int main(int argc, char* argv[]) {

    if (argc <= 2) {
        printf("usage: %s ip_address port_number\\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);   // char* -> int num
    // int backlog = atoi(argv[3]);

    struct sockaddr_in address;
    bzero(&address, sizeof(struct sockaddr_in));
    address.sin_family = AF_INET;
    inet_aton(ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr*)&address, sizeof(struct sockaddr));
    assert(ret != -1);

    ret = listen(sock, BACKLOG);
    assert(ret != -1);

    // 暂停20秒以等待客户端连接和相关操作(掉线或退出)完成
    sleep(SLEEPTIME);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);

    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d \\n", errno);
    } else {
        // 接受连接成功则打印出客户端的IP地址和端口号
        char remote[INET_ADDRSTRLEN];
        printf("connected with ip: %s and port: %d\\n", inet_ntop(AF_INET,
            &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
        close(connfd);
    }

    close(sock);
    return 0;
}

服务器执行:监听端口12345

./testaccept 192.168.136.122 12345

客户端执行:

telnet 192.168.136.122 12345

在这里插入图片描述
连接成功后,立即断开网络连接

接着,在服务器上运行netstat -nt | grep 12345查看socket的状态:
在这里插入图片描述

之后服务器又正常输出accept成功的语句,表明accept调用可以正常返回
在这里插入图片描述
接着,又在服务器上运行netstat -nt | grep 12345

在这里插入图片描述

netstat命令输出表明accept调用对于客户端网络断开喊不知情。accept只是从监听队列中取出连接,不论连接处于何种状态(如上的ESTABLISHED状态和FIN_WAIT1),更不关心任何网络状况的变化

【注】
客户端正常关闭时socket状态为TIME_WAIT
在这里插入图片描述
此时12345端口仍然被占用,如果执行./testaccept 192.168.136.122 12345肯定会出错
在这里插入图片描述

TIME_WAIT持续时间

主动关闭方响应完最后一次ACK之后,会在TIME_WAIT这个状态维持2MSL。

为什么需要TIME_WAIT

可靠地实现了TCP全双工连接的终止
我们知道,TCP是比较可靠的。当TCP向另一端发送数据时,他要求对端返回一个确认(如同我们关闭时候的FIN和ACK)。如果没有收到确认,则会重发。

回忆一下我们最终的那个FIN与ACK,被动关闭方发送FIN,并等待主动关闭方返回的ACK。我们假设最终的ACK丢失,被动关闭方将需要重新发送它的最终那个FIN,主动关闭方必须维护状态信息(TIME_WAIT),以允许它重发最终的那个ACK。

如果没有了这个状态,当他第二次收到FIN时,会响应一个RST(也是一种类型的TCP分节),会被服务器解释成一个错误。

为了TCP打算执行必要的工作以彻底终止某个连接两个方向上的数据流(即全双工关闭),那么他必须要正确处理连接终止四个分节中任何一个分节丢失的情况。

允许老的重复分节在网络中的消逝(为什么需要2MSL)

首先,存在这样的情况,某个路由器崩溃或者两个路由器之间的某个链接断开时,路由协议需要花费数秒到数分钟的时间才能稳定找出另一条通路。在这段时间内,可能发生路由循环(路由器A把分组发送给B,B又发送回给A),这种情况我们称之为迷途。假设迷途的分组是一个TCP分节,在迷途期间,发送端TCP超时并重传该分组,重传分组通过某路径到达目的地,而后不久(最多MSL秒)路由循环修复,早先迷失在这个循环中的分组最终也被送到目的地。这种分组被称之为重复分组或者漫游的重复分组,TCP必须要正确处理这些重复的分组。

我们假设ip1:port1和ip2:port2 之间有一个TCP连接。我们关闭了这个链接,过一段时间后在相同IP和端口之间建立了另一个连接。TCP必须防止来自之前那个连接的老的重复分组在新连接上出现。为了做到这一点,TCP将不复用处于TIME_WAIT状态的连接。2MSL的时间足以让某个方向上的分组存活MSL秒后被丢弃,另一个方向上的应答也最多存活MSL秒后被丢弃。

以上是关于accept接受连接的主要内容,如果未能解决你的问题,请参考以下文章

套接字 API accept() 函数如何工作?

线程中的套接字接受所有端口连接? C++

boost::asio::acceptor - 在旧连接仍然打开时接受新的传入连接

Accepting connections是啥意思

TCP 套接字 - 服务器不接受任何连接 UNIX

如何创建将侦听特定端口并接受 TCP 连接(非 https)的 servlet