Non-Blocking IO in java with logic - java

I am creating a server that will handle >1000 connections. I decided to go with non-blocking IO in my server. I found some code on the internet, which is basically an echo server. I think everything is fine, but I don't understand a few of the concepts in the server.
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
public class EchoServer {
private InetAddress addr;
private int port;
private Selector selector;
private Map<SocketChannel,List<byte[]>> dataMap;
public EchoServer(InetAddress addr, int port) throws IOException {
this.addr = addr;
this.port = port;
dataMap = new HashMap<SocketChannel,List<byte[]>>();
startServer();
}
private void startServer() throws IOException {
// create selector and channel
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// bind to port
InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
serverChannel.socket().bind(listenAddr);
serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
log("Echo server ready. Ctrl-C to stop.");
// processing
while (true) {
// wait for events
this.selector.select();
// wakeup to work on selected keys
Iterator keys = this.selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// this is necessary to prevent the same key from coming up
// again the next time around.
keys.remove();
if (! key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
}
else if (key.isReadable()) {
this.read(key);
}
else if (key.isWritable()) {
this.write(key);
}
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
// write welcome message
channel.write(ByteBuffer.wrap("Welcome, this is the echo server\r\n".getBytes("US- ASCII")));
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connected to: " + remoteAddr);
// register channel with selector for further IO
dataMap.put(channel, new ArrayList<byte[]>());
channel.register(this.selector, SelectionKey.OP_READ);
}
private void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(8192);
int numRead = -1;
try {
numRead = channel.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
}
if (numRead == -1) {
this.dataMap.remove(channel);
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connection closed by client: " + remoteAddr);
channel.close();
key.cancel();
return;
}
byte[] data = new byte[numRead];
System.arraycopy(buffer.array(), 0, data, 0, numRead);
log("Got: " + new String(data, "US-ASCII"));
// write back to client
doEcho(key, data);
}
private void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
Iterator<byte[]> items = pendingData.iterator();
while (items.hasNext()) {
byte[] item = items.next();
items.remove();
channel.write(ByteBuffer.wrap(item));
}
key.interestOps(SelectionKey.OP_READ);
}
private void doEcho(SelectionKey key, byte[] data) {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
pendingData.add(data);
key.interestOps(SelectionKey.OP_WRITE);
}
private static void log(String s) {
System.out.println(s);
}
public static void main(String[] args) throws Exception {
new EchoServer(null, 8989);
}
}
So For this code, I have a few questions. One, if I read 10 bytes, but I don't want to do anything until I read 100 bytes, how would I implement that? Also, say I only want to write when a counter reaches a certain number, how would I implement that non-blocking? The thing about this code is, is that it will echo no matter how big the bytebuffer is. How do I change that so it would only echo when it has 100 bytes? How can I write only if a counter is a certain size? Thanks!
Would putting an if(numRead < 100) {do rest} else {return} in the read method work for the first problem?
Also, would putting an if(counter > 100) {do rest} else{return} in the write method work for the second?

You have to code that part, basically you need to keep track of bytes read, keep adding bytes read to temporary buffer and once you reached your required limit, you can pass that buffer to your worker thread.
I'll recommend you to use netty it provides all the things which you are looking for out of the box.
Look at this link.
Hope this helps

Did channel with non blocking mode return -1 while reading?? For your question, You can set the bytebuffer limit:-
For example:-
ByteBuffer buff = ByteBuffer.allocate(1024);
buff.clear();
buff.limit(your_limit);//is this what you want??
while(buff.remaining>0&&channel.read(buff)); // if will reach till your limit only.
System.out.println(new String(buff.array()));
hope this help

Related

CastException in Nio server : SocketChannelImpl cannot be cast to ServerSocketChannel

i use pseudo code to describe the problem first,and i'll paste the whole code in the follow,which can run in the local.
1.selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
2.while(true){
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
3. if(!key.isAcceptable()){
continue;
}
4. ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
...
}
}
the exception occured in the step 4,then i do a check in the step 3,but it can not pass the acceptable check and go into a dead loop.
occasionally ,it can receive and response normally and i have not make any change,it's too strange to me.
here i paste the code and hope someone can help me. thanks.
package io.Nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import io.util.IOUtil;
public class NioServer extends Thread{
private int port;
private Selector selector;
private ServerSocketChannel serverChannel;
public NioServer(int port){
this.port = port;
}
#Override
public void run() {
try{
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch(IOException e){
IOUtil.close(serverChannel);
}
System.out.println("server start:" + port);
while(true){
try {
selector.select();
} catch (ClosedSelectorException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if(!key.isValid()){
key.cancel();
IOUtil.close(key.channel());
IOUtil.close(key.selector());
System.out.println(IOUtil.now() + "clear a invalid key.");
continue;
}
// i put a check here,if is not Acceptable,then continue, but it's a dead loop
if(!key.isAcceptable()){
System.out.println("not Acceptable");
continue;
}
try {
//Exception here: SocketChannelImpl cannot be cast to ServerSocketChannel
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
if(channel == null){
continue;
}
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
// if (key.isReadable()){
// System.out.println("not read");
// }
ByteBuffer buffer = ByteBuffer.allocate(1024);
if (channel.read(buffer) > 0) {
buffer.flip();
byte[] byteArray = new byte[buffer.remaining()];
buffer.get(byteArray);
String expression = new String(byteArray, "UTF-8");
System.out.println(IOUtil.now() + "receive request:" + expression);
String result = null;
response(channel, result);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void shutdown(){
IOUtil.close(selector);
IOUtil.close(serverChannel);
}
private void response(SocketChannel channel, String response) throws IOException {
response = "hello response";
System.out.println(IOUtil.now() + "send response:"+ response);
byte[] bytes = response.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
channel.write(buffer);
}
public static void main(String[] args) {
new NioServer(IOUtil.DEFAULT_PORT).start();
}
}
package io.Nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import io.util.IOUtil;
public class NioClient extends Thread{
private volatile CountDownLatch connectLatch;
private String ip;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private NioClient(String ip, int port) {
this.ip = ip;
this.port = port;
connectLatch = new CountDownLatch(1);
}
public static NioClient open(String ip, int port){
NioClient client = new NioClient(ip,port);
client.start();
return client;
}
#Override
public void run(){
try{
long begin = System.currentTimeMillis();
System.out.println(IOUtil.now() + "start client");
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(ip,port));
while(!socketChannel.finishConnect()){
yield();
}
System.out.println(IOUtil.now() + "cost time:" + (System.currentTimeMillis() - begin) + "ms");
connectLatch.countDown();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while(true){
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = it.next();
if(!key.isValid() || !key.isReadable()){
continue;
}
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
if(channel.read(buffer) > 0){
buffer.flip();
byte[] byteArray = new byte[buffer.remaining()];
buffer.get(byteArray);
String response = new String(byteArray,"UTF-8");
System.out.println(IOUtil.now() + "receive response:" + response);
}
}
}
}catch(IOException e){
e.printStackTrace();
}
}
public void request(String request) {
try {
connectLatch.await();
} catch (InterruptedException e) {
System.out.println(IOUtil.now() + "interrupted" + e.getMessage());
}
try {
byte[] bytes = request.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.register(selector, SelectionKey.OP_READ);
//TODO
System.out.println(IOUtil.now() + "send request:" + request);
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(final String[] args) throws InterruptedException {
NioClient client = NioClient.open(IOUtil.DEFAULT_HOST, IOUtil.DEFAULT_PORT);
client.request("hello");
// while(true){
// sleep(500);
// String request = IOUtil.buileRequest(1991);
// client.request(request);
// }
}
}
package io.util;
import java.io.Closeable;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class IOUtil {
public static final String DEFAULT_HOST = "127.0.0.1";
public static final int DEFAULT_PORT = 8080;
public static final String operators[] = {"+", "-", "*", "/"};
public static final int CLIENNT_NUM = 10;
public static final boolean CLIENT_CLOSEABLE = true;
public static String now(){
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS ").format(new Date());
}
public static String buileRequest(int seed){
Random random = new Random(seed);
return random.nextInt(10) + IOUtil.operators[random.nextInt(4)] + (random.nextInt(10) + 1);
}
public static void close(Closeable io) {
if (io != null) {
try {
io.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
The only way the exception you describe could occur is if you attempt that typecast on a channel which is not a ServerSocketChannel, which could happen if its key is ready but not 'acceptable'. Clearly you didn't have your step 2 when you got this exception, so you processed a 'readable' channel as though it was an 'acceptable' channel.
So the code you posted doesn't actually exhibit that problem, but it does have a large number of others. You don't need to close the selector just because a key is invalid, and if it is invalid it is already cancelled, so you don't need to cancel it, and anyway closing the channel cancels the key. Why aren't you interested in OP_READ/isReadable() on the channels you have accepted and registered for OP_READ? And why are you trying to read from a channel you have just accepted, without waiting for OP_READ?
Throw it away and have a good look at the Java NIO tutorial.
I do want to comment on one particular piece of nonsense in the client:
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(ip,port));
while(!socketChannel.finishConnect()){
yield();
}
Here you are performing the equivalent of a blocking-mode connect in non-blocking mode. It can all be replaced by:
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(ip,port));
socketChannel.configureBlocking(false);
Then:
socketChannel.register(selector, SelectionKey.OP_CONNECT);
Here you are registering for an event that has already happened. Truly bizarre. You will never get OP_CONNECT from an already-connected socket. Remove.

Not getting OP_READ in my client

I am new to NIO and NIO2. I have been playing with an Echo server sample code, which works flawlessly. I started to write a similar client. My goal is to have multiple client socket connections running off the one main-thread.
I do get the OP_CONNECT, then after that the selector does not return and times out from the:
while(Selector.select(10000)>0) {
....
}
If I ignore the selector and start reading the data with socketChannel.read(...), I can read the data. So, the data is ready to be read, but I just do not get Selector.select(10000) to return with some keys.
Here is the complete source code and I really would appreciate any help:
package com.maker.webscraping.nio;
import java.io.IOException;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EchoClient2 {
private static final Logger logger = LoggerFactory.getLogger(EchoClient2.class);
private static final Map<SocketChannel, EchoClient2> mapClients = new ConcurrentHashMap<>();
private static final int DEFAULT_PORT = 5555;
private static final String IP = "127.0.0.1";
private final int clientID;
private final ByteBuffer buffer;
private final SocketChannel socketChannel;
private final CharsetDecoder decoder;
public int getClientID() {
return clientID;
}
public ByteBuffer getBuffer() {
return buffer;
}
public CharsetDecoder getDecoder() {
return decoder;
}
//private static Selector selector = null;
public static void main(String[] args) {
Selector selector = null;
try {
selector = Selector.open();
if (!selector.isOpen())
throw new RuntimeException("Selector closed!");
EchoClient2[] clients = new EchoClient2[2];
clients[0] = new EchoClient2(0, selector);
// wait for incomming events
while (selector.select(10000)>0) {
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
try (SocketChannel socketChannel = (SocketChannel) key.channel()) {
if (key.isConnectable()) {
// connected
logger.info("Client:{} connected!", clients[0].getClientID());
key.interestOps(SelectionKey.OP_READ); // <-- desprete tries
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); // <-- desprete tries
logger.info("R:{}, W:{}, C:{}, validOps:{}", SelectionKey.OP_READ, SelectionKey.OP_WRITE, SelectionKey.OP_CONNECT, socketChannel.validOps());
// close pending connections
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
read(key,selector); // <-- desprete tries
if (key.isReadable()) {
read(key,selector);
}// else if (key.isWritable()) {
// this.writeOP(key);
//}
}
} catch (IOException e) {
logger.error("SocketChannel Exception!", e);
}
}
}
} catch (IOException e) {
logger.error("Selector IOException!", e);
} finally {
try {
selector.close();
} catch (IOException e) {}
}
}
public EchoClient2(int clientID, Selector selector) throws IOException {
this.clientID = clientID;
buffer = ByteBuffer.allocateDirect(2 * 1024);
Charset charset = Charset.defaultCharset();
decoder = charset.newDecoder();
// if (selector==null)
// selector = Selector.open();
socketChannel = SocketChannel.open();
if ((socketChannel.isOpen()) && (selector.isOpen())) {
// configure non-blocking mode
socketChannel.configureBlocking(false);
// set some options
socketChannel.setOption(StandardSocketOptions.SO_RCVBUF,
128 * 1024);
socketChannel.setOption(StandardSocketOptions.SO_SNDBUF,
128 * 1024);
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,
true);
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//socketChannel.register(selector, SelectionKey.OP_CONNECT);
// connect to remote host
socketChannel.connect(new java.net.InetSocketAddress(IP, DEFAULT_PORT));
// add it to the map
mapClients.put(socketChannel, this);
} else
throw new RuntimeException("Channel or Selector closed!");
}
// isReadable returned true
private static int read(SelectionKey key, Selector selector) {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
EchoClient2 client = mapClients.get(socketChannel);
ByteBuffer buffer = client.getBuffer();
buffer.clear();
int numRead = -1;
try {
numRead = socketChannel.read(buffer);
} catch (IOException e) {
System.err.println("Cannot read error!");
}
if (numRead == -1) {
mapClients.remove(socketChannel);
System.out.println("Connection closed by: " + socketChannel.getRemoteAddress());
socketChannel.close();
key.cancel();
return 1;
}
if (numRead == 0)
throw new RuntimeException("numRead is 0!!!");
buffer.flip();
CharBuffer charBuffer = client.getDecoder().decode(buffer);
System.out.println("server says:" + charBuffer.toString());
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
int r = new Random().nextInt(100);
//if (r == 50) {
// System.out.println("50 was generated! Close the socket channel!");
// return 1;
//}
ByteBuffer randomBuffer = ByteBuffer.wrap("Random number:".concat(String.valueOf(r))
.getBytes("UTF-8"));
socketChannel.write(randomBuffer);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); // <-- desprete tries
} catch (IOException e) {
logger.error("IOException inside read!", e);
}
return 0;
}
}
Thanks,
while (selector.select(10000)>0)
This code is already wrong. It will stop selecting the first time a select timeout occurs. It should be:
while (selector.isOpen())
{
if (selector.select(10000) > 0)
{
// ...
}
}
There are other problems.
You should only assume the connection is complete if finishConnect() returns true.
Reading when the connection is complete isn't valid. You should only read when key.isReadable() is true. You need a separate test for that, not one piggybacked onto the isConnectable() case.
Similarly you need a separate case for isWritable().
You shouldn't register OP_WRITE until you have something to write, or better still after you get a zero-length return when you try to write something. OP_WRITE signals that the socket send buffer isn't full. It is almost always true.
You need to call keys.remove() after keys.next(), otherwise you will keep getting the same selected keys over and over again. The selector doesn't clear the selected-key set.
Your comment // close pending connections is completely incorrect.
The following isConnectionPending() call is redundant. Of course it's pending, that's why you got the OP_CONNECT.

java nio cannot read after register a write operation

I got a problem when using Java nio to do networking.
I start a server in a thread and register read operationsin the selector. and another thread connect to the server.
client write a byte to the server
server reads the datas and back a byte to the client
client get the response and write a byte to the server again.
server loop step 2.
but server cannot do the step 4 successfully because I can't get any read Key from selector any more. why ?? I have registered it before, and client has written data.
there is my code below :
package test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class JavaNIO {
public static void main(String[] args) throws Exception {
final Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(5555));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
new Thread() {
public void run() {
while(true) {
try {
long count = selector.select();
if(count == 0) continue;
Iterator<SelectionKey> itKey = selector.selectedKeys().iterator();
while(itKey.hasNext()) {
SelectionKey key = itKey.next();
itKey.remove();
if(key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
SocketChannel socket = channel.accept();
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()) {
ByteBuffer buffer= ByteBuffer.allocate(1);
SocketChannel channel = (SocketChannel)key.channel();
while(true) {
buffer.flip();
int len = channel.read(buffer);
if(len == 0) break;
buffer.clear();
}
channel.read(buffer);
channel.register(selector, SelectionKey.OP_WRITE, null);
}
if(key.isWritable()) {
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap("ce".getBytes()));
key.cancel();
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
try {
Socket socket = new Socket("localhost", 5555);
byte[] reads = new byte[1024];
for(int i = 0; i < 1000; i++) {
socket.getOutputStream().write(new byte[]{1});
socket.getInputStream().read(reads);
System.out.println(new String(reads));
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
You re-registered the channel for OP_WRITE, so its interestOps ceased to include OP_READ. If you want both, change accordingly. 'Or' them together.

Java NIO client

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
public class EchoServer {
private InetAddress addr;
private int port;
private Selector selector;
private Map<SocketChannel,List<byte[]>> dataMap;
public EchoServer(InetAddress addr, int port) throws IOException {
this.addr = addr;
this.port = port;
dataMap = new HashMap<SocketChannel,List<byte[]>>();
startServer();
}
private void startServer() throws IOException {
// create selector and channel
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// bind to port
InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
serverChannel.socket().bind(listenAddr);
serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
log("Echo server ready. Ctrl-C to stop.");
// processing
while (true) {
// wait for events
this.selector.select();
// wakeup to work on selected keys
Iterator keys = this.selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// this is necessary to prevent the same key from coming up
// again the next time around.
keys.remove();
if (! key.isValid()) {
continue;
}
if (key.isAcceptable()) {
this.accept(key);
}
else if (key.isReadable()) {
this.read(key);
}
else if (key.isWritable()) {
this.write(key);
}
else if (key.isConnectable()) {
this.doConnect(key);
}
}
}
}
private void doConnect(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.finishConnect()) {
/* success */
System.out.println("Connected");
} else {
/* failure */
System.out.println("failure");
}
}
public void connect(String hostname, int port) throws IOException {
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(hostname,port));
clientChannel.register(selector,SelectionKey.OP_CONNECT);
clientChannel.write(ByteBuffer.wrap(("$Hello "+UserInfo[0]+"|").getBytes("US-ASCII")));
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
// write welcome message
channel.write(ByteBuffer.wrap("Welcome, this is the echo server\r\n".getBytes("US-ASCII")));
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connected to: " + remoteAddr);
dataMap.put(channel, new ArrayList<byte[]>()); // register channel with selector for further IO
channel.register(this.selector, SelectionKey.OP_READ);
}
private void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(8192);
int numRead = -1;
try {
numRead = channel.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
}
if (numRead == -1) {
this.dataMap.remove(channel);
Socket socket = channel.socket();
SocketAddress remoteAddr = socket.getRemoteSocketAddress();
log("Connection closed by client: " + remoteAddr);
channel.close();
key.cancel();
return;
}
byte[] data = new byte[numRead];
System.arraycopy(buffer.array(), 0, data, 0, numRead);
log("Got: " + new String(data, "US-ASCII"));
doEcho(key, data); // write back to client
}
private void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
Iterator<byte[]> items = pendingData.iterator();
while (items.hasNext()) {
byte[] item = items.next();
items.remove();
channel.write(ByteBuffer.wrap(item));
}
key.interestOps(SelectionKey.OP_READ);
}
private void doEcho(SelectionKey key, byte[] data) {
SocketChannel channel = (SocketChannel) key.channel();
List<byte[]> pendingData = this.dataMap.get(channel);
pendingData.add(data);
key.interestOps(SelectionKey.OP_WRITE);
}
private static void log(String s) {
System.out.println(s);
}
public static void main(String[] args) throws Exception {
new EchoServer(null, 8989);
}
}
The program works with incoming connections. But when I make an outgoing connection, the program does not work. I need to make some connections in a row through the connect (String hostname, int port) and receive data in a method read(). The program stops working on the line clientChannel.register(...)
You need to check for a connectable key, e.g.
if (key.isConnectable()) {
this.doConnect(key);
}
...
private void doConnect(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.finishConnect()) {
/* success */
} else {
/* failure */
}
}
Use SocketChannel.finishConnect to determine whether the connection was established successfully.
This is my NIO Client example Ive been using. It gives a timed out open/write/read functions and is suitable for request-response messaging.
Tricky part is always how do parties recognize a packet is fully received. This example assumes
Server gets one_line_command+newline (client->server packet)
Client receives 1..n lines with ">>" terminator line without trailing newline in a terminator line (server->client packet)
you could specify terminator to be ">>\n" but readUntil needs small fix in a newline parser
You could write 4-byte length header, fixed size packet splitter or delimiter 0x27 byte but make sure it cannot be a value of data payload. NIO read() or write() never assumes you receive a full packet in one call. Or may read two or more packet bytes in one read() buffer. It is up to us make a packet parser not loosing bytes.
import java.util.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
public class DPSocket {
private boolean debug;
private String host;
private int port;
private String charset;
private ByteArrayOutputStream inBuffer;
private ByteBuffer buf;
private Selector selector;
private SocketChannel channel;
public DPSocket(String host, int port, String charset) {
this.charset = charset==null || charset.equals("") ? "UTF-8" : charset;
this.host = host;
this.port = port;
}
public boolean isDebug() { return debug; }
public void setDebug(boolean b) { debug=b; }
public void open(long timeout) throws IOException {
selector = Selector.open();
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT);
channel.connect(new InetSocketAddress(host, port));
inBuffer = new ByteArrayOutputStream(1024);
buf = ByteBuffer.allocate(1*1024);
long sleep = Math.min(timeout, 1000);
while(timeout > 0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isConnectable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect();
channel.configureBlocking(false);
if (debug) System.out.println("finishConnect");
return; // we are ready to receive bytes
}
}
}
throw new IOException("Connection timed out");
}
public void close() {
try { if(channel!=null) channel.close(); } catch(Exception ex) { }
try { if(selector!=null) selector.close(); } catch(Exception ex) { }
inBuffer=null;
buf=null;
}
public void write(String data, long timeout) throws IOException {
write(data.getBytes(charset), timeout);
}
public void write(byte[] bytes, long timeout) throws IOException {
ByteBuffer outBuffer = ByteBuffer.wrap(bytes);
channel.register(selector, SelectionKey.OP_WRITE);
long sleep = Math.min(timeout, 1000);
while(timeout > 0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isWritable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
if (debug) System.out.println("write remaining="+outBuffer.remaining());
channel.write(outBuffer);
if (debug) System.out.println("write remaining="+outBuffer.remaining());
if (outBuffer.remaining()<1)
return;
}
}
throw new IOException("Write timed out");
}
public List<String> readUntil(String terminator, long timeout, boolean trimLines) throws IOException {
return readUntil(new String[]{terminator}, timeout, trimLines);
}
public List<String> readUntil(String[] terminators, long timeout, boolean trimLines) throws IOException {
List<String> lines = new ArrayList<String>(12);
inBuffer.reset();
// End of packet terminator strings, line startsWith "aabbcc" string.
byte[][] arrTerminators = new byte[terminators.length][];
int[] idxTerminators = new int[terminators.length];
for(int idx=0; idx < terminators.length; idx++) {
arrTerminators[idx] = terminators[idx].getBytes(charset);
idxTerminators[idx] = 0;
}
int idxLineByte=-1;
channel.register(selector, SelectionKey.OP_READ);
long sleep = Math.min(timeout, 1000);
while(timeout>0) {
if (selector.select(sleep) < 1) {
timeout-=sleep;
continue;
}
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid() || !key.isReadable()) continue;
SocketChannel channel = (SocketChannel)key.channel();
buf.clear();
int len = channel.read(buf);
if (len == -1) throw new IOException("Socket disconnected");
buf.flip();
for(int idx=0; idx<len; idx++) {
byte cb = buf.get(idx);
if (cb!='\n') {
idxLineByte++;
inBuffer.write(cb);
for(int idxter=0; idxter < arrTerminators.length; idxter++) {
byte[] arrTerminator = arrTerminators[idxter];
if (idxLineByte==idxTerminators[idxter]
&& arrTerminator[ idxTerminators[idxter] ]==cb) {
idxTerminators[idxter]++;
if (idxTerminators[idxter]==arrTerminator.length)
return lines;
} else idxTerminators[idxter]=0;
}
} else {
String line = inBuffer.toString(charset);
lines.add(trimLines ? line.trim() : line);
inBuffer.reset();
idxLineByte=-1;
for(int idxter=0; idxter<arrTerminators.length; idxter++)
idxTerminators[idxter]=0;
}
}
}
}
throw new IOException("Read timed out");
}
// **************************
// *** test socket client ***
// **************************
public static void main(String[] args) throws Exception {
String NEWLINE = "\n";
int TIMEOUT=5000;
DPSocket dps = new DPSocket("myserver.com", 1234, "UTF-8");
dps.setDebug(true);
try {
List<String> lines;
dps.open(15000);
dps.write("Command1 arg1 arg2"+NEWLINE, TIMEOUT);
lines = dps.readUntil(">>", TIMEOUT, true);
dps.write("Command2 arg1 arg2"+NEWLINE, TIMEOUT);
lines = dps.readUntil(">>", TIMEOUT, true);
} catch (Exception ex) {
String msg = ex.getMessage();
if (msg==null) msg = ex.getClass().getName();
if (msg.contains("timed out") || msg.contains("Invalid command ")) {
System.out.println("ERROR: " + ex.getMessage());
} else {
System.out.print("ERROR: ");
ex.printStackTrace();
}
} finally {
dps.close();
}
}
}

Infinite loop with Selector even there's no connection from client

I'm new with Java NIO, after reading some tutorials, I've tried my own to write a simple NIO server and client.
My server just does a simple thing is listen from client and print to console, and the client just connects to server and send to it 3 messages "Hello".
The problem is my server listens and works well with the 3 messages, after that it should be blocked and continue listening, but it does not, there's no blocking, it runs it while loop infinitely. Here's my server and client:
Server
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String args[]) throws Exception {
// Create the server socket channel
ServerSocketChannel server = ServerSocketChannel.open();
// nonblocking I/O
server.configureBlocking(false);
// host-port 8000
server.socket().bind(new InetSocketAddress(8000));
System.out.println("Server actives at port 8000");
// Create the selector
Selector selector = Selector.open();
// Recording server to selector (type OP_ACCEPT)
server.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
// Get keys
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
// print
System.out.println("[ " + keys.size() + " ]");
// For each keys...
while (i.hasNext()) {
SelectionKey key = (SelectionKey) i.next();
// Remove the current key
i.remove();
// if isAccetable = true
// then a client required a connection
if (key.isAcceptable()) {
// get client socket channel
SocketChannel client = server.accept();
// Non Blocking I/O
client.configureBlocking(false);
// recording to the selector (reading)
client.register(selector, SelectionKey.OP_READ);
continue;
}
// if isReadable = true
// then the server is ready to read
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
// Read byte coming from the client
int BUFFER_SIZE = 1024;
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
client.read(buffer);
} catch (Exception e) {
// client is no longer active
e.printStackTrace();
}
// Show bytes on the console
buffer.flip();
Charset charset = Charset.forName("ISO-8859-1");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println("[" + charBuffer.toString() + "]");
}
}
}
}
}
And here's my client:
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Client {
public static void main(String args[]) throws Exception {
// Create client SocketChannel
SocketChannel client = SocketChannel.open();
// nonblocking I/O
client.configureBlocking(false);
// Connection to host port 8000
client.connect(new java.net.InetSocketAddress("127.0.0.1", 8000));
// Create selector
Selector selector = Selector.open();
// Record to selector (OP_CONNECT type)
SelectionKey clientKey = client.register(selector,
SelectionKey.OP_CONNECT);
int counter = 0;
boolean chk = true;
// Waiting for the connection
while (selector.select(500) > 0 && chk) {
// Get keys
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
// For each key...
while (i.hasNext() && chk) {
SelectionKey key = (SelectionKey) i.next();
// Remove the current key
i.remove();
// Get the socket channel held by the key
SocketChannel channel = (SocketChannel) key.channel();
// Attempt a connection
if (key.isConnectable()) {
// Connection OK
System.out.println("Server Found");
// Close pendent connections
if (channel.isConnectionPending())
channel.finishConnect();
// Write continuously on the buffer
ByteBuffer buffer = null;
for (;chk;counter++) {
Thread.sleep(1000);
buffer = ByteBuffer.wrap(new String(" Client ").getBytes());
channel.write(buffer);
buffer.clear();
if (counter == 2)
{
chk = false;
client.close();
}
}
}
}
}
}
}
Anyone can explain what is wrong with my code?
Thanks in advance.
You are probably getting an endless stream of EOS-s from the accepted socket channel. You are ignoring the result of read(). You must at least check it for -1 and if so close the channel.
NIO socket apis are non-blocking. The selector returns the keys that are ready for operation. In case there is nothing ready then it will just keep looping. This is the expected behaviour.
no blocking because of
server.configureBlocking(false);
in Server.main()

Categories