【问题标题】:Can I write() to a socket just after connect() call, but before TCP connection established?我可以在 connect() 调用之后但在 TCP 连接建立之前将 write() 写入套接字吗?
【发布时间】:2017-02-25 19:36:26
【问题描述】:

我的实验表明,我可以在connect() 调用之后立即写入非阻塞套接字,尚未建立 TCP 连接,并且在连接发生后(异步)对等方正确接收写入的数据。这在 Linux / FreeBSD 上是否有保证?我的意思是,当连接仍在进行中时,write() 会返回 > 0 吗?或者我很幸运,在connect()write()调用之间成功建立了TCP连接?

实验代码:

int fd = socket (PF_INET, SOCK_STREAM, 0);
fcntl(fd, F_SETFL, O_NONBLOCK)

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(_ip_port.port);
addr.sin_addr.s_addr = htonl(_ip_port.ipv4);

int res = connect(fd, (struct sockaddr*)&addr, sizeof(addr));

// HERE: res == -1, errno == 115 (EINPROGRESS)

int r = ::write(fd, "TEST", 4);

// HERE: r == 4

附: 我在单线程中处理多个侦听和连接套接字(传入和传出连接)并通过 epoll 管理它们。通常,当我想创建一个新的传出连接时,我调用非阻塞connect() 并等待EPOLLOUT(epoll 事件)然后write() 我的数据。但我注意到我可以在EPOLLOUT 之前开始写作并获得适当的结果。我可以相信这种方法还是应该使用我的旧方法?

P.P.S. 我用延迟为 170 毫秒的远程主机重复了我的实验,得到了不同的结果:write()(就在connect() 之后)返回-1 和errno == EAGAIN。所以,是的,我的第一个实验是不公平的(连接到快速 localhost),但我仍然认为可以使用“connect()”旁边的“write()”:如果write() 返回 -1 和 EAGAIN,我等待EPOLLOUT 并重试写入。但我同意,这是肮脏无用的做法。

【问题讨论】:

  • IIRC 用于 nin 阻塞套接字,connect() 的行为不同。您应该等待(通过选择或轮询)套接字变为可写
  • 显然调用之间的连接已完成。你真是幸运。不要写这样的代码。除非您有多个 aocket,否则在非阻塞模式下进行连接确实没有意义。以阻塞模式连接,然后切换。
  • NB 您使用poll() 的方法没有任何“过时”之处。这只是正确的。正确性不是时间的函数。
  • 重新编辑,您现在提到的方法与您询问的方法不同。

标签: sockets tcp


【解决方案1】:

我可以在 connect() 调用之后但在 TCP 连接建立之前将 write() 写入套接字吗?

当然,你可以。它很可能会失败。

根据POSIX specification of write()

[ECONNRESET]

尝试在未连接的套接字上写入。

根据Linux man page for write()

EDESTADDRREQ

fd 指的是对等地址具有的数据报套接字 未使用connect(2) 设置。

如果 TCP 连接尚未完成,您的 write() 调用将失败。

【讨论】:

  • @pavelkolodin 你似乎只是在狡辩。什么意思很清楚,现在应该更清楚了。
  • 我觉得这个答案不能令人满意,因为在这种情况下根本不清楚“未连接的套接字”是什么意思。也许这意味着没有完成对 connect() 的调用。我认为 ECONNRESET 是连接“由对等方重置”时出现的错误;也就是说,它是从您尝试写入的主机接收到 icmp 数据包的结果。我可以想象,如果连接已经在进行中,那么这将永远不会发生(由于 TCP 的工作方式)。
【解决方案2】:

至少在 Linux 上,套接字在从对等方收到 [SYN, ACK] 之前被标记为不可写。这意味着在收到 [SYN, ACK] 之前,系统不会通过网络发送任何应用程序数据。

如果套接字处于非阻塞模式,则必须使用 select/poll/epoll 等待它变为可写(否则 write 调用将失败并返回 EAGAIN 并且不会有数据入队)。当套接字变为可写时,内核通常在应用程序有时间写入第一个数据之前已经向对端发送了一个空的 [ACK] 消息,这会由于 API 设计而导致一些不必要的开销。

在非阻塞套接字上调用connect 并获得EINPROGRESS 后,似乎正在工作,将套接字设置为阻塞,然后开始写入数据。然后内核将在内部首先等待,直到从对等方接收到[SYN,ACK],然后在单个数据包中发送应用程序数据和初始ACK,这将避免那个空的[ACK]。请注意,write 调用将阻塞,直到收到 [SYN, ACK] 并将例如如果连接失败,则返回 -1 和 errno ECONNREFUSEDETIMEDOUT 等。然而,这种方法在 WSL 1(Linux 的 Windows 子系统)中不起作用,它会立即失败 EPIPE(虽然没有 SIGPIPE)。

在任何情况下,由于 TCP 的设计,没有什么办法可以消除这种初始往返时间。但是,如果两个端点都支持 TCP 快速打开 (TFO) 功能,并且可以接受其安全问题,则可以消除此往返。请参阅https://lwn.net/Articles/508865/ 了解更多信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2018-02-15
    • 2015-01-08
    • 2013-12-12
    • 2017-08-13
    • 2019-05-27
    相关资源
    最近更新 更多