POSIX 连接到自身的套接字客户端

POSIX socket client connecting to itself

我的 TCP 客户端套接字代码出现了非常奇怪的行为,因此我编写了一个简单的测试程序。

下面的代码旨在不断重试连接到服务器 (127.0.0.1:36000),直到它连接为止。它正在用 g++ 4.8.5-std=c++98 编译(不能使用 C++03/11)。

#include <arpa/inet.h>
#include <cctype>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main()
{
    int sd = -1;

    while (true)
    {
        close(sd);

        std::cout << "trying to connect" << std::endl;

        if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            std::cerr << strerror(errno) << std::endl;
            continue;
        }

        std::cout << "socket created " << sd << std::endl;

        struct sockaddr_in addr;
        memset((char *)&addr, 0, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;

        if (!inet_aton("127.0.0.1", &addr.sin_addr))
        {
            std::cerr << strerror(errno) << std::endl;
            continue;
        }
        std::cout << "translated ip" << std::endl;

        addr.sin_port = htons(36000);

        if (connect(sd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
        {
            std::cerr << "Error connecting socket" << std::endl;
            continue;
        }

        std::cout << "connected " << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port) << std::endl;
        std::cout << "write " << write(sd, "from client", 11) << std::endl;

        char buf[1024] = {0};
        int size;
        while ((size = read(sd, buf, 1024, 0, NULL, NULL)) > -1)
        {
            buf[size] = 0;
            std::cout << "received " << size << " ["  << buf << "]" << std::endl;
        }

        std::cout << "stopped" << std::endl;
    }

    return 0;
}

尽管没有服务器或任何其他程序使用端口 36000,但 connect 调用会成功,write 也会成功。我得到的输出看起来像:

trying to connect
socket created 3
translated ip
Error connecting socket
trying to connect
socket created 3
translated ip
Error connecting socket
...
...
...
trying to connect
socket created 3
translated ip
connected 127.0.0.1:36000
write 11
received 11 [from client]

如果将端口更改为任何其他未使用的端口(35999、36001 等),connect 只会失败(连接被拒绝)。其他端口似乎永远无法连接。

参数 recvfrom 如果存在则不会被修改。

此外,只有当套接字没有超时时才会发生。如果套接字仍然超时,connect 将失败。

运行 netstat -anread 被阻止时 return

tcp        0      0 127.0.0.1:36000         127.0.0.1:36000         ESTABLISHED

这发生在 CentOS 6.7 和 7 虚拟机上。

这是怎么回事? 36000 端口有什么奇怪的地方吗?这是 Centos/connect 的错误吗?我在做什么傻事吗?

这是 How can you have a TCP connection back to the same port? 的部分重复,但这仍然不能解释为什么它只发生在端口 36000 上。

当我用 tcpdump 执行你的程序时 运行 我得到了一些有趣的输出。

sudo tcpdump -i lo

...
14:51:16.477170 IP localhost.35988 > localhost.36000: Flags [S], seq 3826079620, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477173 IP localhost.36000 > localhost.35988: Flags [R.], seq 0, ack 3826079621, win 0, length 0
14:51:16.477203 IP localhost.35990 > localhost.36000: Flags [S], seq 2431563950, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477206 IP localhost.36000 > localhost.35990: Flags [R.], seq 0, ack 2431563951, win 0, length 0
14:51:16.477247 IP localhost.35992 > localhost.36000: Flags [S], seq 3688613148, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477250 IP localhost.36000 > localhost.35992: Flags [R.], seq 0, ack 3688613149, win 0, length 0
14:51:16.477282 IP localhost.35994 > localhost.36000: Flags [S], seq 1503921089, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477285 IP localhost.36000 > localhost.35994: Flags [R.], seq 0, ack 1503921090, win 0, length 0
14:51:16.477315 IP localhost.35996 > localhost.36000: Flags [S], seq 2868111150, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477318 IP localhost.36000 > localhost.35996: Flags [R.], seq 0, ack 2868111151, win 0, length 0
14:51:16.477348 IP localhost.35998 > localhost.36000: Flags [S], seq 281293569, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477351 IP localhost.36000 > localhost.35998: Flags [R.], seq 0, ack 281293570, win 0, length 0
14:51:16.477381 IP localhost.36000 > localhost.36000: Flags [S], seq 3196081163, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 0,nop,wscale 7], length 0
14:51:16.477386 IP localhost.36000 > localhost.36000: Flags [S.], seq 3196081163, ack 3196081164, win 65495, options [mss 65495,sackOK,TS val 95720198 ecr 95720198,nop,wscale 7], length 0
14:51:16.477422 IP localhost.36000 > localhost.36000: Flags [.], ack 1, win 512, options [nop,nop,TS val 95720198 ecr 95720198], length 0

此输出表明套接字实际上正在与自身执行 TCP 3 次握手。出于某种原因(至少在 CentOS 6.7 中)它似乎将源端口增加 2 从而防止端口 35999、36001 上的问题。

RFC 793中将这种连接称为同时连接。