Hi I am trying to make a chat using NIO, in the server part in a separate thread from the main when I :
private void broadcast(String msg, String user)
{
String Message = user + ":" + msg + "\0";
System.out.println(Message);
ByteBuffer buf=ByteBuffer.wrap(Message.getBytes());
Set<SelectionKey> selectedKeys = selector.keys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext())
{
SelectionKey key = keyIterator.next();
if(key.attachment() != user)
{
try {
((SocketChannel) key.channel()).write(buf);
} catch (IOException e) {
e.printStackTrace();
}
buf.rewind();
}
keyIterator.remove();
}
}
I get this exception:
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection$1.remove(Unknown Source)
at chatserver.MessageSender.broadcast(MessageSender.java:41)
at chatserver.MessageSender.run(MessageSender.java:113)
at java.lang.Thread.run(Unknown Source)
Line 41 is: keyIterator.remove();
I am using this as guideline: Java NIO Tutorial
You are invoking Selector.keys() where you appear to want Selector.selectedKeys(). The Set returned by the former is documented to be completely unmodifiable, whereas the one returned by the latter -- which agrees better with the name of the variable to which you assign the result -- is documented to permit element removal. Moreover, the selected keys are the ones ready for I/O.
Related
We wrote an incoming reactor that works this way:
Opens a selector
Opens a server socket channel
Starts a selection loop in which: The ServerSocketChannel accepts new SocketChannels into the loop, and each SocketChannel reads data and transfers it to a worker.
The shutting down procedure of the reactor is iterating over the selector.keys() and for each of them closing the corresponding channel and cancelling the key.
We wrote the following unit test for the shutdown procedure:
Open a reactor thread running the selction loop.
Open several Sender threads. Each opens a socket to the reactor and reads.
The read blocks until it gets -1 (meaning the reactor closed the socket).
After the read returns -1, the sender closes the socket and finishes.
The test causes ConcurrentModificationException pointing to the loop iterating over the sockets and closes them (which was in the main thread context).
Our assumption is that when a Sender read method got -1, it closed the socket and somehow it woke up the selector select method, The selector then accessed its keys set which was iterated by the shutdown loop and hence the exception.
We worked around this problem by creating a new list with all the keys of the selector. Canceling those keys by iterating this list prevent two objects from modifying the same key's set.
Our question are:
Is our assumption correct? When the client socket calls the close method- does it really wake up the selector?
Does the creation of a new list is the appropriate solution or is it just a work-around?
EDIT: Added some code snippets for clarifications
(We tried to narrow the code as possible)
IncomingReactor:
public boolean startAcceptingIncomingData() {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open());
serverSocketChannel.bind(new InetSocketAddress(incomingConnectionsPort));
serverSocketChannel.configureBlocking(false);
SelectionKey acceptorSelectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
acceptorSelectionKey.attach((Worker) this::acceptIncomingSocket);
startSelectionLoop(selector);
return true;
}
private boolean acceptIncomingSocket() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
selectionKey.attach(new WorkerImpl() /*Responsible for reading data and tranferring it into a parsing thread*/);
return true;
} catch (IOException e) {
return false;
}
}
private void startSelectionLoop(Selector selector) {
shouldLoop = true;
while (shouldLoop) {
try {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
if (!shouldLoop) {
break;
}
selectedKeys.forEach((key) -> {
boolean workSuccess = ((Worker) key.attachment()).work();
if (!workSuccess) {
key.channel().close();
key.cancel();
}
});
selectedKeys.clear();
} catch (ClosedSelectorException ignore) {
}
}
}
public void shutDown() {
shouldLoop = false;
selector.keys().forEach(key -> { /***EXCEPTION - This is where the exception points to (this is line 129) ***/
key.channel().close();
key.cancel();
});
try {
selector.close();
} catch (IOException e) {
}
}
UnitTest:
#Test
public void testMaximumConnectionsWithMultipleThreads() {
final int PORT = 24785;
final int MAXINUM_CONNECTIONS = 10;
IncomingReactor incomingReactor = new IncomingReactor(PORT);
Callable<Boolean> acceptorThread = () -> {
incomingReactor.startAcceptingIncomingData();
return true;
};
ExecutorService threadPool = Executors.newFixedThreadPool(MAXIMUM_CONNECTIONS + 1);
Future<Boolean> acceptorFuture = threadPool.submit(acceptorThread);
List<Future<Boolean>> futureList = new ArrayList<>(MAXIMUM_CONNECTIONS);
for (int currentSenderThread = 0; currentSenderThread < MAXIMUM_CONNECTIONS; currentSenderThread++) {
Future<Boolean> senderFuture = threadPool.submit(() -> {
Socket socket = new Socket(LOCALHOST, PORT);
int bytesRead = socket.getInputStream().read();
if (bytesRead == -1) { //The server has closed us
socket.close();
return true;
} else {
throw new RuntimeException("Got real bytes from socket.");
}
});
futureList.add((senderFuture));
}
Thread.sleep(1000); //We should wait to ensure that the evil socket is indeed the last one that connects and the one that will be closed
Socket shouldCloseSocket = new Socket(LOCALHOST, PORT);
Assert.assertEquals(shouldCloseSocket.getInputStream().read(), -1);
shouldCloseSocket.close();
incomingReactor.shutDown();
for (Future<Boolean> senderFuture : futureList) {
senderFuture.get();
}
acceptorFuture.get();
threadPool.shutdown();
}
Exception:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$KeyIterator.next(HashMap.java:1461)
at java.lang.Iterable.forEach(Iterable.java:74)
at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
at mypackage.IncomingReactor.shutDown(IncomingReactor.java:129)
at mypackage.tests.TestIncomingReactor.testMaximumConnectionsWithMultipleThreads(TestIncomingReactor.java:177)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:85)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:659)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:845)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1153)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:108)
at org.testng.TestRunner.privateRun(TestRunner.java:771)
at org.testng.TestRunner.run(TestRunner.java:621)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:357)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:352)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:310)
at org.testng.SuiteRunner.run(SuiteRunner.java:259)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1199)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1124)
at org.testng.TestNG.run(TestNG.java:1032)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:74)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:124)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
The shutting down procedure of the reactor is iterating over the selector.keys() and for each of them closing the corresponding channel and cancelling the key.
It should start by stopping the selector loop. NB Closing the channel cancels the key. You don't have to cancel it yourself.
We wrote the following unit test for the shutdown procedure:
Open a reactor thread running the selction loop.
Open several Sender threads. Each opens a socket to the reactor and reads.
The read blocks until it gets -1 (meaning the reactor closed the socket).
The reactor closed its accepted socket. Your client socket remained open.
After the read returns -1, the sender closes the socket and finishes.
I hope this means the sender closed its client socket.
The test causes ConcurrentModificationException pointing to the loop iterating over the sockets and closes them (which was in the main thread context).
Really? I don't see any stack trace in your question.
Our assumption is that when a Sender read method got -1, it closed the socket and somehow it woke up the selector select method
Not possible unless the reactor didn't close the channel, in which case you wouldn't have got -1 from read etc.
The selector then accessed its keys set which was iterated by the shutdown loop and hence the exception.
The exception is caused by modifying the key set during iteration. Bug in your server code.
We worked around this problem by creating a new list with all the keys of the selector. Canceling those keys by iterating this list prevent two objects from modifying the same key's set.
You need to fix the actual problem, and for that you need to post the actual code.
Our question are:
Is our assumption correct? When the client socket calls the close method- does it really wake up the selector?
Not unless the selector-end channel is still open.
Does the creation of a new list is the appropriate solution or is it just a work-around?
It is just a nasty workaround for a problem you haven't identified yet.
You cannot modify the selector.keys() Set<SelectionKey> from inside of the for loop because that Set is not capable of concurrent modification. (calling channel.close() will modify the Set from inside the loop reading the Set)
https://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html
The iterators returned by this class's iterator method are fail-fast:
if the set is modified at any time after the iterator is created, in
any way except through the iterator's own remove method, the Iterator
throws a ConcurrentModificationException. Thus, in the face of
concurrent modification, the iterator fails quickly and cleanly,
rather than risking arbitrary, non-deterministic behavior at an
undetermined time in the future.
SelectionKey[] keys = selector.keys().toArray(new SelectionKey[0]);
for( SelectionKey k : keys )
{
try
{
k.channel().close();
}
catch(Throwable x )
{
// print
}
}
try
{
selector.close();
}
catch(IoException e )
{
// print
}
I'm receiving a continuous stream of data that I'm saving to a ByteBuffer.
Sometimes I need to write to the channel, however, it's important not to lose any data. Is it possible to use the selector to solve this issue?
If I'm constantly checking the selector for the channel state, it always says that the channel is currently reading and it's like there is no opportunity to perform writing. I can't use multiple connections because the server doesn't support it.
this.socketChannel = SocketChannel.open();
this.socketChannel.configureBlocking(false);
this.socketChannel.connect(new InetSocketAddress(IP, this.port));
try {
this.selector = Selector.open();
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey selectionKey = this.socketChannel.register(selector, interestSet);
while (selector.select() > -1) {
// Wait for an event one of the registered channels
// Iterate over the set of keys for which events are available
Iterator selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
} else if (key.isReadable()) {
System.out.println("readable");
} else if (key.isWritable()) {
System.out.println("writable");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
Edit:
Sorry I didn't add more info. This is an important bit of my code. It always prints "readable" to the console and I was hoping that isWritable block also gets executed.
Thanks in advance, Honza
You are using else if operator so if your key is readable checking for if it is writeable will not be performed, but it doesn't mean that the channel is not writeable.
Actually it could be readable and writeable in the same time. But in your program if it is readable you just don't check for writeable.
replace else-if with if and see the result.
I'm trying to find the most elegant way of pairing two socket channels using java NIO. So far I am writing to one channel, reading from it and and writing the result to another.
The way I'm going about it seems like a hack and I was wondering if anyone knew of a better way?
public void readyChannels() {
while (true) {
try {
selector.select();// block here until a new IO event
Iterator keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
keys.remove();// do not process this again
write(key.channel(),"random data".getBytes());
byte[] bytes = read(key.channel());
write(otherChannel, bytes);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Just make one channel the key-attachment of the other channel's selection key, or better still make an object that contains them both and set that as the attachment.
I have a simple non-blocking server with main loop:
try {
while (selector.select() > -1) {
// Wait for an event one of the registered channels
// Iterate over the set of keys for which events are available
Iterator selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
try {
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
}
if (key.isReadable()) {
read(key);
}
if (key.isWritable()) {
write(key);
}
} catch (Exception e) {
e.printStackTrace();
close(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
In read/write section I check if there is something to read/write if not - then I try to close channel:
if (channel.read(attachment.buffer) < 1)
close(key);
Close method:
private void close(SelectionKey key) throws IOException {
key.cancel();
key.channel().close();
}
But during processing this code I get exception in main loop (it is catched but I supposed something wrong) I get this stacktrace:
java.nio.channels.CancelledKeyException
at sun.nio.ch.SelectionKeyImpl.ensureValid(Unknown Source)
at sun.nio.ch.SelectionKeyImpl.readyOps(Unknown Source)
at java.nio.channels.SelectionKey.isWritable(Unknown Source)
So it fails on main loop when enter write section, close channel and came back to main loop in 'writable' if section and fails with such exception. Any suggestions?
The error is very simple.
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
}
if (key.isReadable()) {
read(key);
}
if (key.isWritable()) {
write(key);
}
Your read method is the one which cancels the SelectionKey. However, after returning from read, you again test the key for whether the channel is writable -- potentially after just cancelling that very same key! Your initial check cannot help here.
One solution would be to check for whether the key is valid wherever it might've just been cancelled:
...
if (key.isValid() && key.isWritable()) {
write(key);
}
...
Alternatively, you could also try only registering one interest at a time as you need to on any particular channel, and thus all readiness events are mutually exclusive:
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
connect(key);
} else if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
This might be beneficial in situations; as generally a channel will almost always be write-ready, keeping an interest in write-readiness along side read-readiness might keep the Selector loop spinning, which is more than likely not desirable. For the most part, generally register interest in write-readiness only when the underlying socket output buffer is full.
As a side note, know that SocketChannel.read can return a value < 1 without it being an error.
A read operation might not fill the buffer, and in fact it might not read any bytes at all. Whether or not it does so depends upon the nature and state of the channel. A socket channel in non-blocking mode, for example, cannot read any more bytes than are immediately available from the socket's input buffer;
Additionally, Selector.select does not state anything about returning < -1 to indicate it is closed.
Returns: The number of keys, possibly zero, whose ready-operation sets were updated
How can i solve this problem. I got following error:
java.nio.channels.ClosedChannelException
This is coding:
public void run() {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(512);
int i1 = socketChannel.read(buffer);
if (buffer.limit() == 0 || i1 == -1) {
Socket s = null;
try {
s = socketChannel.socket();
s.close();
key.cancel();
} catch (IOException ie) {
if (UnitDataServer.isLog) {
log.error("Error closing socket " + s + ": " + ie);
}
}
} else {
buffer.flip();
if (UnitDataServer.isLog) {
log.info(" Recvd Message from Unit : " + buffer.array());
}
byte byteArray[] = buffer.array();
log.info("Byte Array length :" + byteArray.length);
hexString = new StringBuffer();
for (int i = 0; i < i1 /* byteArray.length */; i++) {
String hex = Integer.toHexString(0xFF & byteArray[i]);
if (hex.length() == 1) {
// could use a for loop, but we're only dealing with a
// single byte
hexString.append('0');
}
hexString.append(hex);
}
hexString.trimToSize();
log.info("Hex String :" + hexString);
Communicator.dataReceive(new DataReceive(
socketChannel, hexString.toString(), dst));
}
} catch (Exception e) {
if (UnitDataServer.isLog) {
// log.error(e);
}
try {
socketChannel.socket().close();
key.cancel();
} catch (IOException ex) {
if (UnitDataServer.isLog) {
log.error(ex);
}
}
}
}
You have closed the channel and are still trying to use it.
There are several issues with your code.
First, your test for EOS is faulty. Remove the limit() == 0 test. That doesn't indicate EOS, it just indicates a zero length read, which can happen in non-blocking mode at any time. It doesn't mean the peer has closed his end of the connection, and it doesn't mean you should close your end.
Second, closing a channel closes the socket as well. You should close the channel only, not the socket.
Third, closing a channel cancels the key. You don't need to follow every close with a cancel.
You may also have failed to check whether a ready key is valid in the select loop before using it, e.g. for reading.
I continue to be amazed, and amused, and bemused, by the claim elsewhere in this thread that 'source code is untrue' under some circumstances.
You need to fix/secure code that is throwing this exception. ClosedChannelException is ...
... thrown when an
attempt is made to invoke or complete
an I/O operation upon channel that is
closed, or at least closed to that
operation. That this exception is
thrown does not necessarily imply that
the channel is completely closed. A
socket channel whose write half has
been shut down, for example, may still
be open for reading
(as described in Java 6 API)
But really, you would need to provide us code snipped and stack trace in order to get more detailed help.