几种NIO程序写法(一)kafka的SocketServer

原创
2013/04/11 22:03
阅读数 9.5K

总结一点常见的NIO程序的写法,虽然没有一个固定的格式,也没有特别大的差别,但是多总结总结各位大师的写法,多见点儿组合还是对自身代码的质量有很大提高的。这一篇我想通过对kafka network包下的源码进行分析,然后抽取第一种比较常见的写法。之前已经写过一篇关于kafka通信源码的解读,可参见http://my.oschina.net/ielts0909/blog/102336,最近再改写一些源码,所以还是有必要把更完整的认识更新上来。

当然这不是最常见的写法,最常见,也是大多数人都能接受的代码可以参考http://my.oschina.net/ielts0909/blog/89849文中的代码,当然在read或者write的时,更多的会采用多线程来处理。

各种写法的本质都是一样的,可能我们要更注重比较不同细节的写法,kafkaSocketServer的代码主要在kafka broker端使用,是最基础的服务端通信类。在kafka代码中,与常见写法区别比较大的就是将选择器键的接收部分(acceptor)单独拿出来写了。再通过接收器(acceptor)将读写的任务交付给更多个处理器(processor)。在处理器中采用队列的形式按先进先出的顺序对读写进行操作。


我特意将图画的跟reactor模式的图差不多的形式,其实两者的本质也没什么差别,主要看怎么去实现acceptor与多线程处理读写操作。


我用java改写了部分kafka的代码,这样便于阅读,我们看AbstractServerThread中的一些细节,这个类主要定义了startupshutdown相关的一系列方法,这些方法通过闭锁来同步多线程执行的顺序。这个类直接向子类提供了selector对象。

package org.gfg.inforq.network;

import java.io.IOException;
import java.nio.channels.Selector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A base class for server thread with several variables and methods
 * 
 * @author Chen.Hui
 * @since 0.1
 */
public abstract class AbstractServerThread implements Runnable {

	protected Selector selector;
	private static final Logger LOG = LoggerFactory
			.getLogger(AbstractServerThread.class);
	private CountDownLatch startupLatch = new CountDownLatch(1);
	private CountDownLatch shutdownLatch = new CountDownLatch(1);
	private AtomicBoolean alive = new AtomicBoolean(false);

	protected AbstractServerThread() throws IOException {
		this.selector = Selector.open();
		LOG.info("selector is opening");
	}

	/**
	 * shutdown the running therad
	 * @throws InterruptedException
	 * 
	 */
	protected void shutdown() throws InterruptedException {
		alive.set(false);
		selector.wakeup();
		shutdownLatch.await();
	}

	/**
	 * Causes the current thread to wait until the latch has counted down to
	 * zero, unless the thread is interrupted.
	 * @throws InterruptedException
	 */
	protected void awaitStartup() throws InterruptedException {
		startupLatch.await();
	}

	/**
	 *  releasing all waiting threads if the count reaches zero.
	 */
	protected void startupComplete() {
		alive.set(true);
		startupLatch.countDown();
	}

	/**
	 * 
	 */
	protected void shutdownComplete() {
		shutdownLatch.countDown();
	}
	
	/**
	 * 
	 * @return true only this thread is startup complete
	 */
	protected boolean isRunning(){
		return alive.get();
	}
	
}

Acceptor的作用也仅仅就是accept。我们可以注意到Acceptor引用了一系列的Processor,然后为每个processor与一个key绑定。并将相应的通道传递给processor。所以总结起来acceptor做了两件事,绑定keyprocessor,并将通道传递给相应的processor

package org.gfg.inforq.network;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author Chen.Hui
 * 
 */
public class Acceptor extends AbstractServerThread {

	protected int port;
	private Processor[] processors;
	protected int sendBufferSize;
	protected int receiveBufferSize;
	
	private static final Logger LOG=LoggerFactory.getLogger(Acceptor.class);
	
	
	public Acceptor(int port, Processor[] processors, int sendBufferSize,
			int receiveBufferSize) throws IOException {
		//super();
		this.port=port;
		this.processors=processors;
		this.sendBufferSize=sendBufferSize;
		this.receiveBufferSize=receiveBufferSize;
	}

	public void run() {
		try {
			ServerSocketChannel serverChannel = ServerSocketChannel.open();
			serverChannel.configureBlocking(false);
			serverChannel.socket().bind(new InetSocketAddress(port));
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			LOG.info("Awaiting connections on port "+port);
			startupComplete();
			
			int currentProcessor=0;
			while(isRunning()){
				int ready=selector.select(500);
				if(ready>0){
				 Set<SelectionKey> keys=selector.selectedKeys();
				 Iterator<SelectionKey> iter=keys.iterator();
				 while(iter.hasNext()&&isRunning()){
					 SelectionKey key=null;
					 try{
						 key=iter.next();
						 iter.remove();
						 if(key.isAcceptable()){
							 accept(key,processors[currentProcessor]);
						 }else{
							 throw new IllegalStateException("Unrecognized key state for acceptor thread");
						 }
						 currentProcessor=(currentProcessor+1)%processors.length;
					 }catch (Exception e) {
						LOG.error(" ",e);
					}
				 }
				}
			}
			serverChannel.close();
			selector.close();
			shutdownComplete();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void accept(SelectionKey key, Processor processor) throws IOException {
		ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
		ssc.socket().setReceiveBufferSize(receiveBufferSize);
		
		SocketChannel socketChannel=ssc.accept();
		socketChannel.configureBlocking(false);
		socketChannel.socket().setTcpNoDelay(true);
		socketChannel.socket().setSendBufferSize(sendBufferSize);
		
		if(LOG.isDebugEnabled()){
			LOG.debug("sendBufferSize:["+socketChannel.socket().getSendBufferSize()+"] receiveBufferSize:["+socketChannel.socket().getReceiveBufferSize()+"]" );
		}
		processor.accept(socketChannel);
	}
}

Acceptor里也没做什么策略直接将任务按照currentProcessor=(currentProcessor+1)%processors.length;的形式分配,如果做的更好的话,可以按照任务的权重、执行时间等重新分配。另外要注意这里调用的一些AbstractServerThread里的方法。

Processor类中主要做的就是读写操作,这部分没有什么特别突出的不同,在类里引用了队列来存储由acceptor分发过来的通道,并按顺序处理。

package org.gfg.inforq.network;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Processor extends AbstractServerThread {

	private final int maxRequestSize;
	
	public Processor(int maxRequestSize) throws IOException {
		super();
		this.maxRequestSize=maxRequestSize;
	}

	private ConcurrentLinkedQueue<SocketChannel> newConnections = new ConcurrentLinkedQueue<SocketChannel>();
	private static final Logger LOG = LoggerFactory.getLogger(Processor.class);

	public void run() {
		/** make sure the selector is open completely */
		startupComplete();

		while (isRunning()) {
			try {
				// setup any new connections that have been queued up
				configureNewConnections();
				int ready = selector.select();
				if (ready > 0) {
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iter = keys.iterator();
					while (iter.hasNext() && isRunning()) {
						SelectionKey key = null;
						try {
							key = iter.next();
							iter.remove();

							if (key.isReadable()) {
								//
								read(key);
							} else if (key.isWritable()) {
								//
								write(key);
							} else if (key.isValid()) {
								//close
								close(key);
							} else {
								throw new IllegalStateException("Unrecognized key state for processor thread");
							}
						} catch (Exception e) {
							if (e instanceof IOException) {

							}
						}
					}
				}

			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

	private void read(SelectionKey key) {
		SocketChannel socketChannel=channelFor(key);
		Receive request=(Receive) key.attachment();
		
		if(key.attachment()==null){
			request=new BoundedByteBufferReceive(maxRequestSize);
			key.attach(request);
		}
		
		int read=request.readFrom(socketChannel);
		
		if(LOG.isTraceEnabled()){
			LOG.trace(read+" bytes read from "+socketChannel.socket().getRemoteSocketAddress());
		}
		
		if(read<0){
			close(key);
			return;
		}else if(request.complete){
			handle(key,request);
		}
		
	}

	private void handle(SelectionKey key, Receive request) {
		// TODO Auto-generated method stub
		
	}

	private void write(SelectionKey key) {	
		Send response= (Send) key.attachment();
		SocketChannel socketChannel=channelFor(key);
		int written =response.writeTo(socketChannel);
		
		if(LOG.isTraceEnabled()){
			LOG.trace(written+" bytes written to "+socketChannel.socket().getRemoteSocketAddress());
		}
		if(response.complete){
			key.attach(null);
			key.interestOps(SelectionKey.OP_READ);
		}else{
			key.interestOps(SelectionKey.OP_WRITE);
			selector.wakeup();
		}
	}
	
	private SocketChannel channelFor(SelectionKey key){
		return (SocketChannel) key.channel(); 
	}

	/**
	 * close the channel
	 * @param key
	 */
	private void close(SelectionKey key) {
		SocketChannel channel=(SocketChannel) key.channel();
		if(LOG.isDebugEnabled()){
			LOG.debug("Closing the connection from"+channel.socket().getRemoteSocketAddress());
		}
		if(channel.isOpen()&&channel.socket().isConnected()){
			try {
				//care the sequence of closing
				channel.socket().close();
				channel.close();
				key.attach(null);
				key.cancel();
			} catch (IOException e) {
				LOG.info(e.getMessage(),e);
			}
		}
	}

	private void configureNewConnections() {
		while (newConnections.size() > 0) {
			SocketChannel channel = newConnections.poll();
			if (LOG.isDebugEnabled()) {
				LOG.debug("linstening to a new connection from "
						+ channel.socket().getRemoteSocketAddress());
			}
			try {
				channel.register(selector, SelectionKey.OP_READ);
			} catch (ClosedChannelException e) {
				LOG.info("the channel has been closed..");
				continue;
			}
		}
	}

	public void accept(SocketChannel socketChannel) {
		newConnections.add(socketChannel);
		selector.wakeup();
	}
}

我也无法说出这种将接收器单独拿出来写的方式到底好处在哪里,但是这样的写法结构清晰,各个组件分工明确,还是很值得学习的。总结的目的不就是为了学习各种优秀的东西么。


展开阅读全文
打赏
5
63 收藏
分享
加载中
Gaischen博主

引用来自“softer1”的评论

搜狐的Jafka有这段代码~~

jafka 就是用java改写了Kafka的 所以代码用java写出来肯定都差不多的
2014/01/28 09:48
回复
举报
搜狐的Jafka有这段代码~~
2014/01/28 09:24
回复
举报
更多评论
打赏
2 评论
63 收藏
5
分享
返回顶部
顶部