服务器端或客户端的Java NIO Socket 在什么时候套接字连接准备好发送数据?

At what point during Java NIO Socket on the server or client end is the socket connection ready to send data?

我正在使用 Java NIO,这是我第一次建立有效的 TCP 连接(到目前为止我只完成了 UDP 和很久以前的一个简单的 TCP 测试程序) .

现在,我不确定我可以可靠地开始向客户端发送数据的确切时间,以便他们知道另一端有一个活动连接(假设某些东西没有消失错).


假设一切都是非阻塞的。

客户:

1) 打开一个没有绑定的新套接字通道,因此可以将其设置为异步

s = SocketChannel.open()

2) 设置为非阻塞

s.configureBlocking(false)

3) 尝试连接到正在侦听的服务器

s.connect(someAddr)

服务器:

4) 服务器通过一些 serverSocketChannel.accept() 获得传入连接,我们假设它是非阻塞的,并且在这种情况下会假装它 return 是一个有效的套接字对象(而不是 null)。


客户:

5) 客户现在打电话给finishConnect()吗?


我不确定我这样做是否正确,而且我也不确定在什么时候服务器发送或客户端发送是安全的...是在 (4 ) 用于服务器,(5) 用于客户端?

我是否需要让客户端在连接到服务器后发送心跳数据包,以便我的应用程序知道可以开始发送数据?我不知道服务器如何知道它已完全连接,因为我看不到 服务器 会知道最终确认何时完成......除了客户端知道建立连接并发送某种 'first packet' 数据。

服务器知道的唯一方法是我是否能以某种方式弄清楚它何时收到 ACK 数据包,但我目前看不到 Java 让我知道的方法。

注意:我可能缺少知识,我可能说了一些不正确的东西,如果你能指出他们错误的地方,我会很高兴更新我的post所以这是事实。

指导我 knowledge/creation 这个 post 的链接:

TCP protocol

SocketChannel Javadocs

警告:我使用套接字而不是套接字通道完成套接字编程。本答案基于阅读SocketChannel javadoc,知道socketchannels在幕后使用套接字并熟悉套接字。

根据 javadoc,如果 connect() returns 为真,则连接已建立,您无需调用 finishConnect()。将 finishConnect() 视为一种检查连接建立状态的方法,而不是一个执行建立连接所需的任何操作的函数。

在服务器上,如果 accept() returns 一个 SocketChannel,那么已经从客户端接收到连接尝试。来自服务器的 SYN ACK 很可能已经发送,尽管 Java 文档有意不指定这一点,以便允许未来的优化。 Java 认为连接很可能是 "established",尽管您可以调用 finishConnect() 来确定;有可能 Java 等到服务器收到客户端的 ACK 才认为连接已完全建立。请注意,Java文档指定返回的 SocketChannel 最初将处于阻塞模式,即使 ServerSocketChannel 处于非阻塞模式。

在客户端,只要 connect() 或 finishConnect() returns 为真,您就可以开始发送数据。在服务器端,当然可以在finishConnect() returns true 时开始发送数据;可能是对 finishConnect() 的调用是多余的,而 accept() returns 具有已建立连接的 SocketChannel - 这就是我编写它的方式 - 但我没有充分使用 SocketChannels 来确定。您不需要在连接建立期间发送心跳数据包,这样做可能会浪费精力,因为 TCP 协议本身会处理所有与连接相关的事情,包括三向握手(SYN、SYN-ACK、ACK)到建立连接。

您需要使用选择器来等待 CONNECT 和 ACCEPT 事件。

NIO 并不难。但是 Java 的 NIO API 比必要的要难一些。好好折磨自己吧:)


Java API 调用和 TCP 握手步骤之间的确切映射不是很清楚。但是,我会在下面的模型中考虑它(如果它不完全是现实中发生的事情也没关系)

 connect() 
                     -- SYN -->
                    <-- ACK --
                    <-- SYN --  
 CONNECT event
 finishConnect()        
                     -- ACK -->
                                   ACCEPT event
                                   accept()

序列:

  • 客户端应用程序调用 connect(serverAddress) - 客户端向服务器发送 SYN

如果connect() return为真,则可以立即使用套接字通道。但是,我从未在实践中观察到这种情况,甚至在本地环回连接上也没有。

  • 服务器收到客户端SYN;响应 ACK/SYN

  • 客户端接收服务器 ACK/SYN。

  • 向客户端应用程序引发 CONNECT 事件。

  • cilent 应用然后调用 finishConnect() - 客户端向服务器发送 ACK

这里,finishConnect()不会return假,因为之前的CONNECT事件。不过它可能会抛出异常。

现在,客户端认为 TCP 握手完成。客户端套接字通道可以立即使用。

  • 服务器收到客户端ACK。服务器认为 TCP 握手完成。连接被放在积压中。
  • 向服务器应用程序发起了一个 ACCEPT 事件。

  • 服务器应用程序然后调用 accept()。 returned 套接字通道可以立即使用。

Do we need to bind here? Or is that just for if we want to refer to a local port?

您不需要绑定客户端SocketChannel

s.connect(someAddr)

If this returns true, the javadocs say the connection is established. Does that mean I don't need to call finishConnect()?

正确。

From what I read, this is for local connections

你从哪里读到的?这不是真的。

but it does not specify if remote connections could possibly return true immediately or not.

任何时候都可以 return 为真。你必须检查。而且跟本地和远程都没有关系。

Is this where the client sends a SYN to the server?

是的。

Server gets an incoming connection through some serverSocketChannel.accept(), which we assume is non blocking and will pretend in this case it returns a valid socket object (and not null). Does this mean that as soon as the server gets the connection, it accepted the connection (assuming all went well) and sends back a SYN-ACK?

没有。 TCP/IP 堆栈已发送 SYN-ACK。它不依赖于应用程序代码何时调用 accept().

Does the client call finishConnect() now?

同样,这不取决于服务器应用程序调用 accept() 的时间。 TCP 握手由服务器内核完成。如果 connect() 不是 return true 并且随后的 select() 表示通道现在可以连接,则客户端应该调用 finishConnnect()

请注意,finishConnect() 可以 return 为真,在这种情况下您可以继续,或者为假,在这种情况下您只需继续等待 OP_CONNECT: 或者它可以抛出异常,在这种情况下失败,您必须关闭频道。

When does the client know when to keep calling finishConnect()? Do I just loop for X seconds immediately after calling s.connect(...) from step (3)?

见上文。但是,如果您准备好循环,为什么要以非阻塞模式进行连接呢?以阻塞模式执行,成功后进入非阻塞模式。简单多了。

Is this when it sends the ACK?

没有。这都是由内核异步完成的

Am I supposed to loop for X seconds until it returns true... and kill the 'half formed connection' if it doesn't respond within X seconds due to something going wrong?

不,见上文。

Does s.isConnected() return true for the connect() in step (3) succeeding, or finishConnect() succeeding?

两者:它们是互斥的。

I'm not sure if I'm doing this properly, and I'm also not sure at what point is it safe for the server to send, or the client to send... is it at (4) for the server, and (5) for the client?

服务器一旦有接受的套接字就可以发送。只要 connect()finishConnect() return 为真,客户端就可以发送。

Do I need to have the client send a heartbeat packet after the connection is done to the server so my application knows it's okay to start sending data?

没有

I don't know how the server would know it's fully connected since I can't see any way the server would know when the final acknowledgement is done... except for the client knowing the connection is established and it sends some kind of 'first packet' data.

见上文。发个包看能不能发个包的想法没有意义

The only way the server would know is if I can somehow figure out when it gets the ACK packet

服务器在accept()return秒时已经有ACK包。

NOTE: I may be missing knowledge ...

您忽略的是服务器上存在监听积压队列。