If I obtain a SocketChannel that is set to the non-blocking mode, what happens when I write to the channel and the underlying socket buffer is full because the other side cannot keep up? Would the data be discarded or something to that effect?
The write() method returns zero and the data stays in the ByteBuffer. At that point you should register the channel for OP_WRITE, remember the output buffer, return to the select loop. When the channel becomes writable, retry the write, and this time as long as it completes, i.e. doesn't return zero or less than the remaining bytes in the buffer to be written, deregister OP_WRITE.
Related
Is it necessary to register interest to write to a NIO client socket channel to send data? Do I have to always call socketChannel.register(selector, SelectionKey.OP_WRITE), or some equivalent, before writing to the client SocketChannel to be able to write there?
Would not be enough simply to write data to client SocketChannel with channel.write(outputBuffer) and awake potentially blocked Selector, all in a client thread? The main selector loop would then look like this:
Selector selector = SelectorProvider.provider().openSelector();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
while (selector.select() > 0) {
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = readyKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = (SelectionKey)keyIterator.next();
keyIterator.remove();
while (keyIterator.hasNext()) {
...
if (key.isAcceptable()) {
...
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
// client socket channel would be permanently in the read mode
...
} else if (key.isReadable()) {
...
} else if (key.isWritable()) {
// the key should know here that the underlying channel
// has something to be send to the wire, so it should get
// here if there are still data to be sent
socketChannel.write(outputBuffer)
}
It would only get to the if (key.isWritable()) branch when there is something remaining to be send, the remainder of data from the initial channel.write(outputBuffer) call; like when the message is too long and needs to be send out into chunks and I want no blocking. The loop would spin until outputBuffer.hasRemaining() is finally false.
I am even thinking, does have to be writing to Channel, that is sending data out, done through Selector at all? Leave only incoming traffic to be handled with Selector, as only incoming traffic need wait states?
UPDATE
From further reading of valuable user207421 posts, which I partly inspired me to post this question, NIO Javadoc, in the push to join the dots, I summarized this:
SocketChannel and SelectionKey are thread safe.
Reading and writing from/to channel socket outside the selector loop is indeed possible; it's independent of it. Reading outside does not make much sense, writing to socket definitively does and it's in fact how most of writing is done.
Writing of data is typically initiated outside the selector loop, in a different thread. write(buffer) will not necessary send all data - if the message is too huge for the socket buffer, remote client is very slow or even went unresponsive. In that case write() has to be called repeatedly. Buffer updates the current position with every call, depending how much was sent, which can be anything between 0 and buffer.remaining().
If write() returns 0 then, and user207421 cannot stress this more, it's the case, possibly the only one, when OP_WRITE needs to be registered and writing is then handled within selector loop.
When all data are written the OP_READ must be immediately restored
Having OP_WRITE set without any data to write would cause selector loop to endlessly spin; waste of CPU time.
It is unclear what to do if write() returns >0 but still not all remaining message data were sent, that is buffer.remaining() > 0. Should I also set OP_WRITE and pass it to selector loop, or loop write(buffer) inside writing thread until there is no remainder or 0 is returned?
Can actually such situation happen? Official SocketChannel Javadoc says initially Unless otherwise specified, a write operation will return only after writing all of the r requested bytes ..meaning, no, it cannot happen! But then they add: Some types of channels, depending upon their state, may write only some of the bytes ..so does it mean write() can return before sending all? Or does that mean that returns '0' if it sent, say, just half of the buffer?
In the write returned 0 scenario, when writing is passed to the selector loop, ie different thread; is the writing thread blocked to write a completely new message, ie new buffer, to the same socket channel? Or I have to ensure it does not happen by other means? The Javadoc says: This method may be invoked at any time. If another thread has already initiated a write operation upon this channel, however, then an invocation of this method will block until the first operation is complete. Does that cover my scenario?
Official Javadoc does not mention any special meaning for 0 as a return value for write() method. It just says: The number of bytes written, possibly zero. Hmm.
I'm also not sure how unresponsive or slow client is handled during the write. I'm guessing that returning 0 from write() and passing it to selector loop is exactly the reason it's done like that. Selector then can, and I'm only guessing here, manage writing in an optimal way: speeding it, slowing it, increasing intervals, sending smaller chunks, whatever; whatever the underlying TCP/IP stack reports back.
Classic NIO client:
Create a selector
Register channel to selector for READ WRITE
Loop:
select
iterate selectedKeys
do work for Readable channel and Writeable channel
I wrote a simple UDP NIO client for android like above, but found 30k+ HashMapNode memory allocations every 10 seconds. Since the channel was concerned both READ and WRITE, the select() call returned immediately because of it was writeable, during every select(), at least one SelectionKey was added to HashMap returned by SelectedKeys().
I changed the design to only register for READ at the beginning, and call select() with a small timeout(say 10ms), and if the buffer to write was not empty, register for WRITE, do the writes, and register for READ again, memory allocation problem fixed, but write operation will be delayed because you have to wait for the READ select timeout.
Any better approach?
Ok, I think I got it. Here it goes.
Main loop:
Open channel and configureBlocking(false)
Open selector
Register channel to selector, only concern about OP_READ
LOOP:
selector.select() // No timeout
if write buffer is empty or no channel been selected, continue
LOOP for selectionKey in selectedKeys:
if selectionKey is readable
do read operation
else if selectionKey is writeable
do write operation
register channel to OP_READ
remove selectionKey from selectedKeys
Write:
write data to write buffer
register channel to OP_WRITE
selector.wakeup()
In Java, one can write a couple of bytes (or however many bytes that is less than the buffer's size) to the socket's output stream and then flush it, in case some bytes need to be sent immediately. In Win32 API, there doesn't seem to be any sort of flush function, so I feel like "flushing" in this case is just padding the rest of the buffer with dummy bytes (probably zeroes) to force the send function to, well, send the data. My question is, how does Java (or even C#, perhaps) implement such a thing under the hood? That is, if I had a Java socket communicating with a Win32 socket somewhere, how would they communicate given that there will be some flushing needed? For example, if the Java socket's buffer were to be flushed (calling flush on the socket's OutputStream), what would I get on the Win32 side (by calling recv)? And the reverse? Would I see some padding behaviour? Would the Win32 side need to know the Java side's buffer size? Something unrelated?
Flushing a Java socket output stream does nothing. Flushing a BufferedOutputStream does something, but only to the application-side buffer. In neither case is there a system call involved other than the send() or write() implied by the flush. You're looking for something that doesn't exist.
so I feel like "flushing" in this case is just padding the rest of the buffer with dummy bytes (probably zeroes) ...
No.
First recall that TCP provides a bidirectional stream of bytes. On one side you write bytes to a socket and the other side can read them from the socket. There is no guarantee whether writing 4 bytes will result in one read call that fetches 4 bytes on the other end. It could also be 2 times 2 bytes. It's no packets or buffers, and therefore "padding the rest of the buffer with dummy bytes" seems like a bad idea, since the other side will eventually receive those dummy bytes (and interpret them for whatever).
Next on to the question:
On the base of all the application there are the OS socket APIs, which provide the write/send calls for the socket. When you write to the OS socket the bytes that are writing are basically only written into the socket send buffer of the OS, from which they will be sent to the remote side at some point of time. This point of time depends on how filled the state of the send buffer is and how things look on the network (TCP windows, network congestion, etc.). However you normally don't have to care about it, the OS will simply send the data eventually and there is no need to flush something. There is one setting on the OS which can be used to influence the sending behavior, which is the nagle algorithm (or TCP_NODELAY) setting. If nagle algorithm is disabled (NODELAY = true) this means the OS will try to send the data immediatly after the write call and doesn't wait for more data from the application in order to send less IP packets. You can use this to reduce latency in case of small packets, but there is no need for it, since the OS will send the data anyway. So it's not a flush in the sense of a required flush.
For the Java side I'm not exactly sure what the flush is doing. It could be that the Java OutputStream has an internal buffer which is only written to the OS socket with the write system call once either a certain treshold of bytes in the buffer is reached or flush is called. Or flush exists purely in order to satisy the OutputStream base class and does nothing extra. You should be safe by using flush on the Java side (or on other platforms where it exists) and doing nothing special in native socket APIs.
If you call read() on an InputStream retrieved from a Socket (with an infinite read timeout) passing a buffer of N bytes and it returns N, how do you distinguish between these 2 cases:
There was exactly N bytes received by the socket so far and your buffer happens to have the same size
Calling read() again will block
You don't want that, since now you won't be able to process the received data so far until new data has been received
There was more than N bytes received by the socket so far and your buffer is too small
Call read() again should not block and just return the extra data
This is what you want as you will have all the received data and can process it
The first solution I can think of is to make sure N is greater than the internal Socket receive buffers, but I don't know what the value should be, and that seems very fragile.
Another possibility would be to temporarily change the read timeout of the socket to a very short one (like a few ms) for the 2nd read() call, so that if we're in case 1), it doesn't really block. That sounds like a hack though.
Recently I was experimenting with netty. I came across the following problem:
ctx.channel().write(new TextWebSocketFrame("hello")) didnot return hello on the client side, but ctx.channel().writeAndFlush(new TextWebSocketFrame("hello")) did.
What is the difference between two ? ctx is the ChannelHandlerContext.
According to doc
http://netty.io/4.0/api/io/netty/channel/Channel.html
channel.writeAndFlush(msg);
is a shortcut for
channel.write(msg);
channel.flush();
And flush() flushes (writes down) all the pending buffers.
The way I think it works is if you use write and you are sending more data than the buffer can hold it will automatically flush when the buffer is full to make room for more data to be buffered. At the end when there is no more data to push the other data through the buffer you must call flush() to flush whatever is left in the buffer. In your case what you are sending does not excede the buffer size so it sits in the buffer and nothing pushes it through that is why you must call flush() after write to push whatever is left in the buffer through. The writeAndFlush method does this automatically for you I think when it reaches the end it automatically flushes the buffer.
Well, clearly in Netty write() buffers and flush() flushes. Exactly as in a BufferedOutputStream, in fact.
So the question arises: when to flush()? The answer is, at the end of sending a request or a response, unless you're about to close the channel. For example, in a client,merits all the request karts, flush, then read the response. In a server, send all the response parts, and flush.