8

I am developping a 1 to many server-client application, which is a small project.

Since socket IO is blocking. I am looking for a solution for this.

Could anyone tell me what is the good/bad for each of the 2 solutions?

  1. use java.nio
  2. new a single thread for every client that connected.

Thanks

Leon
  • 8,151
  • 11
  • 45
  • 51
  • A single thread for every client? Isn't that two threads per client, one for reading and one for writing? Otherwise you can't do anything as long as the thread is blocked in read(). – Sergei Tachenov Feb 02 '11 at 19:10
  • @Sergey, write blocking is real culprit since it cannot be interrupted and in case of bad IO, the thread blocks until the OS decides that it's cool. Read thread can be managed w/ SO_TIMEOUT. So it's 2 threads per socket. – bestsss Feb 02 '11 at 23:30
  • @bestsss, doesn't SO_TIMEOUT also affects write()? And if there's bad IO, usually the thread has nothing better to do than trying to write anyway. – Sergei Tachenov Feb 03 '11 at 07:18
  • @Sergey, no, it doesn't. There is no interrupt-write operations, even if there were, you still can't know how much was written. Such design really sucks vs arbitrary 'bad' clients that read a few bytes per second. You can not even stop the threads during writing. With read threads you can even use available (but it can degrade performance for one more JNI call) – bestsss Feb 03 '11 at 08:02

5 Answers5

5

There's nothing wrong with either approach. If you have limited clients the second option will suffice (and possibly even thrive based on a multicore architecture), otherwise it might be beneficial to let java.nio manage your resources.

See this question on the same topic, as well as this other post, or why not consider this blog post which argues against using java.nio for most scenarios.

Community
  • 1
  • 1
Johan Sjöberg
  • 47,929
  • 21
  • 130
  • 148
4

Individual threads

  • You can use the simple InputStream/OutputStream (or Reader/Writer) API, and wrapping Streams around each other. In my current project, I'm using a stack of
  • MessageFormatter, a custom formatting class
  • PrintWriter
  • OutputStreamWriter
  • DebugOutputStream (custom class, which makes a copy for debugging purposes)
  • DeflatorOutputStream (a custom subclass, in fact, which supports flushing)
  • the OutputStream of a SSLSocket

and the other way around on the receiving side. This is quite comfortable, since you only have to deal with the top layer in your program logic (the rest is mostly one constructor call each).

  • you need a new Thread (or even pair of threads, depending on architecture) for each connection.

(In my project I have a MessageParser-Thread for each connection, which then gives individual jobs to a ThreadPool, and these jobs then may write to one or several of the open connections, not only the one which spawned them. The writing is synchronized, of course.)

  • each thread needs quite a bit of stack space, which can be problematic if you are on a resource limited machine.

In the case of short-lived connections, you actually don't want a new thread for each connection, but only a new Runnable executed on a ThreadPool, since construction of new Threads takes a bit of time. (Note: With JDK 19, we can get cheap virtual threads. Use these instead, the thread pools are hidden in the background by the platform.)

nonblocking IO

  • if you have such a multi-layer architecture with multiple conversions, you have to arrange it all by yourself. It is possible:
    • my MessageFormatter can write to a CharBuffer as well as to a Writer.
    • for the Char-to-Byte-formatting, use CharsetEncoder/CharsetDecoder (transfers data between CharBuffer and ByteBuffer).
    • for the compressing, I used wrapper classes around Deflater/Inflater, which transfers data between two ByteBuffers.
    • for the encryption, I used a SSLEngine (one for each connection), which also uses ByteBuffers for input and output.
    • Then, write to a SocketChannel (or in the other direction, read from it).

Still, the management overhead is quite a hassle, as you must track where the data is. (I actually had a two pipelines for each connection, and one or two threads managing the data in all the pipelines, between this waiting on a Selector for new data on the sockets (or for new space there, in the outgoing case). (The actual processing after the parsing of the Messages still took place in spawned Runnables in a ThreadPool.)

  • You need only a small number of threads. This was actually the reason I tried to do this asynchronously. It worked (with the same synchronous client), but was way slower than my multi-thread solution, so I put this back until we run out of memory for too much threads. (Until now, there are not so much connections at the same time.)
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
1

NIO for the win and I can actually stop threads when I want to. Learning NIO is nowehere difficult, however using buffers properly is almost never explained anywhere. One of the reasons, I believe, people can't squeeze NIO for exrta benefits.

Other parts of NIO is general inability of the most developers to code and use state machines, so they end up copying the buffers way too much.

bestsss
  • 11,796
  • 3
  • 53
  • 63
0

NIO is reasonable for performance, and should use no more than #cores threads, multiplicated by the IO factor of your application, where the IO factor is the percentage of your app waiting for disk IO to complete.

The reason is simple. When you have #cores worker, each worker is likely to be bound to a single cpu core and can unitilize it to its maximum. the more workers, the more context switches, and this is exactly what you don't want and why you use NIO in the first place.

If the workers have to wait for IO, they could handle other requests, so use some more workers than cores for full cpu utilization.

If you use threads, you get the following advantages:

  • you can store session information in ThreadLocals.
  • you don't have to manage the session information in other ways.
Daniel
  • 27,718
  • 20
  • 89
  • 133
  • 1
    `java.nio` [isn't necessarily more efficient](http://paultyma.blogspot.com/2008/03/writing-java-multithreaded-servers.html). Threads are cheap, besides you're not taking into account SMT. – Johan Sjöberg Feb 02 '11 at 17:43
0

I tried Apache MINA, it is really really good. I strongly recommend it.

Edward Dale
  • 29,597
  • 13
  • 90
  • 129
Leon
  • 8,151
  • 11
  • 45
  • 51