I'm working with Java NIO and this is my first time setting up a working TCP connection (I've only done UDP up until this point and a simple TCP test program a long time ago).
Right now, I'm unsure the exact moment where I can reliably start sending data to the clients such that they know there is an active connection on the other end (assuming something hasn't gone wrong).
Assume everything is non-blocking.
CLIENT:
1) Open a new socket channel with no bindings so it can be set to async
s = SocketChannel.open()
2) Set it to be non-blocking
s.configureBlocking(false)
- Do we need to bind here? Or is that just for if we want to refer to a local port?
3) Attempt to connect to a server that is listening
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.
Is this where the client sends a SYN to the server?
SERVER:
4) 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?
CLIENT:
5) Does the client call finishConnect()
now?
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?
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, but I can't see a way of Java letting me know currently.
NOTE: I may be missing knowledge, I may have said some incorrect stuff, if you can point out where they are wrong I'd be more than glad to update my post so it's factually correct.
Links that guided my knowledge/creation of this post:
TCP protocol
SocketChannel Javadocs
Do we need to bind here? Or is that just for if we want to refer to a local port?
You don't need to bind the client 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()?
Correct.
From what I read, this is for local connections, but it does not specify if remote connections could possibly return true immediately or not.
It can return true any time, you have to check.
Is this where the client sends a SYN to the server?
Yes.
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?
No. The SYN-ACK has already been sent by the TCP/IP stack. It doesn't depend on when the application code calls accept().
Does the client call finishConnect() now?
Again this doesn't depend on when the server application called accept. The TCP handshake is completed by the server kernel. The client should call finishConnnect() if connect() didn't return true and a subsequent select() indicates that the channel is now connectable.
Note that finishConnect() can return true, in which case you just proceed, or false, in which case you just keep waiting for OP_CONNECT, or throw an exception, in which case it failed and you should close the channel.
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)?
See above.
Is this when it sends the ACK?
No. That is all done by the kernel asynchronosusly
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?
No, see above.
Does s.isConnected() return true for the connect() in step (3) succeeding, or finishConnect() succeeding?
Both: they're mutually exclusive.
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?
The server can send as soon as it has an accepted socket. The client can send as soon as either connect() or finishConnect() returns true.
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?
No.
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.
See above. The idea of sending a packet to see if you can send a packet doesn't make sense.
The only way the server would know is if I can somehow figure out when it gets the ACK packet
The server already has the ACK packet when accept() returns.
NOTE: I may be missing knowledge ...
What you're overlooking is the existence of the listen backlog queue at the server.
Caveat: I've done my socket programming using sockets, and not socketchannels. This answer is based on reading the SocketChannel javadoc, knowing that socketchannels use sockets behind the scenes and being familiar with sockets.
According to the javadoc, if connect() returns true, the connection is established, and you don't need to call finishConnect(). Think of finishConnect() as a way of checking on the status of connection establishment, rather than as a function that does anything required to establish a connection.
On the server, if accept() returns a SocketChannel, then the connection attempt has been received from the client. The SYN ACK from the server has most likely been sent, although the Java documentation purposely does not specify this, in order to permit future optimizations. Most likely the connection is considered by Java to be "established", though you could call finishConnect() to make sure; it's possible Java waits until the server gets the client's ACK before it considers the connection fully established. Note that the Javadoc specifies that the returned SocketChannel will initially be in blocking mode, even if the ServerSocketChannel was in nonblocking mode.
On the client side, you can start sending data as soon as either connect() or finishConnect() returns true. On the server side, you can certainly start sending data when finishConnect() returns true; it may be that the call to finishConnect() is superfluous and accept() returns a SocketChannel with an established connection - that's how I would have written it - but I haven't used SocketChannels enough to be sure. You do not need to send a heartbeat packet during connection establishment and doing so would probably be a waste of effort, as the TCP protocol itself handles all the connection related stuff, including the three way handshake (SYN, SYN-ACK, ACK) to establish the connection.
You need to use a Selector to wait on CONNECT and ACCEPT events.
NIO is not that hard. But Java's NIO API is a little harder than necessary. Have fun torturing yourself:)
The exact mapping between Java API calls and TCP handshake steps is not very clear. However, I would think of it in the following model (it doesn't matter much if it is not exactly what's happening in reality)
connect()
-- SYN -->
<-- ACK --
<-- SYN --
CONNECT event
finishConnect()
-- ACK -->
ACCEPT event
accept()
Sequences:
- client app calls
connect(serverAddress)
- client sends SYN to server
if connect()
returns true, the socket channel can be used immeidately. however, I've never observed this case in practice, not even on local loopback connections.
server receives client SYN; responds with ACK/SYN
client receives server ACK/SYN.
a CONNECT event is raised to client app.
cilent app then calls finishConnect()
- client sends ACK to server
Here, finishConnect()
will not return false, because of the previous CONNECT event. It could throw exception though.
Now, client considers TCP handshake complete. The client socket channel can be used immediately.