在分布式java应用中,经常需要在各个子系统间进行通信与数据交换。在java领域要实现这样的功能有很多途径,下面我将使用nio+tcp/ip这种基于消息机制的方式来实现这样的功能。利用nio的非阻塞模式以及选择器机制能够很大程度上的提高程序的性能及吞吐量。利用线程池能够方便的做到一请求一线程,在实际环境中这是非常有作用的,真实的应用场景往往是连接数可能会很多,但是同一时间向服务器发送的请求会远远小于实际的连接数。
/**
*NIO tcp/ip客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc=SocketChannel.open();
sc.configureBlocking(false);//需在sc.connect之前设置
sc.connect(new InetSocketAddress("127.0.0.1",9999));
Selector selector=Selector.open();
sc.register(selector, SelectionKey.OP_CONNECT);
while(true){
if(sc.isConnected()){
BufferedReader read=new BufferedReader(new InputStreamReader(System.in));
int writeBytes=sc.write(Charset.forName("UTF-8").encode(read.readLine()));
if(writeBytes==0){
sc.register(selector, SelectionKey.OP_WRITE);//注册写事件(当写缓冲区满时)
}
}
int selectKeys=selector.select();
if(selectKeys==0){
continue;
}
for(SelectionKey key:selector.selectedKeys()){
if(key.isConnectable()){
SocketChannel socketChanel=(SocketChannel)key.channel();
if(socketChanel==null){
continue;
}
socketChanel.configureBlocking(false);
socketChanel.register(selector,SelectionKey.OP_READ);
socketChanel.finishConnect();
}else if(key.isReadable()){
SocketChannel socketChanel=(SocketChannel)key.channel();
ByteBuffer bf=ByteBuffer.allocate(1024);
while(socketChanel.read(bf)>0){
bf.flip();
System.out.println(Charset.forName("UTF-8").decode(bf).toString());
bf.clear();
}
}else if(key.isWritable()){
//只要写缓冲区未满就一直会产生写事件,如果此时又不写数据时,会产生不必要的资源损耗,所以这里需要取消写事件以免cpu消耗100%
//写数据,如果写缓冲区满时继续注册写事件key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
key.interestOps(key.interestOps()&(~SelectionKey.OP_WRITE));
}
}
selector.selectedKeys().clear();
}
}
/**
*NIO tcp/ip 服务器端
*/
public class Server {
final static int PORT = 9999;
//处理请求的线程池
final static ExecutorService workThreadsPool = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 5 / 8 + 3);
public static void main(String[] args) throws IOException,
InterruptedException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("127.0.0.1", PORT));
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int selectorKeys = selector.select(1000L);
if (selectorKeys == 0) {
continue;
}
for (SelectionKey selectKey : selector.selectedKeys()) {
if (selectKey.isAcceptable()) {
ServerSocketChannel serverSocketChanel = (ServerSocketChannel) selectKey
.channel();
SocketChannel sc = serverSocketChanel.accept();
// 因为是采用的非阻塞模式,所以当没有连接时以上方法也为立即返回,只是返回的值为null
if (sc == null) {
continue;
}
System.out.println("accept a quest");
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
} else if (selectKey.isReadable()) {
// 此处通过线程池实现了一请求一线程的机制,该机制的实现利用了NIO的非阻塞模式与通道选择器机制
// 相比以前BIO+THREADPOOL实现的一连接一线程的吞吐量更高,并发性更强
HandlerRequestWork handlerRequest = new HandlerRequestWork(
"Request handler");
workThreadsPool.execute(handlerRequest);
handlerRequest.handler(selectKey);
}
}
selector.selectedKeys().clear();
}
}
static class HandlerRequestWork extends Thread {
private SelectionKey key;
private final Lock lock = new ReentrantLock();
private final Condition preparedSingle = lock.newCondition();
public HandlerRequestWork(String threadName) {
super(threadName);
}
public void handler(SelectionKey key) {
lock.lock();
try {
preparedSingle.signalAll();
this.key = key;
// 在缓冲区数据未处理完成时,下一次轮询selector.select(1000L)时依然会触发readable事件,
// 所以这里避免对同一次请求进行多次处理需要取消readable这一感兴趣事件 ,当处理完成后再注册该事件
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
} finally {
lock.unlock();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
lock.lock();
try {
if (key == null) {
try {
preparedSingle.await();
} catch (InterruptedException e) {
logger.admin(e.getMessage(), e);
throw new RuntimeException(e);
}
}
if (key == null) {
return;
}
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(1024);
try {
while (sc.read(bf) > 0) {
bf.flip();
System.out.println(Charset.forName("UTF-8").decode(bf)
.toString());
bf.clear();
}
if (bf != null) {
bf.clear();
bf = null;
}
sc.write(Charset.forName("UTF-8").encode(
"got messages from client=" + sc));
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
} catch (IOException e) {
logger.admin(e.getMessage(), e);
try {
sc.close();// 关闭发生发生异常的连接,并取消注册SelectionKey
} catch (IOException e1) {
logger.admin(e1.getMessage(), e1);
}
key.cancel();
}
} finally {
lock.unlock();
}
}
}