36808

Java NIO Server/Client Chat App - sending data only by closing the socket

friends! I'm new to Java NIO and I'm currently trying to make a non-blocking chat app. The client connects to the server without problem. The client writes a message or few messages to the server but the server starts reading the messages only when the Socket connection is closed from the client code, so a SocketChannel (or only Socket) must be created and closed in the client code for every message - this doesn't seems to me right. I've tried the client side with simple Java I/O and also with NIO Selector. Same problem - the server starts to read only when the SocketChannel or the Socket is closed from client. Can somebody please tell me the proper way of doing such non blocking connections or show me the error in my logic... Thank You very much!

This is the server code:

public class NIOServer implements Runnable { @Override public void run() { try { runServer(); } catch (IOException e) { e.printStackTrace(); } } private void runServer() throws IOException { ServerSocketChannel server = ServerSocketChannel.open(); server.socket().bind(new InetSocketAddress(8080)); server.configureBlocking(false); Selector selector = Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyChannels = selector.selectNow(); if(readyChannels==0){ continue; } System.out.println("Ready channels: "+readyChannels); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); keyIterator.remove(); if(key.isAcceptable()){ ServerSocketChannel acceptableServer = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); if(client!=null){ System.out.println("Client accepted!"); client.configureBlocking(false); SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); } } if (key.isReadable()) { read(key); } /*if(key.isConnectable()){ System.out.println("connectable"); } if(key.isWritable()){ //System.out.println("writable"); }*/ } } } public void read(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel)key.channel(); channel.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(100); buffer.clear(); int bytesRead = channel.read(buffer); while(bytesRead>0){ System.out.println("Read bytes: "+ bytesRead); bytesRead=channel.read(buffer); if(bytesRead==-1){ channel.close(); key.cancel(); } buffer.flip(); while(buffer.hasRemaining()){ System.out.print((char)buffer.get()); } } //key.cancel(); //channel.close(); }

}

Client with NIO Selector:

public class NIOSelectorClient implements Runnable{ private Selector selector; @Override public void run() { try { startClient(); } catch (IOException e) { e.printStackTrace(); } } public void startClient() throws IOException { SocketChannel socketChannel= openConnection(); selector = Selector.open(); socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE); while(!Thread.interrupted()) { int readyChannels = selector.selectNow(); if(readyChannels==0) { continue; } Set<SelectionKey> keySet = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keySet.iterator(); while(keyIterator.hasNext()) { SelectionKey currentKey = keyIterator.next(); keyIterator.remove(); if(!currentKey.isValid()) { continue; } if(currentKey.isConnectable()) { System.out.println("I'm connected to the server!"); handleConnectable(currentKey); } if(currentKey.isWritable()){ handleWritable(currentKey); } } } } private void handleWritable(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(100); Scanner scanner = new Scanner(System.in); System.out.println("Enter message to server: "); String output = scanner.nextLine(); buffer.put(output.getBytes()); buffer.flip(); //while(buffer.hasRemaining()) { channel.write(buffer); //} System.out.println("Message send"); buffer.clear(); channel.close(); key.cancel(); } private void handleConnectable(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); if(channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); } private static SocketChannel openConnection() throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); socketChannel.configureBlocking(false); while(!socketChannel.finishConnect()) { System.out.println("waiting connection...."); } return socketChannel; }

}

And this is the non-NIO cliet:

public class NIOClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8080); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); while(socket.isConnected()) { //synchronized (socket) { writeMessage(socket,writer); //readServerMessage(socket); //} } } public static void writeMessage(Socket socket, BufferedWriter writer) throws IOException { Scanner scanner = new Scanner(System.in); System.out.println("Enter message: "); String output = "Client 1: " + scanner.nextLine(); writer.write(output); writer.flush(); //writer.close(); } public static void readServerMessage(Socket socket) throws IOException { }

}

Answer1:

Your code suffers from the usual raft of NIO mistakes:

public class NIOServer implements Runnable { private void runServer() throws IOException { ServerSocketChannel server = ServerSocketChannel.open(); server.socket().bind(new InetSocketAddress(8080)); server.configureBlocking(false); Selector selector = Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyChannels = selector.selectNow();

You are selecting without a sleep. If there are no ready channels this loop will smoke the CPU. Use a timeout, even a short one.

SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);

You should not register for OP_WRITE unless you've already written something and got a short return value.

public void read(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel)key.channel(); channel.configureBlocking(false);

The channel is already in non-blocking mode. You put it there when you accepted it. You couldn't have selected on it unless it was in non-blocking mode. Remove.

ByteBuffer buffer = ByteBuffer.allocate(100); buffer.clear();

The buffer is already clear. You just created it. Remove.

int bytesRead = channel.read(buffer); while(bytesRead>0){ System.out.println("Read bytes: "+ bytesRead); bytesRead=channel.read(buffer); if(bytesRead==-1){ channel.close(); key.cancel();

Closing the channel cancels the key. You don't need both. Remove the cancel.

//key.cancel(); //channel.close();

Remove. Don't leave dead code lying around to confuse future readers.

Client with NIO Selector:

public class NIOSelectorClient implements Runnable{ private Selector selector; public void startClient() throws IOException { SocketChannel socketChannel= openConnection(); selector = Selector.open(); socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);

See above.

while(!Thread.interrupted()) { int readyChannels = selector.selectNow();

See above.

if(!currentKey.isValid()) { continue; }

Very good but you need this test before every other one below, e.g. currentKey.isValid() && currentKey.isReadable(), because a prior handler may have closed the channel or cancelled the key. Same applies in the server code.

if(currentKey.isConnectable()) { System.out.println("I'm connected to the server!"); handleConnectable(currentKey); } if(currentKey.isWritable()){ handleWritable(currentKey); }

You never handle isReadable() in the client. Don't you expect any input?

private void handleWritable(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(100); Scanner scanner = new Scanner(System.in); System.out.println("Enter message to server: "); String output = scanner.nextLine();

Here you are blocking the entire client including all its SocketChannels waiting for the user to enter some input. This is very poor design.

buffer.clear();

You don't need this. You're about to release the buffer as a local variable. You're done with it.

channel.close();

You're closing the channel after one write? Why?

key.cancel();

Closing the channel cancels the key. You don't need both. You don't need this. Remove.

private void handleConnectable(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); if(channel.isConnectionPending()) { channel.finishConnect();

finishConnect() can return false, in which case you should do nothing further in this method.

channel.configureBlocking(false);

The channel is already in blocking mode. Otherwise you couldn't have got here. Remove.

channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); }

See above re OP_WRITE.

private static SocketChannel openConnection() throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); socketChannel.configureBlocking(false); while(!socketChannel.finishConnect()) { System.out.println("waiting connection...."); }

Remove this loop. That's what OP_CONNECT is for. You are keeping a dog and barking yourself. If you want not to proceed out of here until the connection is complete, do it in blocking mode. Instead of just smoking the CPU.

And this is the non-NIO cliet:

public class NIOClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8080); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); while(socket.isConnected()) {

The socket is connected. You connected it when you constructed it. It stays that way. isConnected() is not a valid test for peer disconnection.

Recommend

  • how to switch blocking mode of socketchannel?
  • find specific file type from folder and its sub folder
  • Push Notification not proper - Android
  • Index of vector iterator
  • How can I change config file path in Codeigniter?
  • memcache won't store key/value because the value is too big
  • Intermediate and return values in continuation-passing style
  • Mule ESB connecting to RabbitMQ
  • C#: Decoding JPEG images with 12-bit precision using Silverlight FJCore library?
  • Fastest way to read a text file of strings line by line [duplicate]
  • Convert the http response body to JSON format using Unirest C#
  • java.lang.NullPointerException: No FileItemFactory has been set
  • How to send image as base64 string in JSON using HTTP POST in Android?
  • Reading files from Apache Spark textFileStream
  • check if numbers have the same sign
  • EULA Accept Bash Script
  • groupby in pandas with different functions for different columns
  • Sorting a HashMap, while keeping duplicates
  • input type=“file” accept=“image/*” doesn't work in phone gap?
  • jquery draggable stop event
  • Multiple sockets for clients to connect to
  • Google OAuth2 for an web application hosted behind NAT (intranet server without public IP)
  • Delete std::shared_ptr without destroying the managed object?
  • Simplify where clause with repeated associated type restrictions
  • HttpURLConnection Closing IO Streams
  • Yii2: Finding file and getting path in a directory tree
  • Clear activity stack before launching another activity
  • Angular2 Response for preflight is invalid (redirect) from some GET requests
  • How do I configure context broker accept post requests from my remote sensor?
  • Regex thinks I'm nesting, but I'm not
  • What is the “return” in scheme?
  • How to disable jQuery.jplayer autoplay?
  • Easiest way to encapsulate a HTML5 webpage into an android app?
  • Busy indicator not showing up in wpf window [duplicate]
  • costura.fody for a dll that references another dll
  • Observable and ngFor in Angular 2
  • How to Embed XSL into XML
  • UserPrincipal.Current returns apppool on IIS
  • Conditional In-Line CSS for IE and Others?
  • java string with new operator and a literal