非阻塞socket中的几个重要类:
- ServerSocketChannel(可以看作是ServerSocket的替代类, 需要通过open()的静态方法创建)
- SocketChannel(Socket 的替代类)通过register方法向selector 注册一个事件
- Selector,类似一个观察者,SocketChannel向Selector注册了特定的事件,Selector就会监控这些事件是否发生。Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。主要方法有:
1. keys() 当前所有向Selector注册的SelectionKey的集合
2. selectedKeys() 已注册的被Selector捕获的SelectionKey的集合
3. open() 打开一个selector
4. select() 如果注册的事情发生了,select() 函数返回值就会大于0
- SelectionKey,当ServerSocketChannel或SocketChannel通过register()方法向Selector注册事件时,register()方法会创建一个SelectionKey对象,这SelectionKey 对象是用来跟踪注册事件的句柄。 在SelectionKey对象的有效期间,Selector会一直监控与SelectionKey对象 相关的事件,如果事件发生,就会把SelectionKey对象加入到selectedKeys()集合中。
在 SelectionKey 中定义了四种事件,
- SelectionKey.OP_ACCEPT :接收连接就绪事件,表示服务器监听到了客户连接,服务器可以接收这个连接了。常量值为 16
- SelectionKey.OP_CONNECT :连接就绪事件,表示客户与服务器的连接已经建立成功。常量值为 8 。
- SelectionKey.OP_READ :读就绪事件,表示通道中已经有了可读数据,可以执行读操作了。常量值为 1 。
- SelectionKey.OP_WRITE :写就绪事件,表示已经可以向通道写数据了。常量值为 4 。
- Charset, CharsetDecoder,CharsetEncoder主要用来对字符编码在网络上传输,到本地之后解码输出。
这几个类之间的关系通过register方法可以看的出来
ssc = ServerSocketChannel.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
socket通道通过register方法向一个selector 注册它感兴趣的事件。创建一个非阻塞服务器的步骤有:
- 打开selector, SocketServerChannel
- 为SocketServerChannel 绑定一个本地地址,并设置为非阻塞模式
- 注册SelectionKey中的事件到selector
- 循环处理已注册的事件
public class NioServer {
private static final int PORT = 3000;
private static final String DEFAULT_CHARSET = "GBK";
private static final int BUFFERSIZE = 1024;
private Selector selector;
private ServerSocketChannel ssc;
private ByteBuffer buffer;
private Charset charset;
private CharsetDecoder decoder;
private CharsetEncoder encoder;
public NioServer() throws IOException{
// 打开seletor
selector = Selector.open();
ssc = ServerSocketChannel.open();
// 绑定一个本地地址
ssc.socket().bind(new InetSocketAddress(PORT));
// 设置为非阻塞模式
ssc.configureBlocking(false);
// 为ssc 注册接入事件
ssc.register(selector, SelectionKey.OP_ACCEPT);
buffer = ByteBuffer.allocate(BUFFERSIZE);
//
charset = Charset.forName(DEFAULT_CHARSET);
decoder = charset.newDecoder();
encoder = charset.newEncoder();
}
private void listen() throws IOException{
while (true){
int i = selector.select();
if (i > 0){
// 已注册的事件集合
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()){
SelectionKey sk = iter.next();
// 每次取出一个事件,就移除
iter.remove();
handleSelectionKey(sk);
}
}
}
}
// 分别对应SelectionKey的四种事件
private void handleSelectionKey(SelectionKey sk) throws IOException{
if(sk.isAcceptable()){
// 接收连接就绪
handleAccept(sk);
} else if (sk.isReadable()){
// 读就绪
handleRead(sk);
} else if (sk.isWritable()){
// 写就绪
}
}
private void handleAccept(SelectionKey sk) throws IOException{
ServerSocketChannel ssc = (ServerSocketChannel)sk.channel();
SocketChannel sc = ssc.accept(); // 得到客户端socket
sc.configureBlocking(false);
// 连接后, 为客户端注册读事件
sc.register(selector, SelectionKey.OP_READ);
}
private void handleRead(SelectionKey sk) throws IOException{
SocketChannel sc = (SocketChannel) sk.channel();
buffer.clear();
int r = sc.read(buffer);
if (r == -1){
sc.close();
return;
}
buffer.flip();
// 解码从客户端传过来的数据
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println("client says: " + charBuffer.toString());
// 向客户端发送编码后的信息
sc.write(encoder.encode(CharBuffer.wrap("服务器写")));
}
public static void main(String args[]) throws IOException{
System.out.println("server listen...");
new NioServer().listen();
}
}
创建非阻塞的客户端与服务器几乎是一样, 只不过是把ServerSocketChannel改成SocketChannel
public class NioClient {
private static final int PORT = 3000;
private static final String DEFAULT_CHARSET = "GBK";
private static final int BUFFERSIZE = 1024;
private InetSocketAddress sia;
private Selector selector;
private ByteBuffer buffer;
private Charset charset;
private CharsetDecoder decoder;
private CharsetEncoder encoder;
public NioClient() throws IOException {
SocketChannel sc= SocketChannel.open();
sc.configureBlocking(false);
selector = Selector.open();
// 注册连接事件
sc.register(selector, SelectionKey.OP_CONNECT);
buffer = ByteBuffer.allocate(BUFFERSIZE);
charset = Charset.forName(DEFAULT_CHARSET);
decoder = charset.newDecoder();
encoder = charset.newEncoder();
// 连接到服务器
sia = new InetSocketAddress("localhost", PORT);
sc.connect(sia);
}
public void connect() throws IOException{
while (true){
int i = selector.select();
if (i > 0){
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
handleSelectionKey(key);
}
}
}
}
public void handleSelectionKey(SelectionKey key) throws IOException{
if (key.isConnectable()){
SocketChannel sc = (SocketChannel) key.channel();
if (sc.isConnectionPending()){
sc.finishConnect();
}
sc.write(encoder.encode(CharBuffer.wrap("客户端写")));
// 注册读事件
sc.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
buffer.clear();
int r = sc.read(buffer);
if (r == -1){
sc.close();
return;
}
buffer.flip();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println("server says: " + charBuffer.toString());
}
}
public static void main(String args[]) throws IOException{
System.out.println("client connect...");
new NioClient().connect();
}
}