Java nio bytebuffer - IT Справочник
Llscompany.ru

IT Справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java nio bytebuffer

Java Nio ByteBuffer Example

Posted by: JJ in nio June 15th, 2017 0 Views

This article is a tutorial on demonstrating the usage of the Java Nio ByteBuffer. All examples are done in the form of unit tests to easily prove the expectations of the API.

1. Introduction

The ByteBuffer class is an abstract class which also happens to extend Buffer and implement Comparable. A Buffer is simply a linear finite sized container for data of a certain primitive type. It exhibits the following properties:

  • capacity: the number of elements it contains
  • limit : the index of where the data it contains ends
  • position : the next element to be read or written

ByteBuffer has these properties but also displays a host of semantic properties of it’s own. According to the ByteBuffer API the abstraction defines six categories of operations. They are:

  1. get(. ) and put(. ) operations that operate relatively (in terms of the current position) and absolutely (by supplying an index)
  2. bulk get(. ) operation done relatively (in terms of the current position) which will get a number of bytes from the ByteBuffer and place it into the argument array supplied to the get(. ) operation
  3. bulk put(. ) operation done absolutely by supplying an index and the content to be inserted
  4. absolute and relative get(. ) and put(. ) operations that get and put data of a specific primitive type, making it convenient to work with a specific primitive type when interacting with the ByteBuffer
  5. creating a “view buffer’ or view into the underlying ByteBuffer by proxying the underlying data with a Buffer of a specific primitive type
  6. compacting, duplicating and slicing a ByteBuffer

A ByteBuffer is implemented by the HeapByteBuffer and MappedByteBuffer abstractions. HeapByteBuffer further specializes into HeapByteBufferR (R being read-only), which will very conveniently throw a ReadOnlyBufferException and should you try to mutate it via it’s API. The MappedByteBuffer is an abstract class which is implemented by DirectByteBuffer . All of the HeapByteBuffer implementations are allocated on the heap (obviously) and thus managed by the JVM.

2. Technologies used

The example code in this article was built and run using:

  • Java 1.8.101 (1.8.x will do fine)
  • Maven 3.3.9 (3.3.x will do fine)
  • Spring source tool suite 4.6.3 (Any Java IDE would work)
  • Ubuntu 16.04 (Windows, Mac or Linux will do fine)

3. Overview

A ByteBuffer is created via the the two static factory methods:

  • allocate(int) this will allocate a HeapByteBuffer with the capacity specified by the int argument
  • allocateDirect(int) this will allocate a DirectByteBuffer with the capacity specified by the int argument

The ByteBuffer class affords us the luxury of a fluent interface through much of it’s API, meaning most operations will return a ByteBuffer result. This way we can obtain a ByteBuffer by also wrapping a byte [] , slicing a piece of another ByteBuffer, duplicating an existing ByteBuffer and performing get(. ) and put(. ) operations against an existing ByteBuffer. I encourage you to review the ByteBuffer API to understand the semantics of it’s API.

So why the distinction between direct and non-direct? It comes down to allowing the Operating System to access memory addresses contiguously for IO operations (hence being able to shove and extract data directly from the memory address) as opposed to leveraging the indirection imposed by the abstractions in the JVM for potentially non-contiguous memory spaces. Because the JVM cannot guarantee contiguous memory locations for HeapByteBuffer allocations the Operating System cannot natively shove and extract data into these types of ByteBuffers. So generally the rule of thumb is should you be doing a lot of IO, then the best approach is to allocate directly and re-use the ByteBuffer. Be warned DirectByteBuffer instances are not subject to the GC.

4. Test cases

To ensure determinism we have been explicit about the Charset in use, therefore any encoding of bytes or decoding of bytes will use the explicit UTF-16BE Charset.

Relative Get and Put operations Test cases

The above suite of test cases demonstrate relative get() and put() operations. These have a direct effect on certain ByteBuffer attributes (position and data). In addition to being able to invoke these operations with byte arguments or receive byte arguments we also demonstrate usage of the putChar() and getChar(. ) methods which conveniently act on the matching primitive type in question. Please consult the API for more of these convenience methods

Absolute Get and Put operations Test cases

The above suite of test cases demonstrate usage of the absolute variants of the get(. ) and put(. ) operations. Interestingly enough, only the underlying data is effected ( put(. ) ) as the position cursor is not mutated owing to the method signatures providing client code the ability to provide an index for the relevant operation. Again convenience methods which deal with the various primitive types are also provided and we demonstrate use of the . Char(. ) variants thereof.

ViewBuffer Test cases

In addition to the various convenience get(. ) and put(. ) methods that deal with the various primitive types ByteBuffer provides us with an assortment of methods that provide primitive ByteBuffer views of the underlying data eg: asCharBuffer() demonstrates exposing a Character Buffer view of the underlying data.

Miscellaneous ByteBuffer Test cases

5. Summary

In this tutorial we learned a bit about ByteBuffers, we understood how to create one, the various types, why we have the different types and when to use them as well as the core semantic operations defined by the abstraction.

ByteBuffers are not thread safe and hence many operations on it, need to be guarded against to ensure that multiple threads do not corrupt the data or views thereon. Be wary of relative get(. ) and put(. ) operations as these do sneaky things like advancing the ByteBuffers position.

Wrapping, slicing and duplicating all point to the byte [] they wrapped or the ByteBuffer they sliced / duplicated. Changes to the source input or the resulting ByteBuffers will effect each other. Luckily with slice(. ) and duplicate(. ) the position, mark and limit cursors are independent.

When toggling between reading data into a ByteBuffer and writing the contents from that same ByteBuffer it is important to flip() the ByteBuffer to ensure the limit is set to the current position , the current position is reset back to 0 and the mark , if defined, is discarded. This will ensure the ensuing write will be able to write what was just read. Partial writes in this context can be guarded against by calling compact() right before the next iteration of read and is very elegantly demonstrated in the API under compact.

When comparing ByteBuffers the positions matter, ie: you can have segments of a ByteBuffer that are identical and these compare favorably should the two ByteBuffers, in question, have the same position and limit ( bytesRemaining() ) during comparison.

For frequent high volume IO operations a DirectByteBuffer should yield better results and thus should be preferred.

Converting a byte [] into a ByteBuffer can be accomplished by wrapping the byte [] via the wrap(. ) method. Converting back to a byte [] is not always that straight forward. Using the convenient array() method on ByteBuffer only works if the ByteBuffer is backed by a byte [] . This can be confirmed via the hasArray() method. A bulk get(. ) into an applicably sized byte [] is your safest bet, but be on the guard for sneaky side effects, ie: bumping the position cursor.

Java NIO — Buffer

Buffers in Java NIO can be treated as a simple object which act as a fixed sized container of data chunks that can be used to write data to channel or read data from channel so that buffers act as endpoints to the channels.

It provide set of methods that make more convenient to deal with memory block in order to read and write data to and from channels.

Buffers makes NIO package more efficient and faster as compared to classic IO as in case of IO data is deal in the form of streams which do not support asynchronous and concurrent flow of data.Also IO does not allow data execution in chunk or group of bytes.

Primary parameters that defines Java NIO buffer could be defined as −

Capacity − Maximum Amount of data/byte that can be stored in the Buffer.Capacity of a buffer can not be altered.Once the buffer is full it should be cleared before writing to it.

Читать еще:  Как восстановить прежние настройки компьютера windows 7

Limit − Limit has meaning as per the mode of Buffer i.e. in write mode of Buffer Limit is equal to the capacity which means that maximum data that could be write in buffer.While in read mode of buffer Limit means the limit of how much data can be read from the Buffer.

Position − Points to the current location of cursor in buffer.Initially setted as 0 at the time of creation of buffer or in other words it is the index of the next element to be read or written which get updated automatically by get() and put() methods.

Mark − Mark a bookmark of the position in a buffer.When mark() method is called the current position is recorded and when reset() is called the marked position is restored.

Buffer Type

Java NIO buffers can be classified in following variants on the basis of data types the buffer deals with −

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Important methods of Buffer

As mentioned already that Buffer act as memory object which provide set of methods that make more convenient to deal with memory block.Following are the important methods of Buffer −

allocate(int capacity) − This method is use to allocate a new buffer with capacity as parameter.Allocate method throws IllegalArgumentException in case the passed capacity is a negative integer.

read() and put() − read method of channel is used to write data from channel to buffer while put is a method of buffer which is used to write data in buffer.

flip() − The flip method switches the mode of Buffer from writing to reading mode.It also sets the position back to 0, and sets the limit to where position was at time of writing.

write() and get() − write method of channel is used to write data from buffer to channel while get is a method of buffer which is used to read data from buffer.

rewind() − rewind method is used when reread is required as it sets the position back to zero and do not alter the value of limit.

clear() and compact() − clear and compact both methods are used to make buffer from read to write mode.clear() method makes the position to zero and limit equals to capacity,in this method the data in the buffer is not cleared only the markers get re initialized.

On other hand compact() method is use when there remained some un-read data and still we use write mode of buffer in this case compact method copies all unread data to the beginning of the buffer and sets position to right after the last unread element.The limit property is still set to capacity.

mark() and reset() − As name suggest mark method is used to mark any particular position in a buffer while reset make position back to marked position.

Example

The following example shows the implementation of above defined methods.

Java: преобразование строки в ByteBuffer и из него и связанные с этим проблемы

Я использую Java NIO для моих сокетных соединений, и мой протокол основан на тексте, поэтому мне нужно иметь возможность конвертировать строки в ByteBuffers перед записью их в SocketChannel и конвертировать входящие ByteBuffers обратно в строки. В настоящее время я использую этот код:

это работает большую часть времени, но я сомневаюсь, если это рекомендуемый (и самый простой) способ сделать каждое направление этого преобразования, или если есть другой способ попробовать. Иногда, и, казалось бы, на случайные, звонки на encode() и decode() бросит a java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END исключение или подобное, даже если я использую новый объект ByteBuffer каждый раз, когда выполняется преобразование. Нужно ли синхронизировать эти методы? Какой-нибудь лучший способ для преобразования между строками и объектов ByteBuffer? Спасибо!

4 ответа:

Проверьте CharsetEncoder и CharsetDecoder описания API — вы должны следовать определенная последовательность вызовов метода чтобы избежать этой проблемы. Например, для CharsetEncoder :

  1. переустановите шифратор через reset метод, если он не был использован ранее;
  2. вызов encode метод ноль или более раз, пока дополнительный вход может быть доступен, передавая false для аргумента endOfInput и заполнение входного буфера и промывка выходного буфера между вызовами;
  3. вызов encode метод в последний раз, проходя true для аргумента endOfInput; а затем
  4. вызов flush метод так, что шифратор сможет потопить любое внутреннее положение к буферу выхода.

кстати, это тот же подход, который я использую для NIO, хотя некоторые из моих коллег преобразуют каждый символ непосредственно в байт в знаниях, которые они только используя ASCII, который я могу себе представить, вероятно, быстрее.

Если все не изменилось, вам лучше с

обычно буфер.hasArray() будет либо всегда true, либо всегда false, в зависимости от вашего варианта использования. На практике, если вы действительно не хотите, чтобы он работал при любых обстоятельствах, безопасно оптимизировать ветку, которая вам не нужна.

ответ Адамского является хорошим и описывает шаги в операции кодирования при использовании общего метода кодирования (который принимает байтовый буфер в качестве одного из входов)

однако рассматриваемый метод (в данном обсуждении) является вариантом кодирования — encode (CharBuffer in). Это же удобный метод, который реализует всю операцию кодирования. (См. Ссылка на Java Docs в С. П.)

согласно документации, этот метод поэтому не следует вызывать, если операция кодирования уже выполняется (что и происходит в коде ZenBlender-использование статического кодера/декодера в многопоточной среде).

лично мне нравится использовать удобство методы (более общие методы кодирования / декодирования), поскольку они снимают нагрузку, выполняя все шаги под обложками.

ZenBlender и Adamski уже предложили несколько вариантов способов смело делайте это в своих комментариях. Перечисляя их все здесь:

  • создать новый объект кодера / декодера, когда это необходимо для каждой операции (не эффективно, поскольку это может привести к большому количеству объектов). Или,
  • использовать ThreadLocal, чтобы избежать создания нового кодера/декодера для каждой операции. Или,
  • синхронизировать всю операцию кодирования / декодирования (это может быть не предпочтительнее, если пожертвование некоторым параллелизмом не подходит для вашей программы)

It’s all about buffers: zero-copy, mmap and Java NIO

There are use cases where data need to be read from source to a sink without modification. In code this might look quite simple: for example in Java, you may read data from one InputStream chunk by chunk into a small buffer (typically 8KB), and feed them into the OutputStream , or even better, you could create a PipedInputStream , which is basically just a util that maintains that buffer for you. However, if low latency is crucial to your software, this might be quite expensive from the OS perspective and I shall explain.

What happens under the hood

Well, here’s what happens when the above code is used:

  1. JVM sends read() syscall.
  2. OS context switches to kernel mode and reads data into the input socket buffer.
  3. OS kernel then copies data into user buffer, and context switches back to user mode. read() returns.
  4. JVM processes code logic and sends write() syscall.
  5. OS context switches to kernel mode and copies data from user buffer to output socket buffer.
  6. OS returns to user mode and logic in JVM continues.

This would be fine if latency and throughput aren’t your service’s concern or bottleneck, but it would be annoying if you do care, say for a static asset server. There are 4 context switches and 2 unnecessary copies for the above example.

OS-level zero copy for the rescue

Clearly in this use case, the copy from/to user space memory is totally unnecessary because we didn’t do anything other than dumping data to a different socket. Zero copy can thus be used here to save the 2 extra copies. The actual implementation doesn’t really have a standard and is up to the OS how to achieve that. Typically *nix systems will offer sendfile() . Its man page can be found here. Some say some operating systems have broken versions of that with one of them being OSX link. Honestly with such low-level feature, I wouldn’t trust Apple’s BSD-like system so never tested there.

With that, the diagram would be like this:

You may say OS still has to make a copy of the data in kernel memory space. Yes but from OS’s perspective this is already zero-copy because there’s no data copied from kernel space to user space. The reason why kernel needs to make a copy is because general hardware DMA access expects consecutive memory space (and hence the buffer). However this is avoidable if the hardware supports scatter-n-gather:

Читать еще:  Восстановить сломанную флешку микро сд в люблино

A lot of web servers do support zero-copy such as Tomcat and Apache. For example apache’s related doc can be found here but by default it’s off.

Note: Java’s NIO offers this through transferTo (doc).

The problem with the above zero-copy approach is that because there’s no user mode actually involved, code cannot do anything other than piping the stream. However, there’s a more expensive yet more useful approach — mmap, short for memory-map.

Mmap allows code to map file to kernel memory and access that directly as if it were in the application user space, thus avoiding the unnecessary copy. As a tradeoff, that will still involve 4 context switches. But since OS maps certain chunk of file into memory, you get all benefits from OS virtual memory management — hot content can be intelligently cached efficiently, and all data are page-aligned thus no buffer copying is needed to write stuff back.

However, nothing comes for free — while mmap does avoid that extra copy, it doesn’t guarantee the code will always be faster — depending on the OS implementation, there may be quite a bit of setup and teardown overhead (since it needs to find the space and maintain it in the TLB and make sure to flush it after unmapping) and page fault gets much more expensive since kernel now needs to read from hardware (like disk) to update the memory space and TLB. Hence, if performance is this critical, benchmark is always needed as abusing mmap() may yield worse performance than simply doing the copy.

The corresponding class in Java is MappedByteBuffer from NIO package. It’s actually a variation of DirectByteBuffer though there’s no direct relationship between classes. The actual usage is out of scope of this post.

NIO DirectByteBuffer

Java NIO introduces ByteBuffer which represents the buffer area used for channels. There are 3 main implementations of ByteBuffer :

  1. HeapByteBuffer
  2. This is used when ByteBuffer.allocate() is called. It’s called heap because it’s maintained in JVM’s heap space and hence you get all benefits like GC support and caching optimization. However, it’s not page aligned, which means if you need to talk to native code through JNI, JVM would have to make a copy to the aligned buffer space.
  3. DirectByteBuffer
  4. Used when ByteBuffer.allocateDirect() is called. JVM will allocate memory space outside the heap space using malloc() . Because it’s not managed by JVM, your memory space is page-aligned and not subject to GC, which makes it perfect candidate for working with native code (e.g. when writing OpenGL stuff). However, you are then “deteriorated” to C programmer as you’ll have to allocate and deallocate memory yourself to prevent memory leak.
  5. MappedByteBuffer
  6. Used when FileChannel.map() is called. Similar to DirectByteBuffer this is also outside of JVM heap. It essentially functions as a wrapper around OS mmap() system call in order for code to directly manipulate mapped physical memory data.

Conclusion

sendfile() and mmap() offer efficient, low-latency low-level solutions to data manipulation across sockets. Again, no code should assume these are silver bullets as real world scenarios may be complex and it might not be worth the effort to switch code to them if this is not the true bottleneck. For software engineering to get the most ROI, in most cases, it’s better to “make it right” and then “make it fast”. Without the guardrails offered by JVM, it’s easy to make software much more vulnerable to crashing (I literally mean crashing, not exceptions) when it comes to complicated logic.

Quick Reference

Efficient data transfer through zero copy — It also covers sendfile() performance comparison.

Java nio bytebuffer

Java NIO Tutorial

Java new input output library allows you to perform input and output operations efficiently. Unlike Java I/O classes which read and write data byte by byte using stream objects, Java NIO reads and writes data in blocks using channels which use operating system features.

NIO allows you to create high speed input and output programs as it doesn’t deals with keeping and removing data to and from buffers. NIO uses operating system features to do that. That is why you will get performance improvement in performing input and output operations using NIO API.

Table of Contents

Difference between IO and NIO

The I/O classes found in java.io.* package reads and writes data one byte at a time. Whereas, NIO classes found in java.nio.* package reads and writes data in blocks.

Input and output operations are slow using IO. Using NIO API, input and output operations are performed fast because it relies on operating system features and deals with data in blocks.

The advantage of using IO is that it allows you to use filters and chain of filters on data stream.

With IO, you directly read from and write to stream objects. Using NIO, you read from buffer objects and write to buffer objects. Buffer object in turn uses channel to read data from sources and to write data to destinations. NIO uses system level buffers.

Stream objects are unidirectional meaning you can create a stream for reading or writing. Channels are bidirectional meaning a channel can be used for both reading and writing data.

Using NIO API, data can be read and written asynchronously. You can use NIO API in such as a way that while performing IO operations current thread is not blocked.

NIO API offer selectors which allow you to listen to multiple channels for IO events in asynchronous or non blocking way.

Buffers

Buffers are non-resizable data container objects. You write data to buffers and channels read the data from it and transfer it to destinations. Channels get data from the source, write it to buffers and your program reads data from buffers. NIO API provides buffers which can contain different types of primitive data, such as CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, ShrotBuffer, etc. ByteBuffer can contain data in bytes. You communicate with Channels using ByteBuffer.

Attributes of Buffers are capacity (maximum number of elements buffer can contain), limit (it tells the end of active buffer content), position (index of the next element to be read and written) and mark (remembers the position when mark() is called, when reset() is called its position will be set to mark.)

To create a buffer, you need to first call allocate() method passing capacity which indicates the number of elements the array inside the buffer can hold.

Method allocate() creates non-direct buffer. If you want to create direct buffers, you need to use allocateDirect() method. With direct buffers, IO operations are performed directly on it without using intermediates. But direct buffers are expensive to allocate and de-allocate. Direct buffers improve performance when they are used for handling large amount of data that is operated upon by system native IO operations.

You can also create Buffer object by calling wrap() method and passing appropriate array to it as an argument.

ByteBuffer offers methods such as asIntBuffer(), asFloatBuffer(), asLongBuffer(), asDoubleBuffer(), etc, to convert it into different types of buffer objects.

To fill buffers, you can use put methods of the buffer object. In the below example, after CharBufer allocated, the capacity and limit are set 50 and position is set to 0. After filling the buffer with the deals string, capacity and limit stays same and position is set to 5. As you keep adding characters to the buffer, position value increases.

To access values or to drain buffers, you can use absolute or relative get methods. To use absolute get method, you need to pass index of the element you want to access. Relative get method returns the value relative to the current position.

To access different types of values, ByteBuffer offers methods such as getDouble, getChar, getInt, getDouble, etc.

Читать еще:  Как восстановить пароль яндекс почты без телефона

To access bulk values, you can use relative and absolute get() methods passing array to it.

Buffer object provides relative and absolute put() methods to add values to buffer. It also provides bulk relative put() methods to add array of values to it.

If you call get() immediately after put, you will not get first character. In the below example, after put, position is set to 5. A call to get() on the buffer give empty char value.

To make the get() return first element, you need to set the position to 0. Subsequent get() calls return next char value that means get() call increases position. If you call get() after position reaches to limit, it will throw BufferUnderflowException.

In order to prevent getting empty values or to get only active values from the buffer, you can set limit to total active element in the buffer. Going back to our example, char buffer contains only 5 elements. So we can set limit to 5. To prevent BufferUnderflowException, you can hasRemaining() to check if next active value is available.

Instead of calling limit and position methods, you can use flip() method to exactly do the same. It is important to note that flip() call should not be called twice. Second call to flip() sets the limit to 0.

Buffer provides rewind() method, it just sets the position of buffer to 0 without resetting the limit property of the buffer. You can reuse buffer after it is filled and drained using clear() method. Clear method doesn’t remove elements from buffer, but it sets the current position to 0 and the limit property is assigned the value of capacity of the buffer.

Buffer provides slice() method using which you can create a new Buffer object that contains values from current position of source buffer. The elements are shared between source and the new buffer, meaning changes to common element in buffers will be visible in the other buffer object. Slicing buffers allows you to get part of a buffer and perform modifications on it without using complete content.

You can convert a buffer to read only buffer using asReadOnlyBuffer() method.

Character Set

Java new input output API provides in java.nio.charset package charset, character encoders and decoders which can be used to translate between bytes and Unicode characters.

CharSet is a name mapping between 16 bit Unicode characters and bytes.

Decoder transfers bytes in a specific character set into characters. Encode transfers characters into bytes in a specific character set. Standard character sets are US-ASCII (Seven-bit ASCII), ISO-8859-1 (ISO Latin alphabet No. 1), UTF-8 (8 bit character set unicode standard), UTF-16BE, UTF-16LE and UTF-16 (16 bit character set unicode standard) .

To get a Charset object, you need to call forName() static method of Charset class passing character set name.

You can get character set name and display name by calling name() and displayName() methods respectively on charset object. To encode a string using a specific char set, first create charset object using forName method and then call encode() method passing the string to be encoded. Method encode returns ByteBuffer object.

To decode bytes into characters, first create ByeBuffer containing the bytes, call decode() method on char set object passing ByteBuffer. Method decode() returns CharBuffer object.

Channels

Channel objects efficiently perform read and write operations on the destinations such as file, device, network socket and program components. To read data, you create a buffer and pass it to the read method of the channel object. To write data, you create a buffer object and pass it to write method of the channel object.

Channels can be used in blocking and non-blocking mode. In blocking mode operations return after completion only.

Some of the implementation classes of Channel interface are AsynchronousFileChannel, AsynchronousServerSocketChannel, AsynchronousSocketChannel, DatagramChannel, FileChannel, SelectableChannel, ServerSocketChannel and SocketChannel.

FileInputStream.getChannel(), FileOutputStream.getChannel(), and RandomAccessFile.getChannel() methods return FileChannel.

File Reading and Writing using Buffer and Channel Objects

Let’s see how to read from a channel and to write to the channel. For this example, we will use FileChannel.

First let’s see how to read data from a file. For that, we need to create Buffer object, then get FileChannel object from InputStreamReader and call read() method on FileChannel object passing ByteBuffer object.

To write data to a file, get the FileChannel object from OutputStreamWriter and call write method on FileChannel object passing ByteBuffer object.

Non Blocking IO Using Selector and Selectable Channels

You can create IO components which are multiplexed and non blocking using selector, selectable channel and selection key. Selector class is a multiplexer which allows multiple selectable channels to be added to it allowing multiple channels to be used on a single thread. Selector allows you to select channels which are ready for IO operations.

The channels that can be added to Selector are special channels created for using with Selector. The selectable channels can be operated in blocking mode (every IO operation blocks until it is completed) and non blocking mode (non-blocking channel doesn’t block the thread). To use a selectable channel with the selector, you need to put the selectable channel in non blocking mode.

When a selectable channel is added to selector, a token called selection key representing the registration of a channel with selector is created. Selector adds the newly created key to a set. When a channel becomes ready for IO operation, selector adds the key to a set which contains only keys of channels which are ready for IO. You can access this set, by invoking selectedKeys() method on the selector object.

Here are the steps to be performed in creating multiplexed non blocking IO components.

  • First create a selectable channel. Selectable channel classes are DatagramChannel for data gram sockets, Pipe.SinkChannel represents readable end of pipe, Pipe.SourceChannel represents writable end of pipe, ServerSocketChannel for stream oriented listening sockets, SocketChannel for stream oriented connecting sockets.
  • Put the selectable channel in non blocking mode.
  • Create selector object by calling static method open() on Selector class. It creates selector object using default selector provider.
  • Then register the selectable channel with selector by calling register() method on the channel object passing selector object and operation interest such as SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ or SelectionKey.OP_WRITE. The register() method returns selection key.
  • Then repeatedly invoke select() method on the selector object to check if any channels are ready for IO operations. This method returns number of channels ready for IO operations.
  • If select method in the above step returns greater than zero, get selected keys by calling selectedKeys() method on the selector object.
  • Iterate over the selected keys and check to see if a key represents an interested operation. If a key represents an interested operation, then get the selectable channel associated with the key and perform the operation on the channel.
  • Remove the key from selected set by calling remove on iterator.

Non Blocking IO Server Example

Let’s see how to use selector, selectable channel and selection key by creating non blocking IO component. In this example, we will use ServerSocketChannel and SocketChannel to create multiplexing server which accepts client connections, processes request and sends response to client.

The example first creates ServerSocketChannel, registers with selector for accept operation and repeatedly checks for channel readiness. When there is a client request, it gets the SocketChannel from ServerSocketChannel and registers it with selector for read operations. It checks for readiness of SocketChannel for read operation, reads input from client and sends response.

You can run the program, it will start server and listens for requests on 8080 port. If you send deals string from client, it will respond with offer. Below is the output.

Non Blocking IO Client Example

Below is the non blocking multiplexing client example. The example first creates SocketChannel, then connects to server, sends request to it and registers the channel with selector for read operations. Once the channel is ready for read operations, the response from server is captured.

If you run this example, you will get response from the server started using above server example.

Ссылка на основную публикацию
ВсеИнструменты 220 Вольт
Adblock
detector