刚学了NIO,写一下自己的理解
网络通信中,NIO提供了SocketChannel和ServerSocketChannel两种不同的套接字通道来实现,可以设置阻塞与非阻塞两种模式,为了实现高负载高并发都采取非阻塞的模式。通道是双向的,可以同时在通道上发送和读取数据。NIO采用可分配大小的缓冲区Buffer实现对数据的读写操作。
服务器仅采用一个线程去处理所有的客户端线程,这就需要创建一个Selector,将ServerSocketChannel和想要监控的SocketChannel注册到Selector中(用SelectableChannel的register方法,该方法返回一个这个channel向Selector注册的键,是一个SelectionKey实例,它包装了SelectableChannel和该通道感兴趣的操作)。
Selector就像一个观察者,不断地获取Selector的select方法的返回值,返回值是准备就绪的SelectionKey的数目,然后就进行处理(通过Selector的selectedKeys方法返回被选择的SelectionKey集合,然后处理连接请求和读取数据)。
每个客户端只有一个SocketChannel,将该SocketChannel注册到指定的Selector后,监听该Selector即可。如果监听到该Selector的select方法的返回值大于0,表明该Selector上有需要进行IO处理的SelectionKey,获取到SocketChannel后即可处理请求和数据。
贴一段练习的代码:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
class NServer
{
private Selector selector = null;
static final int PORT = 30000;
private Charset charset = Charset.forName("UTF-8");
public void init() throws IOException
{
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);
server.bind(isa);
//设置ServerSocket以非阻塞方式工作
server.configureBlocking(false);
//将server注册到指定的Selector对象
server.register(selector,SelectionKey.OP_ACCEPT);
while(selector.select()>0)
{
//依次处理每个已选择的SelectionKey
for(SelectionKey sk : selector.selectedKeys())
{
selector.selectedKeys().remove(sk);
//如果sk对应的Channel包含客户端的连接请求
if(sk.isAcceptable())
{
SocketChannel sc = server.accept();
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
//将sk对应的Channel设置成准备接受其他请求
sk.interestOps(SelectionKey.OP_ACCEPT);
}
//如果sk对应的Channel有数据需要读取
if(sk.isReadable())
{
//获取该SelectionKey对应的Channel
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
try
{
while(sc.read(buff)>0)
{
buff.flip();
content += charset.decode(buff);
}
System.out.println("读取的数据:"+ content);
sk.interestOps(SelectionKey.OP_READ);
}
//如果捕获到带sk对应的Channel出现了异常,即表明该Channel对应的Client出现了问题,
//所以从Selector中取消sk的注册
catch (IOException e)
{
sk.cancel();
if(sk.channel() != null)
sk.channel().close();
}
//将收到的数据发给所有注册的SelectionKey
if(content.length()>0)
{
for(SelectionKey key : selector.keys())
{
Channel targetChannel = key.channel();
if(targetChannel instanceof SocketChannel)
{
SocketChannel dest = (SocketChannel)targetChannel;
dest.write(charset.encode(content));
}
}
}
}
}
}
}
public static void main(String[] args) throws IOException
{
new NServer().init();
}
}
class NClient
{
private Selector selector = null;
static final int PORT = 30000;
private Charset charset = Charset.forName("UTF-8");
private SocketChannel sc = null;
public void init() throws IOException
{
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1",PORT);
sc = SocketChannel.open(isa);
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
//启动读取服务端数据的线程
new ClientThread().start();
//创建键盘输入流
Scanner scan = new Scanner(System.in);
while (scan.hasNextLine())
{
String line = scan.nextLine();
sc.write(charset.encode(line));
}
}
//定义读取服务端数据的线程
private class ClientThread extends Thread
{
public void run()
{
try
{
while (selector.select()>0)
{
//遍历每个有可用IO操作的Channel对应的SelectionKey
for(SelectionKey sk : selector.selectedKeys())
{
selector.selectedKeys().remove(sk);
if(sk.isReadable())
{
//使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while(sc.read(buff)>0)
{
sc.read(buff);
buff.flip();
content += charset.decode(buff);
}
System.out.println("聊天信息:"+ content);
sk.interestOps(SelectionKey.OP_READ);
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException
{
new NClient().init();
}
}