代码实现了一个简单单线程的echo服务,首先创建并绑定监听通道,接着注册到选择器中,选择器等待事件的发生,当有连接到来时创建新的连接并注册读事件,当连接有数据可读时读取并返回数据,当对端关闭连接时则注销事件并关闭连接
ServerSocketChannel acceptor = ServerSocketChannel.open();//创建监听通道
acceptor.configureBlocking(false);
acceptor.socket().bind(new InetSocketAddress(80));
Selector selector = Selector.open();
acceptor.register(selector, SelectionKey.OP_ACCEPT);//注册监听事件
while(true)
{
int nready = selector.select();//等待事件的发生
if(nready <= 0)
continue;
Set<SelectionKey> keys = selector.selectedKeys();//获取选择键
for (SelectionKey key : keys)
{
if(key.isAcceptable())//有连接到来
{
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
else if(key.isReadable())//连接可读
{
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int n = 0;
while( (n = client.read(buf)) > 0)
{
buf.flip();
client.write(buf);//返回数据<pre name="code" class="java"><pre name="code" class="java"> buf.clear();
}if(n < 0){ key.cancel();client.close();}}}keys.clear();}
下面实现一个多线程的echo服务,主线程负责监听并接受连接,接着把新创建的连接放入子线程的连接队列中,子线程处理连接数据的收发
实现子线程的Runnable接口类
class Task implements Runnable
{
BlockingQueue<SocketChannel> queue = new LinkedBlockingQueue<SocketChannel>();
Selector selector;
public Task()
{
try
{
selector = Selector.open();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void run()
{
try
{
while(true)
{
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys)//处理数据的收发
{
if(key.isReadable())
{
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int n = 0;
while((n = channel.read(buf)) > 0)
{
buf.flip();
channel.write(buf);
buf.clear();
}
key.cancel();
channel.close();
}
}
keys.clear();
while(!queue.isEmpty())//连接队列不为空
{
SocketChannel channel = queue.poll();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);//注册读事件
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public class EchoServer
{
private int port = 80;
private ServerSocketChannel listener;
private Selector selector;
private ArrayList<Task> threads = new ArrayList<Task>();
private int index = -1;
private int nthreads = 4;
public EchoServer()
{
try
{
selector = Selector.open();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public EchoServer(int port)
{
this();
this.port = port;
}
public void setThreadNumber(int n)
{
if(n > 0)
nthreads = n;
}
public void start()
{
try
{
listener = ServerSocketChannel.open();
listener.configureBlocking(false);
listener.socket().bind(new InetSocketAddress(port));
listener.register(selector, SelectionKey.OP_ACCEPT);
for(int i = 0; i < nthreads; i++)
{
Task task = new Task();
new Thread(task).start();//创建子线程
threads.add(task);
}
while(true)
{
int nready = selector.select();
if(nready <= 0)
continue;
onAccept();//有新连接
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
private void onAccept()
{
try
{
SocketChannel client = listener.accept();
if(client == null)
return;
index = (index + 1) % nthreads;//轮询子线程
threads.get(index).queue.offer(client);//子线程中加入新连接
threads.get(index).selector.wakeup();//唤醒子线程
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
EchoServer echoServer = new EchoServer(80);
echoServer.setThreadNumber(4);
echoServer.start();//启动echo服务
}
}
在程序的测试过程中遇到了选择器select()调用bug,选择器上没有事件发生,select()不阻塞,一直返回0,浪费了CPU,可以回收旧的选择器,将注册的事件转移到新创建的选择器上