Skip to Main Content

Java APIs

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Taming the NIO circus

843790Oct 29 2003 — edited Nov 25 2008
I've spent some time looking at NIO Sockets and trying to get them to work. There are Dukes for further insights. Here are my observations so far:

- you really should use one thread to process all the I/O controlled by a single selector

- the definition of OP_WRITE in select agrees with the Unix definition, ie. not edge triggered like windows. This means that you must add and remove OP_WRITE from the interestOps depending on the actual ability to write

- JDK 1.4.2.01 works as advertised, maybe not as desired

- interestOps blocks in W2K if there is a select active on the key. Even reference blocks.

My apologies for the size of this post. I am posting a rather large amount of partially tested code that works according to these findings. These programs have been lightly tested under W2K (the worst offender). I'll try them under Solaris next, but it gave very little trouble with the real project that inspired this. If anyone gets any Linux or other ooze results there are dukes.

The first piece is a simple Echo server, its self contained and it deals with network flow control, something I didn't find in all the published examples (I've read Hitchens Java NIO, OReilly 2002 a few times and examined most of the NIO posts on this forum). Here it is, it has a few comments but basically it will either reflect bytes back at their source or just dump them in the bit bucket. It has an additional feature that it will close the connection if it receives a packet starting with IAC (0xff). This is for testing client reaction to remote close, I chose 0xff so my VB-VC6 telnet client could be used, any telnet command requests close.

The second program is a simple Swing based client. It's just a crude terminal program with the added feature that it can shut off read or send its data multiple times. There are several classes involved.

NIOEcho - the echo server
NIOTest - the swing client
NIOSocket - support for a single string based connection
NIOContoller - does the select and queuing
NIOControllable - an interface for an NIOController client
NMessage - a message between the threads
NMReceiver - an interface for a class that can receive NMessages

Here's the code
/*******************************************************************************************
	NIOEcho.java
	Simple echo server based on NIO Sockets.
	author PKWooster, Oct 2003 GPL license

	this class is self contained.
	startup is java NIOEcho -pport -eoff -dlevel
	port is port number
	off is the text off to throw data in the bit bucket
	level is lowest level message displayed
	default is NIOEcho -p5050 -d1
 */
import java.io.*;
import java.net.*;
import java.util.*;
import java.nio.*;
import java.nio.channels.*;

public class NIOEcho
{
	private boolean running;			// true if the server is active
	private ServerSocket ss;			// the listening socket
	private ServerSocketChannel sschan; // the listening channel
	private Selector selector;			// the only selector
	private static int debugLevel = 1;	// only print at this level and higher
	private boolean echo;

	// starts the server, binds to a port and runs the select
	private void start(int port, boolean eon)
	{
		echo = eon;
		int n=0;
		Iterator it;
		SelectionKey key;
		Object att;
		int io;
		running = true;
		int j=0;

		try
		{
			sschan = ServerSocketChannel.open();
			sschan.configureBlocking(false);
			ss = sschan.socket();
			ss.bind(new InetSocketAddress(port));
			selector = Selector.open();
			sschan.register(selector, SelectionKey.OP_ACCEPT);
		}
		catch(IOException ie){fail(ie,"startup failed");}

		while(running)
		{
			// now we select any pending io
			try{n = selector.select();}	// select
			catch(Exception e){fail(e,"select failed");}
			dout(0,"select n="+n);
			if(n==0){if(10<j++)fail(null,"loop detected");}
			else j=0;

			// process any selected keys
			Set selectedKeys = selector.selectedKeys();
			it = selectedKeys.iterator();
			while(it.hasNext())
			{
				key = (SelectionKey)it.next();
				int kro = key.readyOps();
				dout(0,"kro="+kro);
				if((kro & SelectionKey.OP_READ) == SelectionKey.OP_READ)doRead(key);
				if((kro & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)doWrite(key);
				if((kro & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT)doAccept(key);
				it.remove();			// remove the key
			}
		}
	}

	void doAccept(SelectionKey sk)
	{
		ServerSocketChannel sc = (ServerSocketChannel)sk.channel();
		dout(2,"accept");
		SocketChannel usc = null;
		ByteBuffer data;
		try
		{
			usc = sc.accept();
			usc.configureBlocking(false);
			data = ByteBuffer.allocate(8192);
			data.position(data.limit()); // looks like write complete
 			usc.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE,data);
		}
		catch(IOException re){fail(re,"registration error");}
	}

	void doRead(SelectionKey sk)
	{
		int len = 0;
		int wlen = 0;
		dout(1,"read");
		SocketChannel sc = (SocketChannel)sk.channel();
		ByteBuffer data = (ByteBuffer)sk.attachment();

		if(data.hasRemaining())fail(null,"stuck");
		data.clear();
		try{len = sc.read(data);}
		catch(IOException e){e.printStackTrace(); len=-1;}
		if(len > 0)
		{
			if(echo)
			{
				data.flip();
				if(data.get(0) == -1)len = -1; // first byte 255 requests close
				else
				{
					try{wlen = sc.write(data);}
					catch(Exception e){e.printStackTrace();len = -1; wlen = -1;}
					if(wlen < len)
					{	dout(12,"write blocked");
						sk.interestOps(SelectionKey.OP_WRITE);	// hung until we can write
					}
				}
			}
			else data.position(data.limit()); // echo off looks like write complete
		}
		if(len < 0)close(sc);
	}


	void doWrite(SelectionKey sk)
	{
		dout(12,"write ready");
		SocketChannel sc = (SocketChannel)sk.channel();
		ByteBuffer data = (ByteBuffer)sk.attachment();

		if(data.hasRemaining())
		{
			try{sc.write(data);}
			catch(IOException e){e.printStackTrace(); close(sc);}
		}
		if(!data.hasRemaining())sk.interestOps(SelectionKey.OP_READ);
	}

	void close(SocketChannel sc)
	{
			dout(2,"closing channel");
			try{sc.close();}
			catch(IOException ce){fail(ce,"close failed");}
	}

	static public void main(String [] args)
	{
		int port = 5050;
		boolean echo=true;

		for(int i = 0; i<args.length; i++)
		{
			if(args.startsWith("-p"))
{
port = toInt(args[i].substring(2),5050);
}
else if(args[i].startsWith("-d"))
{
debugLevel = toInt(args[i].substring(2),2);
}
else if(args[i].startsWith("-e"))
{
echo = !args[i].substring(2).equals("off");
}
}
System.out.println("debug level="+debugLevel+" Listening on port="+port+" echo "+(echo?"on":"off"));
new NIOEcho().start(port, echo);
}

void fail(Exception e, String s)
{
if(e != null)e.printStackTrace();
if(s != null)System.out.println(s);
System.exit(0);
}

// get an integer or the default value in er if not convertable
private static int toInt(String s, int er)
{
int i;

try{i = new Integer(s).intValue();}
catch(NumberFormatException exc){i =er;}
return i;
}

// debug output method, display controlled by debugLevel
private void dout(int level, String text)
{
if(level >= debugLevel)System.out.println(level+": "+text);
}
}

/*******************************************************************************************
NIOTest.java
A Swing based tester for NIO based sockets
author PKWooster, Oct 2003 GPL license
*/

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;


// client class that acts as a simple terminal
public class NIOTest extends JFrame implements NMReceiver
{
// swing GUI components
JTextField userText = new JTextField(40); // input text
JTextArea log = new JTextArea(24,40); // logging window
JTextField statusText = new JTextField(40); // status
JPanel outPanel = new JPanel();
JScrollPane logScroll = new JScrollPane(log);

JMenuBar menuBar = new JMenuBar();
JMenuItem startItem = new JMenuItem("Start");
JMenuItem hostItem = new JMenuItem("Host");
JMenuItem discItem = new JMenuItem("Request disconnect");
JMenuItem sendsItem = new JMenuItem("send count");
JMenuItem readItem = new JMenuItem("Read off");
JMenuItem debugItem = new JMenuItem("Debug level");
JMenuItem aboutItem = new JMenuItem("About");
JMenuItem exitItem = new JMenuItem("Exit");
JMenu fileMenu = new JMenu("File");
JMenu helpMenu = new JMenu("Help");
public static int debugLevel = 1;

Container cp;

// teminal stuff
NIOController controller;
NIOSocket remote; // NIO support
String address="127.0.0.1"; // default host is self
int port=5050;
int sends = 1;
boolean readOn = true;
int state = NIOControllable.CLOSED;
//----------------------------------------------------------------
// only constructor is default
NIOTest()
{
controller = new NIOController(); // runs the NIO select
controller.start(true); // start NIO select as a separate thread

// build a simple GUI
buildMenu();
cp = getContentPane();
log.setEditable(false);
outPanel.add(new JLabel("Send: "));
outPanel.add(userText);

// enter on userText causes transmit
userText.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt){userTyped(evt);}
});
cp.setLayout(new BorderLayout());
cp.add(outPanel,BorderLayout.NORTH);
cp.add(logScroll,BorderLayout.CENTER);
cp.add(statusText,BorderLayout.SOUTH);
setStatus("Closed");
addWindowListener(new WindowAdapter()
{public void windowClosing(WindowEvent evt){mnuExit();}});

// put some documentaion in log window
toLog("!! use start menu to start");
toLog("!! host is "+address+":"+port);
pack();
}

// user pressed enter in the user text field, we try to send the text
void userTyped(ActionEvent evt)
{
String txt = evt.getActionCommand();
userText.setText(""); // don't send it twice
toLog(txt, true);
System.out.println("From user:"+txt);

// put the text on the remote transmit queue
if(state == NIOControllable.OPENED)
for(int i = 0; i<sends; i++)remote.send(txt);
}

// methods to put text in logging window, toLog(text,true) if it came from user
void toLog(String txt){toLog (txt,false);}
void toLog(String txt, boolean user)
{
log.append((user?"> ":"< ")+txt+"\n");
log.setCaretPosition(log.getDocument().getLength() ); // force last line visible
}


// build the standard menu bar
void buildMenu()
{
JMenuItem item;

// file menu
startItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuStart();}});
fileMenu.add(startItem);

hostItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuHost();}});
fileMenu.add(hostItem);

discItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuDisc();}});
fileMenu.add(discItem);

sendsItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuSends();}});
fileMenu.add(sendsItem);

readItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuRead();}});
fileMenu.add(readItem);

debugItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuDebug();}});
fileMenu.add(debugItem);

exitItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuExit();}});
fileMenu.add(exitItem);
menuBar.add(fileMenu);


helpMenu.add(aboutItem);
aboutItem.addActionListener(new ActionListener()
{public void actionPerformed(ActionEvent e){mnuAbout();}});

menuBar.add(helpMenu);


setJMenuBar(menuBar);
}



// start and stop menu
void mnuStart()
{
switch(state)
{
case NIOControllable.CLOSED:
remote = new NIOSocket(this, address, port, controller);
if(remote.start())setSockState (NIOControllable.OPENING);
else remote = null;
break;
case NIOControllable.OPENED:
case NIOControllable.OPENING:
case NIOControllable.CLOSING:
if(remote != null)remote.close(); // shut it down
break;
}
}

// prompt user for host in form address:port
// default is 127.0.0.1:5050
void mnuHost()
{
String txt = JOptionPane.showInputDialog("Enter host address and port");
if (txt == null)return;

int n = txt.indexOf(':');
if(n == 0)
{
address = "127.0.0.1";
port = toInt(txt.substring(1),5050);
}
else if(n < 0)
{
address = txt;
port = 5050;
}
else
{
address = txt.substring(0,n);
port = toInt(txt.substring(n+1),5050);
}
toLog("!! host set to "+address+":"+port);
}

void mnuDisc()
{
if(state == NIOControllable.OPENED)
{
remote.requestClose(); // request disconnect
setSockState (NIOControllable.CLOSING);
}
}

void mnuSends()
{
String txt = JOptionPane.showInputDialog("Enter number of times to send data");
if (txt == null)return;
sends = toInt(txt,1);
if(sends < 1)sends = 1;
}

void mnuRead()
{
readOn = !readOn;
readItem.setText("Turn read "+(readOn?"off":"on"));
}

void mnuDebug()
{
String txt = JOptionPane.showInputDialog("Enter debug level");
if (txt == null)return;
debugLevel = toInt(txt,0);
}

void mnuAbout(){new AboutDialog(this).show();}

// exit menu
void mnuExit()
{
if(remote != null)remote.close(); // close socket
System.exit(0);
}

private void setSockState (int s)
{
if(state != s)
{
state = s;
switch(state)
{
case NIOControllable.OPENED:
startItem.setText("Close");
setStatus("Connected to "+address);
break;
case NIOControllable.CLOSED:
startItem.setText("Start");
setStatus("Disconnected");
break;
case NIOControllable.OPENING:
setStatus("Connecting to "+address);
startItem.setText("Abort");
break;

case NIOControllable.CLOSING:
setStatus("Disconnecting from "+address);
startItem.setText("Abort");
break;
}
}
}

void setStatus(String st)
{
statusText.setText(st);
}

// called when the run method in NMessage is executed in the AWT event dispatch thread
// looks a bit like an action event
public void runNMessage(NMessage nm)
{
switch(nm.type)
{
case NMessage.OPEN:
setSockState (NIOControllable.OPENED);
break;

case NMessage.CLOSE:
setSockState (NIOControllable.CLOSED);
break;

case NMessage.DATA:
toLog((String)nm.data); // just send the remote data to the log
break;
}
}

public void fromRemote(String text)
{
postNMessage(text, NMessage.DATA);
}

public void fromRemote(boolean open)
{
postNMessage(null, open?NMessage.OPEN:NMessage.CLOSE);
}

// uses invokeLater to put this message on the system event queue so Swing will run it
public void postNMessage(String dat, int type)
{
NMessage nm = new NMessage(this, dat, type);
SwingUtilities.invokeLater(nm); // the message implements Runnable, so we can use invokeLater
}

//-------------------------------------------------------------------------------------
// utility functions

// start up
static public void main(String[] args)
{
try{new NIOTest().show();}
catch(OutOfMemoryError e){e.printStackTrace();}
}

// get an integer or the default value in er if not convertable
public static int toInt(String s, int er)
{
int i;

try{i = new Integer(s).intValue();}
catch(NumberFormatException exc){i =er;}
return i;
}

public static void dout(int level, String text)
{
if(level >= debugLevel)System.out.println(level+": "+text);
}

//============================================================================================
// inner classes

//----------------------------------------------------------------------------
// about dialog
class AboutDialog extends JDialog
{
Container contentPane;
JTextField text = new JTextField("Simple character client");

AboutDialog(Frame f)
{
super(f,"About Client",true);
contentPane = getContentPane();
contentPane.add(text);
pack();
}
}
}

/*******************************************************************************************
NIOSocket.java
support for a single character based socket
author PKWooster, Oct 2003 GPL license
*/
import java.util.*;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
import java.nio.charset.*;


//-----------------------------------------------------------------------------
// client NIO socket support
//

public class NIOSocket implements NIOControllable, NMReceiver
{
SocketChannel sch;
Charset charset;
CharsetEncoder encoder;
CharsetDecoder decoder;
ByteBuffer recvBuffer=null;
ByteBuffer sendBuffer=null;
StringBuffer recvString = new StringBuffer();
StringBuffer sendString = new StringBuffer();
String crlf = "\r\n";
int recvScanned = 0;
boolean atEof;
int bufsz;
SelectionKey key;
NIOController controller;

int interest = 0;
boolean sendReady;

NIOTest client; // a reference to our client
String name ="";

int state = NIOControllable.CLOSED;

boolean signedOn = false; // another indication of state
String address; // our partners IP address
int port; // our partners port

//===========================================================================
// public methods

// construct a client socket connecting the client to address a,port p
public NIOSocket(NIOTest c, String a, int p, NIOController nc)
{
client = c;
address = a;
port = p;
name = a+":"+p;
controller = nc;
}

// start the client socket
public boolean start()
{
state = NIOControllable.OPENING; // opening
key = null;
// connect to address and port
try
{
sch = SocketChannel.open();
bufsz = 8192;
charset = Charset.forName("ISO-8859-1");
decoder = charset.newDecoder();
encoder = charset.newEncoder();
recvBuffer = ByteBuffer.allocate(bufsz);
atEof = false;
NMessage om = new NMessage(this, null, NMessage.OPEN);
controller.invoke(om); // this will enroll us
}
catch(Exception e){return fail(e,"Connection failed to="+name);}
dout(1,"connecting");
return true;
}

public void requestClose()
{
NMessage om = new NMessage(this, null, NMessage.CLOSE);
controller.invoke(om); // this will enroll us
}

public void send(String txt)
{
NMessage om = new NMessage(this, txt, NMessage.DATA);
controller.invoke(om); // this will enroll us
}

// close this remote user
public void close()
{
state = NIOControllable.CLOSED;
try{sch.close();}catch(IOException e){e.printStackTrace();}
key = null;
client.fromRemote(false); // closed as far as the client is concerned
}

// return address and port
public String getAddress()
{
Socket s = sch.socket();
return s.getInetAddress()+":"+s.getPort();
}

//======================================================================
// public method required by the NMReceiver interface
// this is running in the NIOController's thread
public void runNMessage(NMessage nm)
{
switch(nm.type)
{
case NMessage.OPEN:
enroll();
break;

case NMessage.CLOSE:
doRequestClose();
break;

case NMessage.DATA:
doSend((String)nm.data);
break;
}
showInterest();
}

//======================================================================
// public methods required by the NIOControllable interface
// this runs in the NIOController's thread
public void processSelection(SelectionKey sk)
{
int kro = sk.readyOps();
dout(0,"kro="+kro);
if((kro & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT)
doConnect();
if((kro & SelectionKey.OP_READ) == SelectionKey.OP_READ)
doRead();
if((kro & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
doWriteReady();
showInterest(); // safe to call this directly here
}

public int getState(){return state;}


//============================================================================
// private methods


//--------------------------------------------------------------------
// utilities

// set interestOps
private void showInterest()
{
if(key != null)
{
int i = key.interestOps();
if(i != interest)
{
dout(12,"changing interest to "+interest);
key.interestOps(interest);
}
}
}

// enroll our channel
private void enroll()
{
try
{
sch.connect(new InetSocketAddress(address,port));
if(sch.isConnected())reportConnect();
else if(sch.isConnectionPending())interest = SelectionKey.OP_CONNECT;
else interest = 0;
key = controller.enroll(this, sch, interest);
}
catch(Exception e){fail (e,"Connection failed to="+name);} // unexpected connect failure
}

// report failure to client and close
private boolean fail(Exception e, String str)
{
if(str != null)
{
System.out.println(str);
client.fromRemote ("!! " +str);
}
if(e != null)e.printStackTrace();
close();
return false;
}

private void dout(int level, String text){NIOTest.dout(level,text);}


//--------------------------------------------------------------------
// connection support

private void doConnect()
{
dout(1,"finishing connection");
try{sch.finishConnect();}
catch(IOException e){fail (null,"Connection failed to="+name);}
reportConnect();
}


private void reportConnect()
{
dout(1,"connected");
state = NIOControllable.OPENED;
interest = SelectionKey.OP_READ+SelectionKey.OP_WRITE;
client.fromRemote ("!! Connected to="+name);
client.fromRemote(true); // opened
}

// send an IAC character to request a close from NIOEcho
private boolean doRequestClose()
{
if(flush()) // force out any data
{
ByteBuffer b = ByteBuffer.allocate(1);
b.put((byte)-1); // send char 255
b.flip();
try{sch.write(b);}catch(IOException e){e.printStackTrace(); return false;}
return true;
}
else return false;
}



//--------------------------------------------------------------------
// transmission support

// send out text string
private void doSend(String msg)
{
dout(1,"send");
setWriteInterest(writeLine(msg));
}


private void doWriteReady()
{
dout(1,"write ready");
setWriteInterest(flush());
}

// set the read or read+write interest
private void setWriteInterest(boolean w)
{
if(w)interest = SelectionKey.OP_READ;
else interest = SelectionKey.OP_WRITE | SelectionKey.OP_READ;
}

private boolean writeLine(String dat)
{
sendString.append(dat);
sendString.append(crlf);
return flush();
}

private boolean write(String dat)
{
char ch = ' ';
boolean b;
int len;

sendString.append(dat);
if(!writeBuf())return false; // blocked
len = sendString.length();
if(len > 0)ch = sendString.charAt(len-1);
if(ch == '\n' || ch == '\r' || len > bufsz)return flush();
else return true;
}

// flush out any pending output, returns true if all data sent
public boolean flush()
{
if(!writeBuf())return false; // blocked
if(sendString.length() > 0)
{
stringToBuf();
return writeBuf();
}
return true;
}

private void stringToBuf()
{
CharBuffer cb = CharBuffer.wrap(sendString);
try{sendBuffer = encoder.encode(cb);}
catch(Exception e){e.printStackTrace();}
sendString = new StringBuffer(bufsz);
}

private boolean writeBuf()
{
if(sendBuffer != null) // buffer is already empty
{
int n = 0;
try{n = sch.write(sendBuffer);}
catch(Exception e){e.printStackTrace();}
if(n < sendBuffer.limit())
{
dout(12,"short write n="+n);
sendBuffer.position(n);
return false;
}
else sendBuffer = null;
}
return true;
}

//--------------------------------------------------------------------
// reception support

// receive a string
private void doRead()
{
String dat;

while(null != (dat = readLine()))client.fromRemote(dat); // send it back to the client
if(atEof)close();
}



private String readLine()
{
String s = null;
fill();
int len = recvString.length();
int n = firstNL();
if(n == -1)
{
if(atEof && 0 < len)
{
s = recvString.substring(0);
n = len;
}
}
else
{
s = recvString.substring(0,n);
if(len > n+1 && recvString.charAt(n)=='\r' && recvString.charAt(n+1)=='\n')n+=2;
else n++;
}

if(n > 0)
{
recvString.delete(0,n);
recvScanned = 0;
}
return s;
}

public boolean fill()
{
CharBuffer buf;
int len=0;

recvBuffer.clear();
try{len = sch.read(recvBuffer);}
catch(Exception e){e.printStackTrace();atEof=true;}
recvBuffer.flip();

if(len > 0)
{
dout(1,"read "+len+" bytes");
try
{
buf = decoder.decode(recvBuffer);
dout(0,"received="+buf);
recvString.append(buf);
}
catch(Exception e){e.printStackTrace();atEof = true;}
}
else if(len < 0)atEof = true;
return atEof;
}

// scan recvString for first new line
private int firstNL()
{
while(recvScanned < recvString.length())
{
char ch = recvString.charAt(recvScanned);
if(ch == '\r' || ch == '\n')return recvScanned;
recvScanned++;
}
return -1; // no cr or lf
}
}


/*******************************************************************************************
NIOController.java
provides support for a Selector for multiple Selectable channels
adds the capability to run arbitrary methods in the controller's thread
author PKWooster, Oct 2003 GPL license
*/

import java.io.*;
import java.net.*;
import java.util.*;
import java.nio.*;
import java.nio.channels.*;

public class NIOController
{
private Selector selector; // the NIO selector
private LinkedList invocations; // a list of invokations
boolean running;
int loopCount = 0;

// constructor
NIOController()
{
try{selector = Selector.open();}
catch(IOException e){e.printStackTrace();}
invocations = new LinkedList();
running = false;
}

// start the controller, asThread will start it in a separate thread
public void start(){start(true);} // default runs a thread
public void start(boolean asThread)
{
if(asThread)
{
Thread th = new Thread(new Runnable(){
public void run()
{
select();
System.out.println("Controller thread ended");
}});
th.setName("NIOController");
th.start();
}
else select();
}

// stop the dispathcer
public void stop()
{
running = false;
selector.wakeup();
}


// register a channel with this controller
// if this is run from another thread after the controller is started this may block,
// use invoke to prevent that.
public SelectionKey enroll(NIOControllable c, SelectableChannel sch, int interest)
{
SelectionKey sk = null;
try
{
sch.configureBlocking(false);
sk=sch.register(selector, interest, c);
}
catch(IOException e){e.printStackTrace();}
// System.out.println("key="+sk+" enrolled interest="+sk.interestOps());
return sk;
}

// request a call back
public synchronized void invoke(Runnable d)
{
invocations.add(d); // add it to our request queue
selector.wakeup(); // break out of the select
}

public void select()
{
int n;
Iterator it;
SelectionKey key;
Object att;
NIOControllable c;
int io;
running = true;
int j=0;

while(running)
{
// run any requested invocations
doInvocations();

// now we select any pending io
try{n = selector.select();} // select
catch(Exception e){e.printStackTrace(); return;}
// System.out.println("select n="+n);
if(n==0)
{
loopCount++;
if(loopCount>10)
{
System.out.println("loop detected");
break;
}
}
else loopCount=0; // **** testing

// process any selected keys
Set selectedKeys = selector.selectedKeys();
it = selectedKeys.iterator();
while(it.hasNext())
{
key = (SelectionKey)it.next();
c = (NIOControllable)key.attachment(); // get the controllable
c.processSelection(key);// ask it to process its selections
it.remove(); // remove the key
}
}
System.out.println("select ended");
}

// run the invocations in our thread, these probably set the interestOps,
// or do I/o
// but they could do almost anything
private synchronized void doInvocations()
{
Runnable r;
boolean b =true;
while(invocations.size() > 0)
{
loopCount = 0;
r = (Runnable)invocations.removeFirst();
r.run();
}
}
}


/*******************************************************************************************
NIOControllable.java
interface describing a controllable object
this is the client of a NIOController
author PKWooster, Oct 2003 GPL license
*/
import java.nio.channels.*;

public interface NIOControllable
{
public static final int CLOSED = 0;
public static final int OPENING = 1;
public static final int OPENED = 2;
public static final int CLOSING =3;

public void processSelection(SelectionKey sk); // the key is selected
public int getState(); // get the connection state
}

/*******************************************************************************************
NMessage.java
author PKWooster, Oct 2003 GPL license

a simple message that can be given to invokeLater or NIOController.invoke since it implements Runnable
note: no new thread is created for this runnable, its just a way to pass a method to the
AWT event dispatch thread or the NIOController thread
*/

public class NMessage implements Runnable
{
int type;
static public final int DATA = 0; // string data for the target thread
static public final int OPEN = 1; // open action
static public final int CLOSE = 2; // close action

NMReceiver target;
Object data; // the message data
// construct a message from a receiver, an data string and a type
NMessage(NMReceiver t, Object data, int type)
{
target = t;
this.data = data;
this.type = type;
}

// this method is run by a thread when it pops this runnable off the queue
public void run()
{
target.runNMessage(this);
}
}

/*******************************************************************************************
NMReceiver.java
author PKWooster, Oct 2003 GPL license

interface describing a class that receives NMessages
*/
public interface NMReceiver
{
public void runNMessage(NMessage msg);
}
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Dec 23 2008
Added on Oct 29 2003
148 comments
3,995 views