{"id":2219,"date":"2008-07-30T14:54:34","date_gmt":"2008-07-30T14:54:34","guid":{"rendered":"http:\/\/t.motd.kr\/articles\/2219\/filling-the-gap-between-blocking-io-and-nio"},"modified":"2022-12-28T01:45:42","modified_gmt":"2022-12-27T16:45:42","slug":"filling-the-gap-between-blocking-i-o-and-nio","status":"publish","type":"post","link":"https:\/\/vault.motd.kr\/wordpress\/posts\/2219\/filling-the-gap-between-blocking-i-o-and-nio\/","title":{"rendered":"Filling the gap between blocking I\/O and NIO"},"content":{"rendered":"\n
A non-blocking NIO<\/span> A common workaround is to prepend a length field for each message so you can wait until you read a whole message before calling An How can we implement a NIO<\/span> network application to be interoperable with those legacy applications without any modification? It was considered to be impossible\u2026 until today!<\/p>\n\n\n\n I\u2019ve just released a new milestone of Netty<\/a> which addresses the issue I described above. It provides You will also find you can do the same for any kind of The excitement of With How could this work? You might think this is pretty inefficient, but it turned out to be very efficient<\/a> in most cases. Higher throughput means lower chance of replay because we will receive more than one message for a single input buffer (often dozens). Consequently, most messages will be decoded in one shot without a replay. In case of slow connection, it will be less than optimal but you won\u2019t see much difference because it\u2019s already slow because the connection itself is slow. Just compare the code complexity of the two paradigms. I\u2019d definitely go for the latter.<\/p>\n","protected":false},"excerpt":{"rendered":" A non-blocking NIO Channel and a blocking InputStream have inevitable impedance mismatch. Because an InputStream is supposed to block for every read operation unless there\u2019s some data available in the buffer, any InputStream-based decoder implementation can\u2019t be used with a non-blocking NIO application right away. A common workaround is to prepend a length field for… Continue reading Channel<\/code> and a blocking
InputStream<\/code> have inevitable impedance mismatch. Because an
InputStream<\/code> is supposed to block for every read operation unless there\u2019s some data available in the buffer, any
InputStream<\/code>-based decoder implementation can\u2019t be used with a non-blocking NIO<\/span> application right away.<\/p>\n\n\n\n
InputStream.read()<\/code>. However, this turns your NIO<\/span> application incompatible with a legacy blocking I\/O application because the legacy application doesn\u2019t prepend a length field at all. You might have managed to modify the legacy application to prepend a length field, but we know it\u2019s not always the case. We need something to fill this gap between two I\/O paradigms.<\/p>\n\n\n\n
ObjectInput\/OutputStream<\/code>-based blocking I\/O network applications are the most common case because it was considered to be the easiest quick-and-dirty solution for intranet Java object exchange. It\u2019s as simple as wrapping an
InputStream<\/code> of a
Socket<\/code> with an
ObjectInputStream<\/code> (i.e.
in = new ObjectInputStream(socket.getInputStream());<\/code>).<\/p>\n\n\n\n
CompatibleObjectEncoder<\/code> and
CompatibleObjectDecoder<\/code>, which retains interoperability with the legacy
ObjectInput\/OutputStream<\/code>-based socket applications.<\/p>\n\n\n\n
InputStream<\/code> implementations with Netty\u2019s
ReplayingDecoder<\/code> with fairly small amount of effort, which means you can shift the paradigm of your complicated blocking protocol client\/server to more scalable non-blocking paradigm while retaining most legacy code.<\/p>\n\n\n\n
ReplayingDecoder<\/code> doesn\u2019t stop here. It also allows you to implement a non-blocking decoder in a blocking paradigm. In a non-blocking paradigm, you always had to check if there\u2019s enough data in the buffer, like the following:<\/p>\n\n\n\n
public boolean decode(ByteBuffer in) {\n if (in.remaining() < 4) {\n return false;\n }\n\n \/\/ Read the length header.\n int position = in.position();\n int length = in.getInt();\n if (in.remaining() < length) {\n in.position(position);\n return false;\n }\n\n \/\/ Read the body.\n byte[] data = new byte[length];\n in.get(data);\n ...\n return true;\n}<\/code><\/pre>\n\n\n\n
ReplayingDecoder<\/code>, you don\u2019t need to check the availability of the input buffer at all:<\/p>\n\n\n\n
public void decode(ByteBuffer in) {\n \/\/ Read the length header.\n int length = in.getInt();\n\n \/\/ Read the body.\n byte[] data = new byte[length];\n in.get(data);\n ...\n}<\/code><\/pre>\n\n\n\n
ReplayingDecoder<\/code> uses a sort of continuation technique. It rewinds the buffer position to the beginning when there\u2019s not enough data in the buffer automatically and calls
decode()<\/code> again (i.e. replays the decode) when more data is received from a remote peer.<\/p>\n\n\n\n