Got some questions about java non nonblocking socket.
Unplug the client's LAN cable and close the client's SocketChannel if there is no heartbeat message for 5 seconds in Timer thread.
private void startSendHeartBeatMessage() {
new Timer().scheduleAtFixedRate(new TimerTask() {
#SneakyThrows
#Override
public void run() {
if (connections.size() != 0) {
LocalDateTime now = LocalDateTime.now();
Iterator<MessageClient> iterator = connections.iterator();
while (iterator.hasNext()) {
MessageClient client = iterator.next();
if (client.isClientAlive(now)) {
// got a heartbeat 5 seconds ago
client.setData("heartbeat".getBytes());
SelectionKey key = client.getSocketChannel().keyFor(selector);
key.interestOps(SelectionKey.OP_WRITE); // send heartbeat to client
} else {
// remove client and close socketChannel
log.error("time out");
client.getSocketChannel().close();
iterator.remove();
}
}
}
selector.wakeup();
}
}, 0, 1000);
}
Of course, channel close is called because the lan cable is unplugged.
private void startServer() {
executorService.submit(() -> {
try {
while (true) {
if (selector.select(1000) > 0) { // wait for event
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
MessageClient client = (MessageClient) key.attachment();
client.receive(key, selector);
} else if (key.isWritable()) {
MessageClient client = (MessageClient) key.attachment();
client.send(key, selector);
}
}
}
}
} catch (IOException e) {
stopServer();
}
});
startSendHeartBeatMessage();
}
After client's socketChannel closed
Timer's selector.wakeup() and selector.select(1000) is not wakeup blocking and didn't catch any event in SelectionKey
But the client prints out that the connection and message transmission were successful.
As a result of checking, KQueueSelectorImpl wakeup() is called and interruptTriggered always true when the lan plug was unplugged and the socketChannel was closed.
/**
* KQueue based Selector implementation for macOS
*/
class KQueueSelectorImpl extends SelectorImpl {
public Selector wakeup() {
synchronized (interruptLock) {
if (!interruptTriggered) {
try {
IOUtil.write1(fd1, (byte)0);
} catch (IOException ioe) {
throw new InternalError(ioe);
}
interruptTriggered = true;
}
}
return this;
}
}
Not unplugging the LAN cable
Closing the client via cntr + c allows reconnection very normally.
I really don't know the internal working structure of NIO. Please help me
The execution environment runs on MacBook's and Spring Boot 2.
Thanks for reading.
Related
I have to build a JAVA Nio Server Application in JBoss to read data from a 10-200 Sensor Boxes. They open a stream and send data to me all the time. The comunication is bidirectional. Now, sometimes it can happen, that these Boxes (or the server) have some internal error. To detect this kind of problems, an observer thread checks every 5 seconds, if a data block came in since the last check. If none of my Boxes sent data till then, something bad happened and I want to restart the whole socket comunication.
Now, it is well documentated how to build up a socket connection with NIO, but it is harder to find complexe examples how to clean reset them. And here is my problem: when my watchdog detects that no data came in the last 5s, it calls close() and then startEngine(). But after that, still no data arrive. Something seems blocked, some ressource still associated or like that. If I restart my JBoss, data arrive again. Can somebody give me a hint?
thank you for your time!
Stefan
public class TestServer
{
private NIOServer server;
private HashMap<String, SocketChannel> clientsList = new HashMap<String, SocketChannel>();
class NIOServer extends Thread
{
class MessageBuffer
{
int [] msgAsByte = new int[msgSize];
int pos = 0;
int lastSign = 0;
int bytesRead = 0;
}
private ByteBuffer readBuffer = ByteBuffer.allocate(256);
private Selector selector;
private boolean stop = false;
private int[] ports;
private int msgSize = 48;
private HashMap<String,MessageBuffer> buffer = new HashMap<String, MessageBuffer>();
private List<ServerSocketChannel> channels;
// Maps a SocketChannel to a list of ByteBuffer instances
private Map<SocketChannel, List<ByteBuffer>> pendingDataToWrite = new HashMap<SocketChannel, List<ByteBuffer>>();
public NIOServer(int[] ports) {
this.ports = ports;
}
private void stopAll()
{
stop = true;
try
{
server.interrupt();
server.join(3000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
closeConnections();
}
public void sendData(SocketChannel socket, byte[] data)
{
// And queue the data we want written
synchronized (this.pendingDataToWrite) {
List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingDataToWrite.get(socket);
if (queue == null) {
queue = new ArrayList<ByteBuffer>();
this.pendingDataToWrite.put(socket, queue);
}
queue.add(ByteBuffer.wrap(data));
}
SelectionKey key = socket.keyFor(this.selector);
if(key != null)
key.interestOps(SelectionKey.OP_WRITE);
// Finally, wake up our selecting thread so it can make the required changes
this.selector.wakeup();
}
public void run()
{
try
{
stop = false;
selector = Selector.open();
channels = new ArrayList<ServerSocketChannel>();
ServerSocketChannel serverchannel;
for (int port : ports)
{
try
{
serverchannel = ServerSocketChannel.open();
serverchannel.configureBlocking(false);
try
{
serverchannel.socket().setReuseAddress(true);
}
catch(SocketException se)
{
//
}
serverchannel.socket().bind(new InetSocketAddress(port));
serverchannel.register(selector, SelectionKey.OP_ACCEPT);
channels.add(serverchannel);
}
catch(Exception e)
{
//
}
}
while (!stop)
{
SelectionKey key = null;
try
{
selector.select();
Iterator<SelectionKey> keysIterator = selector.selectedKeys()
.iterator();
while (keysIterator.hasNext())
{
key = keysIterator.next();
if(key.isValid())
{
if (key.isAcceptable())
{
accept(key);
}
else if (key.isReadable())
{
readData(key);
}
else if (key.isWritable())
{
writeData(key);
}
}
else
{
SocketChannel sc = (SocketChannel) key.channel();
}
keysIterator.remove();
}
}
catch ( Exception e)
{
if(e instanceof IOException || e instanceof ClosedSelectorException)
{
try
{
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
channels.remove(ssc);
ssc.close();
key.cancel();
}
catch(Exception ex)
{
//
}
}
else
{
//
}
}
}
}
catch(Exception e1)
{
//
}
closeConnections();
}
private void closeConnections()
{
//if thread is stopped, close all
try
{
try
{
if(this.selector == null || this.selector.keys() == null)
{
log.debug("No selectors or keys found to close");
}
else
{
Iterator<SelectionKey> keys = this.selector.keys().iterator();
while(keys.hasNext())
{
SelectionKey key = keys.next();
key.cancel();
}
}
}
catch(Exception ex) {
//
}
if(selector != null)
selector.close();
if(channels != null)
{
for(ServerSocketChannel channel:channels)
{
channel.socket().close();
channel.close();
}
}
if(clientsList != null)
{
Iterator<Map.Entry<String, SocketChannel>> hfm = clientsList.entrySet().iterator();
while(hfm.hasNext())
{
Map.Entry<String, SocketChannel> s = hfm.next();
s.getValue().close();
}
}
clientsList=null;
selector = null;
channels = null;
pendingDataToWrite = null;
}
catch(Exception e)
{
//
}
}
private void accept(SelectionKey key) throws IOException
{
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
String ip = sc.socket().getRemoteSocketAddress().toString();
if(!buffer.containsKey(ip))
buffer.put(ip, new MessageBuffer());
}
private void readData(SelectionKey key) throws Exception
{
SocketChannel sc = (SocketChannel) key.channel();
MessageBuffer buf = buffer.get(sc.socket().getRemoteSocketAddress().toString());
try
{
buf.bytesRead = sc.read(readBuffer); //read into buffer.
}
catch(Exception e2)
{
sc.close();
buffer.remove(sc);
}
//close connection
if (buf.bytesRead == -1)
{
sc.close();
key.cancel();
return;
}
readBuffer.flip(); //make buffer ready for read
while(readBuffer.hasRemaining())
{
//Read the data and forward it to another Process...
}
readBuffer.compact(); //make buffer ready for writing
}
private void writeData(SelectionKey key) throws Exception
{
SocketChannel socketChannel = (SocketChannel) key.channel();
synchronized (this.pendingDataToWrite) {
List queue = (List) this.pendingDataToWrite.get(socketChannel);
// Write until there's not more data ...
while (!queue.isEmpty()) {
ByteBuffer buf = (ByteBuffer) queue.get(0);
try
{
socketChannel.write(buf);
}
catch(Exception e)
{
//
}
finally
{
queue.remove(0);
}
if (buf.remaining() > 0) {
// ... or the socket's buffer fills up
break;
}
}
key.interestOps(SelectionKey.OP_READ);
}
}
}
public void close() {
if (server != null && server.isAlive())
{
server.stopAll();
}
if(clientsList != null)
{
clientsList.clear();
}
server = null;
}
public void startEngine(int[] ports) {
if (ports != null) {
for (int port : ports)
log.info("Listening on port " + port);
server= new NIOServer(ports);
server.start();
}
}
}
Use a select() timeout.
If the timeout happens, close all the registered SocketChannels.
If you want to get more fine-grained, keep track of the last I/O time on each channel, and close those that have expired at the bottom of each select() loop.
NB Your technique for OP_WRITE is not correct. There are many answers here showing how to use it properly.
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'm new in NIO and i need to create simple non-blocking client with next api:
void start();
void send(String msg);
void stop();
Start method should create connection for specified host and port.
stop method should stop the client and release connection.
send should send messages to server.
So I have read documentations and I created simple client:
public class NonBlockingNIOClient {
private DatagramChannel channel;
public final static int MAX_PACKET_SIZE = 65507;
private static final Logger LOGGER = LoggerFactory.getLogger(NonBlockingNIOStatsDClient.class);
public NonBlockingNIOStatsDClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() {
try {
channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress(getHost(), getPort()));
while (!channel.isConnected()) {
LOGGER.debug("still connecting");
}
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(channel.isConnected()) {
}
}
});
thread.start();
} catch(IOException e) {
throw new ClientException("Failed to start client", e);
}
}
public void stop() {
try {
channel.disconnect();
} catch(IOException e) {
throw new StatsDClientException("Failed to stop client", e);
}
}
#Override
public void send(String msg) {
LOGGER.debug("send: {}", msg);
Validate.notBlank(msg, "message to sand cannot be blank");
ByteBuffer buf = ByteBuffer.allocate(MAX_PACKET_SIZE);
buf.clear();
buf.put(msg.getBytes());
buf.flip();
try {
channel.write(buf);
} catch(IOException e) {
getErrorHandler().handle(e);
}
}
}
How I understand from docs that channel.configureBlocking(false); does not guarantee that write method from channel will work in non-blocking mode. I guess I need to use selectors to achieve non-blocking behavior. But when I was trying to do next:
Selector selector = null;
try {
selector = Selector.open();
channel.register(selector, SelectionKey.OP_WRITE);
while(channel.isConnected()){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext())
{
SelectionKey key = iterator.next();
if(key.isWritable())
{
//do send
}
iterator.remove();
}
}
selector.close();
}
In this case client does not respond to send() method because client was blocked by while(channel.isConnected()). Do you have any suggestions how I can make start method available and at the same time use selectors.
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);
}