PyroNet (lowlevel NIO Wrapper)

Nice API, but there are some problems.

When you read from the channel, and you get an IOException, (as opposed to a -1 return value), you will end up with a socket channel that will remain open.

You also create a new selector + a new thread for every connect (not accept). Also, keep in mind that interfacing with the native network buffers is really expensive. You have tied your networking API to a 4byte+payload protocol, but you also read your data that way. It’s going to slow you down if you first try to read 4 bytes, and then the payload, especially when you have 20 packets in your inbound buffer, and you have to call channel.read 40 times. Better to just read everything available and slice and dice it in your own code. You also don’t seem to support asynchronous connect (OP_CONNECT) which is not really bad…

Overall, nice API, but unfortunately ties the user to a 4B+data protocol, which means you never can implement oldschool stuff like HTTP, FTP, POP3, SMTP, etc, etc, on top of it, but then again, your probably only going to use it in your own program.

Cool, thanks for the feedback - I’ll have a go at fixing these points since this is just my first attempt at hacking together a networking protocol!

My main design criteria was having a simple, asynchronous API with relatively low overhead for my own applications. The point of the 4 byte message length protocol is that the networking library needs to know when the end of the message is, so that it only invokes the message handler once it has received a complete message. This simplifies the receiving code significantly since you don’t have to test and wait for a complete message etc.

I’m considering an smarter encoding where the message length is 1 byte for short messages (up to 127 bytes), 2 bytes for slightly longer ones, 3 bytes once it hits 16k etc.

I know what the 4B protocol is for, I’m just saying that you don’t necessarily have to hardcode that in a I/O library. I added on top of the lib, as tiny classes: Packet and PacketListener. The core classes just pump the received data into a bytebuffer, and from there into the packets, much faster than querying the OS for the next few bytes.

What’s the license on this, riven?

Hi Notch, special offer for you: BSD.

I love you =D

Just to satisfy my personal curiosity: what are you going to use it for?

Minecraft. The code I’ve written has a couple of bugs because I misunderstood NIO when implementing it, and the code isn’t really smooth any more.

FWIW: (or if SVN is your thing)

https://sourceforge.net/projects/jawnae-iowa/

After some ‘good advice’, the project is both renamed and relocated:

http://code.google.com/p/priobit/

Cool, Google Code is much better. I’m pretty done with SourceForge and their ugly new site. I still have a project over there I need to move over.

I have some trouble navigating your code. I keep looking for things similar to my code and not finding them. :wink: I’m reading my code, trying to remember gotchas, then trying to find it in your code.

I remember that when you do socketChannel.close(), it doesn’t completely close the socket. You need to do one more select to properly clean up the socket resources, so be careful not to close and exit your select loop without doing the extra select. Unfortunately I don’t remember how the problem manifests. Maybe if you close then try to open on the same port you’ll find it is still open, or check with netstat.

I noticed you are always using OP_WRITE to do writes. Apparently it is better to try the write and only use OP_WRITE if the write is partial. Apparently the write will almost always succeed the first time and is much more efficient.

Line 92 in NioServer, non-blocking accept can return null.

You might want to use setTcpNoDelay to turn off Nagle.

I saw on your old homepage you buffer some number (1400?) of bytes before sending? What happens if I send less than that? Does it timeout waiting for more and then send? How long is the timeout?

When writing my serialization lib, I found this comparison project very helpful. It gave me reasonable goals for my performance. Maybe it would be cool to setup a comparison project for Priobit/KryoNet/MINA/JGN/etc. We’d need to come up with a few tests and implement them in the different projects. I’d like to issue a preemptive “not it” for writing the MINA tests! :persecutioncomplex:

SF is so slow, it’s not funny.

[quote=“Nate,post:44,topic:33699”]
Same for me. Your code is not like mine at all.

[quote=“Nate,post:44,topic:33699”]
Sockets always linger, even if you close() then, use setSoLinger(), or terminate the JVM. This is OS level stuff that you can’t control. Try to make ~5000 TCP connections (in Windows) – it will tell you it can’t make any more. Then terminate the JVM, relaunch it, and you’ll find that you still can’t make any new Sockets. You simply have to wait a few minutes.

Good idea.

[quote=“Nate,post:44,topic:33699”]
this is inside the onReadyToAccept() method, which will only be invoked when OP_ACCEPT was flagged. Therefore I’m quite confident it will never return null.

Already the case. All NioClients go through a setup procedure in the constructor, where that is set: NioClient.configure

That would be silly, eh? :slight_smile: It will send it immediately.

I’ll give that a look later.

Not what I meant. Let me find some text on it rather than vague waving my hands around… :wink: I think the SelectableChannel javadoc has all the vagueness we need… it states a channel cannot be deregistered directly, but that its key must be canceled, which can only happen in a select. So when you close a socket, it cancels the keys, which are actually canceled in the next select, which then deregisters the channel. The symptom is that your sockets will stay in close_wait.

Just going by the javadocs. I’m not confident of anything NIO-related. :slight_smile:

Major Minor overhaul!

Updates:

  • eager write: NioClient.enqueue(...) tries to write the data immediately to the channel

  • Replaced ‘ReadFutures’ with ‘ByteFilter’ which is much more flexible, allowing to write very basic ‘state machines’ that can yield results on (for example) fixed packet length, 2-byte-length + payload, byte[] pattern delimiters (like “\r\n” for line based protocols: HTTP, FTP, SMTP, POP3, etc, etc)

  • non-copy, non-split, non-merge ByteStream class that acts like a FIFO byte-queue, replacing ByteBufferStreamer that could potentially merge/split/merge on successive incomplete writes

Check out the SVN repository
http://code.google.com/p/priobit/source/browse/trunk/+priobit/# priobit/jawnae/priobit

To listen for infinite packets (2b+payload) using ByteFilterPacket


   @Override
   public void connectedClient(final NioClient client)
   {
      client.enqueueByteFilter(new ByteFilterPacket()
      {
         @Override
         public void onReady(ByteBuffer buffer)
         {
            byte[] payload = new byte[buffer.remaining()];
            buffer.get(payload);
            System.out.println("Received: " + new String(payload));

            // listen for next packet
            this.reset();
            client.enqueueByteFilter(this);
         }
      });
   }

To listen for infinite text lines (up to 4096 bytes long) using ByteFilterEndsWith


   @Override
   public void connectedClient(final NioClient client)
   {
      client.enqueueByteFilter(new ByteFilterEndsWith("\r\n".getBytes(), 4096)
      {
         @Override
         public void onReady(ByteBuffer buffer)
         {
            byte[] line = new byte[buffer.remaining() - ("\r\n".length())];
            buffer.get(line);
            System.out.println("Received line: " + new String(line));

            // listen for next line
            this.reset();
            client.enqueueByteFilter(this);
         }
      });
   }

(pump up)
Topic title needs a refresh, project’s current name is Pyronet ;D Good name if you ask me.

Yes, a surprisingly good name! ::slight_smile: :stuck_out_tongue:

KevGlass had been harrassing me about Priobit, and he liked PyroNet. Although I knew it was way too similar to KryoNet, I just couldn’t help myself and gave in.

Besides that, as absolutely nobody is using it, I’m not even updating the SVN repo anymore.

Everything is on pyronet now, isn’t it? I’m going to read your source code…

Do you plan to implement something similar to RMI?

Did you read the summary?
http://code.google.com/p/pyronet/

PyroNet is a fast, minimalistic, lowlevel abstraction layer over the high performance Java NIO API. The aim of the API is not to provide new functionality, but to reduce the complexity of interfacing with non-blocking I/O. The PyroNet API takes care of the boilerplate code, while not forcing any protocol on the developer. Then API is event driven and uses a single Selector per PyroNetwork. You will receive event notifications through a listener interface.