NIO: Nonblocking writes

I’m considering porting my game server to NIO - right now I’m using a traditional multithreaded server.
My biggest problem is: How to handle writes?
The naive way of course would be to stay registered for “writable” events, but then I get tons of those, even if I don’t have anything to send to the client.
Other option is to unregister clients channel every time his send queue is emptied - then re-register when another message is put in the queue. Problem is that the selector might very well be blocked in a Select(). I can’t seem to find a solution for this.

It’s not really an event IIRC it’s just a state (writable).

So just register, and if you have something to send,
wait until the writable-state is reached (which is most
likely already the case).

To stop making the select()-loop eating way too much
cpu-cycles, make the thread sleep 1ms, if, and only if:

  • not in readable state
  • in writable state
  • you have nothing to write

You cna also launch ONE thread to do all your NIO and allow it to block in select waiting for the necessary states.

Thats really the right way to minimize impact on ther est of your code.

Exactly, that’s what I had in mind:
1 thread for gameloop, one thread for handling Select(). But imagine this scenario:

Assume none of the clients are currently sending any data (The players are just not touching the mouse for a few seconds)

gameloop wants to send something to a client so it puts the message on the send queue - how can it send it?
NIO loop is most likely blocked in Select() because key was unregistered for writable the last time we send something. Can’t register for writable now can I?

Okay lets lay it out. It sounds liek youve already thought abotu msot of this BUT…

Now the issue you will run into is “what do I do when there is nothign on the queue? I dont want to spin endlessly because ist writable”

The answer is to change your selection flags according to the situation. WHile you have stuff on the queue, you have write selectio enabled, when you dont, you dont.

The big question is the one you raised: what if im sitting in select without writable selection and suddenly soemthing comes onto the queue? the answer is you need to set a flag and interrupt the select. (See Selector.wakeup() )

Its a bit of tricky multi-threaded coding and if you dont get your synchronization just right you WILL hose yourself, but its all doable AFAIK.

A side nto to ALL fo this however is that it may be over-kiil. A basic write to the SocketStream will never block unless the system buffer is full. The system buffer will not fill unelss the otherside has stopped recieving for some reason OR you are pumnping data faster then the system can put it out the ethernet port (highly unlikely). I’ve written a lot of code that has just assumed that the system buffer will be able to take the data that has worked just fine…

Thanks. The last solution is what I was doing before with my multithreaded server: I only created threads and queues for reading, while writing directly to the output streams when needed (Actually the outputstreams were wrapped in BufferedOutputStream and I only flushed the clients once every game tick to cut down packet overhead)

What happens if I send data faster than the client can receive it? If I use the easy method can a player with a slow connection (Or even evil intentions) block my gameloop? (I remember a Mud I used to play where the admins would punish you by “touring you around the world” so fast that you couldn’t keep up with the send data and simply lost your connection. Would the same happen here? )

What happens is basically this…

(1) First the client system’s recieve buffer for that socket fills to capacity.
(2) If the buffer gets totally filled, then the client sends a singla to the server telling ti to stop sending.
(3) At that point the server’s send buffer for that socket starts filling up
(4) If that fills all the way, a call to wriet on the scoket stream wil lblock until tehre is room or the connection is forceably closed (like when the client just suddenly vanishes)

This is what selecting onWritable is rally telling you-- that the send buffer is not fileld yet. Note that you DO have to kmae sure that there is enough room left for your entire packet befoer yo uwrite it, or else have logic to put what doesnt fit in the send buffer abck on the queu to be setn ina subsequent write. If you do the latter, you will need to handle assembling split packets on the recieving end.

If you are communictaing as a text stream, like you might in a mud, then you may not have to worry about pakets because your stream-reads are going to wait until there is neough data in the stream to satisfy the read anyway, but then you get into blockign issues. In general its easier to organize your communciation in well defiend packets.

Hope that helps.

If I use the easy method can a player with a slow connection (Or even evil intentions) block my gameloop? (I remember a Mud I used to play where the admins would punish you by “touring you around the world” so fast that you couldn’t keep up with the send data and simply lost your connection. Would the same happen here? )

Thanks again:) Yeah I’m working with my own packet format, prefixed by length so I don’t have to worry about fragmented packets at all.
Another thing that could happen I guess is that packets pile up somewhere in the system because the client’s own connection is too slow. I suppose that would trigger some network node on the path to the client to tell my socket to stop sending to?
I’m looking for a way to get the available send buffer size. Socket only has getSendBufferSize() that tells me the total size.

Ok, tested it now.
Had to change my code a bit for reading, instead of reading to the end of a message in a method (with calls possibly blocking while waiting for data) I needed to do a state-based reading:

public void onRead(ByteBuffer buf)
{
	while (buf.remaining() > 0)
	{
		//System.out.print((char) buf.get());
		switch (readState)
		{
			case STATE_READY:
				{
					if(buf.remaining() >= 2)//then we can read the length field
					{
						contentLength = buf.getShort();
						Logger.log("Length Field:" + contentLength);//need logic for illegal lengths
						content = new byte[contentLength];
						contentPointer = 0;
						readState = STATE_READING_CONTENT;
					}
					else if(buf.remaining() == 1)//can't read the whole length field
					{
						contentLength = (buf.get()&0xFF)<<8;//read most significant byte
						readState = STATE_READING_LENGTH;
					}
				}
				break;
			case STATE_READING_LENGTH:
				{
					contentLength |= (buf.get()&0xFF);//read least significant byte
					Logger.log("Length Field:" + contentLength);//need logic for illegal lengths
					content = new byte[contentLength];
					contentPointer = 0;
					readState = STATE_READING_CONTENT;
				}
				break;
			case STATE_READING_CONTENT:
				{
					int length = contentLength - contentPointer;
					if(buf.remaining() < length)
						length = buf.remaining();
					buf.get(content, contentPointer, length);
					contentPointer += length;
					//invariant:contentpointer = number of already read bytes, and also points to
					//the next position in content where bytes will be inserted
					if(contentPointer == contentLength)//We read everything
					{
						commands.add(new Command(content));
						readState = STATE_READY;
					}
				}
				break;
		}
	}
}

I can’t write directly to the socket’s output stream, since I get IllegalBlockingModeException if I try, but a write to the channel does the trick. (still need the logic for handling incomplete writes in case of send buffer being too small - need to maintain consistency. Does a ByteBuffer “wrap” at the end?

If i understand the question then no, it is not a ring buffer. (Though a ring buffer impelemntation of Buffer woudl be way cool and useful :slight_smile: ) If you hit the end you will just get a “buffer full” error of some kind.

…yeah, I filed an RFE with sun for that 3 years ago, IIRC.