I wrote this NIO server, This will causeing self-spin:
public class ServerTest {
private final Queue<String> queue = new ConcurrentLinkedQueue<>();
#Test
public void server() throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(25780));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (serverSocketChannel.isOpen()) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
iterator.remove();
if (next.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
// In normal server usage server won't write active, They write when read trigger
if (next.isReadable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
socketChannel.read(ByteBuffer.allocate(1024));
socketChannel.write(ByteBuffer.wrap("ECHO!".getBytes(StandardCharsets.UTF_8)));
}
// This causing self-spin and one CPU thread 100%
if (next.isWritable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
while (queue.size() > 0) socketChannel.write(ByteBuffer.wrap(queue.poll().getBytes(StandardCharsets.UTF_8)));
}
}
}
selector.close();
serverSocketChannel.socket().close();
}
/**
* I will call this method when I want send active message from other thread
*/
#Test
public void sendMessage(String message) {
queue.add(message);
}
}
As
Is it possible to keep NIO in OP_WRITE mode without high CPU usage
Avoiding high CPU usage with NIO
those post I find out the OP_WRITE will cause self-spin, I think I need that:
if (next.isWritable()) {
SocketChannel socketChannel = (SocketChannel) next.channel();
while (queue.size() > 0) socketChannel.write(ByteBuffer.wrap(queue.poll().getBytes(StandardCharsets.UTF_8)));
next.interestOps(SelectionKey.OP_READ);
// Unregister WRITE when done
}
public void sendMessage(String message) {
queue.add(message);
// How to register interest from other thread ?
// Outside the selector loop I can't even get the "next" instance
}
But I have no idea how to do that, Then I try something absolute wor't work:
private static SelectionKey key;
...
while (iterator.hasNext()) {
...
key = next;
...
public void sendMessage(String message) {
queue.add(message);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// this will cause exception, because KEY is not selected
}
public void sendMessage(String message) {
queue.add(message);
selector.wakeup();
}
selector.selectedKeys(); // It's empty
selector.keys(); // Force get key by this will cause exception same as last one
So how to achive that ? How to interestOps in other thread ?
Related
I am curious why the code always tells me "key is not writable"? Is there anything with my code? Every time the socket has read something, I set the key to be interested in OP_WRITE, and test it whether it is writable. However, it always says not writable. I am totally a newbie in Java socket programming.
By the way, I don't close the client.
public final class DateServer {
private DateServer() {
throw new IllegalStateException("Instantiation not allowed");
}
public static void main(final String[] args) throws Exception {
try (final Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open();) {
InetSocketAddress hostAddress = new InetSocketAddress("127.0.0.1", 9999);
serverSocket.bind(hostAddress);
serverSocket.configureBlocking(false);
serverSocket.register(selector, serverSocket.validOps(), null);
while (true) {
int numSelectedKeys = selector.select();
if (numSelectedKeys > 0) {
handleSelectionKeys(selector.selectedKeys(), serverSocket);
}
}
}
}
private static void handleSelectionKeys(Set<SelectionKey> selectionKeys, ServerSocketChannel serverSocket) throws IOException {
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
while (selectionKeyIterator.hasNext()) {
SelectionKey key = selectionKeyIterator.next();
if (key.isAcceptable()) {
acceptClientSocket(key, serverSocket);
} else if (key.isReadable()) {
readRequest(key);
}
selectionKeyIterator.remove();
}
}
private static void acceptClientSocket(SelectionKey key, ServerSocketChannel serverSocket) throws IOException {
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
client.register(key.selector(), SelectionKey.OP_READ);
System.out.println("Accepted connection from client");
}
private static void readRequest(SelectionKey key) throws IOException {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
client.close();
} else {
System.out.println(String.format("Request data: %s", new String(buffer.array())));
int interestOps = 0;
interestOps |= SelectionKey.OP_WRITE;
key.interestOps(interestOps);
if (key.isWritable()){
System.out.println("key is writable");
} else {
System.out.println("key is not writable");
}
interestOps = 0;
interestOps |= SelectionKey.OP_READ;
key.interestOps(interestOps);
}
}
}
The interestOps only tell the selector what to select for next time. Setting OP_WRITE doesn't magically equip the selection key with the ability to predict the future. You would have to call select() again for OP_WRITE to actually get set as a result of this code.
But you don't need the selector's 'permission' to write to the channel. You just write, and only if the write count is zero do you need to worry about OP_WRITE, as per numerous answers on that topic here.
When you register your client, you only specify SelectionKey.OP_READ, so the channel is never ready for writing. (Look at the isWritable method inside Selectionkey.java)
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
In your case OP_WRITE was never registered and hence it didn't work.
Use this to make it work
client.register(key.selector(), SelectionKey.OP_READ | SelectionKey.OP_WRITE);
I'm writing a chat server in Java using Java NIO. The server accepts a connection without issue, but any time that select() returns > 0 after the first client, the server socket is always in the selected key-set even if there are no pending connections. Even if select() returns 1, the selected key-set will have 2 elements and include the server socket. This causes accept() to return null.
Any help would be greatly appreciated.
The main loop:
public void start() throws IOException {
Set<SelectionKey> keys;
Iterator<SelectionKey> keyIterator;
this.keepGoing = true;
while (keepGoing) {
int readyChannels = this.selector.select();
if (readyChannels == 0)
{
continue;
}
keys = this.selector.selectedKeys();
keyIterator = keys.iterator();
while (keyIterator.hasNext())
{
SelectionKey currentKey = keyIterator.next();
if (currentKey.isAcceptable())
{
addClient(currentKey);
}
if (currentKey.isReadable())
{
readSock(currentKey);
}
if (currentKey.isWritable())
{
// write data to the buffer and remove OP_WRITE
}
}
}
}
The server initialisation code:
public Server(int port) {
this.listenPort = port;
try
{
this.selector = Selector.open();
this.listenChannel = ServerSocketChannel.open();
this.listenChannel.socket().bind(new InetSocketAddress(this.listenPort), BACKLOG);
this.listenChannel.configureBlocking(false);
this.listenChannel.register(this.selector, SelectionKey.OP_ACCEPT);
}
catch (IOException e)
{
System.out.println("Server could not initialise: " + e.getMessage());
}
this.users = new HashMap<>();
}
The addClient method:
private void addClient(SelectionKey key) throws IOException {
ServerSocketChannel acceptSocket = (ServerSocketChannel) key.channel();
SocketChannel newClient = acceptSocket.accept();
SelectionKey clientKey;
// Set the new client to non-blocking mode and add to the selector
newClient.configureBlocking(false);
clientKey = newClient.register(this.selector, SelectionKey.OP_READ);
// Add a new key-user pair to the user list
this.users.put(clientKey, new User());
// Attach a buffer for reading the packets
clientKey.attach(new PacketBuffer(newClient));
}
You must call keyIterator.remove() after keyIterator.next(), or clear the selected key set at the end of the loop. The Selector doesn't remove keys from that set, it's up to you. But you also need to be aware that accept() can return null in non-blocking mode, and program defensively accordingly.
I have a server app. Java NIO
I have Runnable class - EventHandler - that process incoming messages. If message == "Bye" -> EventHandler close related SocketServer and SelectorKey
I have one Runnable object - Acceptor - that is activated on OP_ACCEPT events. It creates new SocketChannel and new EventHandler to process messages from this channel
I have a problem.
First client connect. Send messages. Disconnect. Everything is ok
After first client disconnected Second client connect. Here problem begins - Acceptor object isn't invoked, therefore SocketChannel and EventHandler are not created for new client.
What is wrong in my code? SocketChannel closed improperly?
I changed the code to fix the errors that were noted in the comments. Now it works fine
Reactor. Class with the main loop
public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocketChannel;
Reactor(int port) throws IOException {
//configure server socket channel
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.socket().bind(new InetSocketAddress(port));
this.serverSocketChannel.configureBlocking(false);
//start acceptor
this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT, new Acceptor(this.serverSocketChannel, this.selector));
}
public void run() {
System.out.println("Server is listening to port: " + serverSocketChannel.socket().getLocalPort());
try {
while (!Thread.currentThread().isInterrupted()) {
if (this.selector.select() > 0) {
Set<SelectionKey> selected = this.selector.selectedKeys();
for (SelectionKey selectionKey : selected) {
dispatch(selectionKey);
}
selected.clear(); //clear set (thanks to EJP for comment)
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable) (k.attachment());
if (r != null) {
r.run();
}
}
}
Acceptor
public class Acceptor implements Runnable {
final ServerSocketChannel serverSocketChannel;
final Selector selector;
public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
this.serverSocketChannel = serverSocketChannel;
this.selector = selector;
}
public void run() {
try {
SocketChannel socketChannel = this.serverSocketChannel.accept();
if (socketChannel != null) {
new EventHandler(this.selector, socketChannel);
System.out.println("Connection Accepted");
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
EventHandler
public class EventHandler implements Runnable {
EventHandler(Selector selector, SocketChannel socketChannel) throws IOException {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
this.selectionKey = this.socketChannel.register(selector, SelectionKey.OP_READ, this);
//selector.wakeup(); //we don't need to wake up selector (thanks to EJP for comment)
}
#Override
public void run() {
try {
if (this.state == EventHandlerStatus.READING) {
read();
} else if (this.state == EventHandlerStatus.SENDING) {
send();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Reading client message
*
* #throws IOException
*/
void read() throws IOException {
int readCount = this.socketChannel.read(this.input);
//check whether the result is equal to -1, and close the connection if it is (thanks to EJP for comment)
if(readCount == -1){
this.socketChannel.close();
System.out.println("Stream is closed. Close connection.");
return;
}
if (readCount > 0) {
processMessage(readCount);
}
if(this.clientMessage.equalsIgnoreCase("Bye")){
this.socketChannel.close();
//this.selectionKey.cancel(); //we don't need to cancel selectionKey if socketChannel is just closed (thanks to EJP for comment)
System.out.println("Client said Bye. Close connection.");
return;
}
this.state = EventHandler.Status.SENDING;
this.selectionKey.interestOps(SelectionKey.OP_WRITE); //mark that we interested in writing
}
/**
* Processing of the read message.
*
* #param readCount Number of bytes to read
*/
synchronized void processMessage(int readCount) {
this.input.flip();
StringBuilder sb = new StringBuilder();
sb.append(new String(Arrays.copyOfRange(input.array(), 0, readCount))); // Assuming ASCII (bad assumption but simplifies the example)
this.clientMessage = sb.toString().trim();
this.input.clear();
System.out.println("Client said: " + this.clientMessage);
}
/**
* Sending response to client
*
* #throws IOException
*/
void send() throws IOException {
System.out.println("Answer to client: " + this.clientMessage);
this.socketChannel.write(ByteBuffer.wrap((this.clientMessage + "\n").getBytes()));
this.state = EventHandler.Status.READING;
this.selectionKey.interestOps(SelectionKey.OP_READ); //mark that we interested in reading
}
//----------------------------------------------------------------------------------------------------------------------
// Fields
//----------------------------------------------------------------------------------------------------------------------
final SocketChannel socketChannel;
final SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
EventHandlerStatus state = EventHandler.Status.READING;
String clientMessage = "";
//----------------------------------------------------------------------------------------------------------------------
// Enum to mark current status of EventHandler
//----------------------------------------------------------------------------------------------------------------------
enum Status {
READING, SENDING
}
}
i'm trying to use NIO to build an efficient Socket TCP/IP server.
i have the main thread which accept connection and then add it to another thread which supposed to wait for messages from client and then read it.
when i'm using only one thread and one selector for all the operations it works great, but when i'm trying to make it works with 2 threads and 2 selectors the incoming connection accept is working but the reading is not, i think it because my selector is blocking the thread and therefor he's not aware that I've registered a new SocketChannel.
this is my Main thread:
public static void main(String[] args) {
try {
System.out.println("Who's Around Server Started!");
Selector connectionsSelector = null;
ServerSocketChannel server = null;
String host = "localhost";
int port = 80;
LiveConnectionsManager liveConnectionsManager =
new LiveConnectionsManager();
liveConnectionsManager.start();
connectionsSelector = Selector.open();
server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(host,port));
server.configureBlocking(false);
server.register(connectionsSelector, SelectionKey.OP_ACCEPT);
while (true) {
connectionsSelector.select();
Iterator<SelectionKey> iterator =
connectionsSelector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey incomingConnection = iterator.next();
iterator.remove();
if( incomingConnection.isConnectable()) {
((SocketChannel)incomingConnection.channel()).finishConnect();
}
if( incomingConnection.isAcceptable()){
acceptConnection(server.accept(), liveConnectionsManager);
}
}
}
} catch (Throwable e) {
throw new RuntimeException("Server failure: " + e.getMessage());
}
}
private static void acceptConnection(
SocketChannel acceptedConnection,
LiveConnectionsManager liveConnectionsManager ) throws IOException
{
acceptedConnection.configureBlocking(false);
acceptedConnection.socket().setTcpNoDelay(true);
System.out.println(
"New connection from: " + acceptedConnection.socket().getInetAddress());
liveConnectionsManager.addLiveConnection(acceptedConnection);
}
and this is my LiveConnectionsManager:
private Selector messagesSelector;
public LiveConnectionsManager(){
try {
messagesSelector = Selector.open();
} catch (IOException e) {
System.out.println("Couldn't run LiveConnectionsManager");
}
}
#Override
public void run() {
try {
System.out.println("LiveConnectionManager Started!");
while(true) {
messagesSelector.select();
Iterator<SelectionKey> iterator = messagesSelector.keys().iterator();
while (iterator.hasNext()){
SelectionKey newData = iterator.next();
iterator.remove();
if( newData.isReadable()){
readIncomingData(((SocketChannel)newData.channel()));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void addLiveConnection( SocketChannel socketChannel )
throws ClosedChannelException
{
socketChannel.register(messagesSelector, SelectionKey.OP_READ);
}
In my current project I notice that select() do not block as expected.
It do not block at all and return always, even when no IO was present. So I got a busy cpu.
The registration will always invoked by another thread, so I need the lock and the wakeup.
The doc says for selectNow():
Invoking this method clears the effect of any previous invocations of the wakeup method.
So I invoke the method at the end of each iteration. no succsess.
I found no example or explanation how to use selectNow for my purpose.
What is wrong with the code?
Here is my example code, so you can test this.
BTW: Another stackoverflow question was the rolemodel of my code.
EDIT: Example fixed! It works now.
import java.io.IOException;
import java.net.*;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;
public class Test implements Runnable {
ReentrantLock selectorLock = new ReentrantLock();
Selector selector;
boolean alive;
#Override
public void run() {
SelectionKey key;
Iterator<SelectionKey> keys;
alive = true;
try {
while (alive) {
selectorLock.lock();
selectorLock.unlock();
selector.select();
System.out.println("select() returned");
keys = selector.selectedKeys().iterator();
// handle each "event"
while (keys.hasNext()) {
key = keys.next();
// mark as handled
keys.remove();
// handle
handleKey(key);
}
//selector.selectNow(); // don't fix this
}
} catch ( IOException e ) {
e.printStackTrace();
}
}
private void handleKey(SelectionKey key)
throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if (key.isConnectable()) {
System.out.println("connecting");
if ( channel.finishConnect() ) {
key.interestOps(SelectionKey.OP_READ);
} else {
key.cancel();
}
} else if (key.isReadable()) {
System.out.println("reading");
// read and detect remote close
channel.read(ByteBuffer.allocate(64));
}
}
public void register(SelectableChannel channel, int ops, Object attachment)
throws ClosedChannelException {
selectorLock.lock();
try {
System.out.println("wakeup");
selector.wakeup();
channel.register(selector, ops, attachment);
} finally {
selectorLock.unlock();
}
}
public Test()
throws IOException {
selector = Selector.open();
}
public static void main(String[] args)
throws IOException {
Test t = new Test();
new Thread(t).start();
SocketAddress address = new InetSocketAddress("localhost", 8080);
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(address);
t.register(channel, SelectionKey.OP_CONNECT, "test channel attachment");
}
}
Don't register OP_READ until OP_CONNECT has fired and finishConnect() has returned 'true'. At that point you must deregister OP_CONNECT.
Similarly don't register channels for OP_WRITE until you have something to write. OP_WRITE is always ready except when the socket send buffer is full, so it should only be registered after you have detected that condition (write() returns zero), and you should de-register it immediately it fires (unless the condition happens again).
And finally OP_CONNECT and OP_WRITE are the same thing under the hood, which given what I've just said about OP_WRITE explains your selector spins.