Netty的IO线程NioEventLoop聚合了Selector(选择器 / 多路复用器),可以并发处理成百上千个客户端连接。
当线程从某客户端Socket通道进行读写时,若没有数据可用,该线程可以进行其他任务。
线程通常将非阻塞IO的空闲时间用于其他通道上执行IO操作,所以单独的线程可以管理多个输入输出通道。
由于读写操作都是非阻塞的,就可以充分提高IO线程的运行效率,避免由于频繁IO阻塞导致的线程挂起。
一个IO线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO一连接一线程模型,架构性能、弹性伸缩能力和可靠性都得到极大地提升。
Public abstract class Selector implement Closeable{
Public static Selector open(); //得到一个选择器对象
Public int select(long timeout); //监控所有注册的通道,当其中的IO操作可以进行时,将对应的selectionkey加入内部集合并返回,参数设置超时时间
Public Set selectionKeys(); //从内部集合中得到所有的SelectionKey
}
Selector.select(); //若未监听到注册管道中有事件,则持续-阻塞
Selector.select(1000); //阻塞1000毫秒,1000毫秒后返回
Selector.wakeup(); //唤醒selector
Selector.selectNow(); //不阻塞,立即返回
说明
【注】SelectionKey中定义了四个操作标志位:OP_READ—表示通道中发生读事件;OP_WRITE—表示通道中发生写事件;OP_CONNECT—表示建立连接;OP_ACCEPT—请求新连接
【1】要求:编写一个NIO入门案例,实现服务器和客户端之间的数据简单通讯(非阻塞)
【2】设计思路
服务器端:
1)创建ServerSocketChannel,将其设置为非阻塞,并绑定一个端口进行监听。
2)创建一个Selector对象,将ServerSocketChannel注册到Selector上,并设置该Selector关注的事件(OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT)
3)调用select()方法,循环等待客户端channel的连接
4)一旦有客户端连接,则返回一个SelectionKey的集合
5)使用迭代器将集合中的Key取出,获取其对应通道的事件{
isAcceptable(): |
有新的客户端请求连接 |
isConnectable(): |
数据传输完成,连接结束 |
isReadable(): |
测试该通道是否有数据可以读取 |
isWritable(): |
测试该通道是否有数据写入 |
}
6)每处理完一个通道的请求,需要将其对应的Key从SelectionKey的集合中移除,防止操作被重复执行。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel,类似ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建一个Selector对象
Selector selector = Selector.open();
//绑定一个端口,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把ServerSocketChannel注册到selector,关心事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("注册后的selectionkey的数量 = "+selector.keys().size());
//循环等待客户连接
while(true){
//这里设置了等待1秒,如果1秒内没有事件发生,返回
if (selector.select(1000)==0){ //无事件发生
System.out.println("服务器等待了1秒,无连接");
continue;
}
//如果返回的大于0,就获取到相关的selectionKey集合
//1、如果返回的>0,表示已经获取到关注的事件
//2、selector.selectedKeys()方法返回关注事件的集合
// 通过selectedKeys反向获取通道
Set selectionKeys = selector.selectedKeys();
System.out.println("selectionkeys的数量 = "+ selectionKeys.size());
//遍历集合
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()){
//获取到selectionKey
SelectionKey key = iterator.next();
//根据Key所对应的通道发生的事件做相应处理
if (key.isAcceptable()){
//如果是OP_ACCEPT,表示有新的客户端连接
//给该客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功,生成一个socketChannel "+socketChannel.hashCode());
//将当前的socketChannel注册到selector,关注事件为读事件,同时给socket Channel关联一个`buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客户端连接后,注册的selectionkey的数量 = "+selector.keys().size());
}
if (key.isReadable()){ //发生读事件
//通过key 反向获取到对应的channel
SocketChannel channel = (SocketChannel) key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer);
System.out.println("from 客户端 "+ new String(buffer.array()));
}
//手动从集合中移动当前的selectionKey,防止重复操作
iterator.remove();
}
}
}
}
客户端:
1)首先同样要创建一个通道SocketChannel,类似与Socket通信编程,将其设置为非阻塞形式,并提供服务器IP和端口号进行连接。
2)客户端请求连接,服务端进行处理需要一定的时间,不妨设置一个等待时间,如果在规定的时间内没能连接到服务端,返回连接失败信息。
3)如果连接成功,则发送数据,发送方式参照之前的,通过SocketChannel的write方法写入到byteBuffer中。
import org.w3c.dom.ls.LSInput;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞模式
socketChannel.configureBlocking(false);
//提供服务器端的IP和端口
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(socketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("连接需要时间,客户端不会阻塞,可以做其他工作");
}
}
//如果连接成功,就发送数据
String str = "hello,NIO";
//Wraps a byte array into a buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//发送数据,即将buffer中的数据写入到channel中
socketChannel.write(byteBuffer);
System.in.read();
}
}
public abstract class SelectionKey { public abstract Selector selector(); public abstract SelectableChannel channel(); public final Object attachment() public abstract SelectionKey interestOps(int ops); public final boolean isReadable(); public final boolean isWritable() public final boolean isAcceptable() } |
//得到与之关联的Selector对象 //得到与之关联的通道 //得到与之关联的共享数据 //设置或改变监听的事件类型 //测试通道是否可以读 //测试是否可写 //测试是否可以建立连接ACCEPT |
1、SeverSocKetChannel在服务器端监听新的客户端Socket连接
2、相关方法如下:
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{ |
|
public static ServerSocketChannel open(); |
//得到一个ServerSocketChannel通道 |
public void bind(SocketAddress endpoint); |
//设置服务器端口号 |
public final SelectableChannel configureBlocking(boolean block); |
//设置阻塞或非阻塞模式,取值false表示采用非阻塞模式 |
public abstract SocketChannel accept(); |
//接受一个连接,返回代表这个连接的通道对象 |
public final SelectionKey register(Selector sel, int ops); | //注册一个选择器并设置监听事件 |
} |
public abstract class SocketChannel extends AbstractSelectableChannel Implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{ |
|
public static SocketChannel open(); |
//得到一个SocketChannel通道 |
public final SelectableChannel configureBlocking(boolean block); |
//设置阻塞或非阻塞模式,取值false为非阻塞模式 |
public abstract boolean connect(SocketAddress remote); |
//连接服务器 |
public abstract boolean finishConnect(); |
//如果上述方法连接失败,接下来则通过该方法完成链接 |
public abstract int write(ByteBuffer src); |
//向通道中写入数据 |
public abstract int read(ByteBuffer dst); |
//从通道中读取数据 |
public final SelectionKey register(Selector sel, int ops,Object att); |
//注册一个选择器并设置监听事件,最后一个参数可以设置共享数据 |
public final void close(); |
//关闭通道 |
} |
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class GroupChatServer {
//定义相关的属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//构造器 初始化操作
public GroupChatServer(){
try{
//得到选择器
selector = Selector.open();
//初始化ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞
listenChannel.configureBlocking(false);
//将该listenChannel注册到selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
}
}
//监听
public void listen(){
try{
//循环处理
while(true){
int count = selector.select();
if(count > 0){ //如果有事件则处理
//遍历得到的selectorKey集合
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//取出SelectionKey
SelectionKey key = iterator.next();
//监听到ACCEPT事件
if (key.isAcceptable()){
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
//将该sc注册到selector上
sc.register(selector,SelectionKey.OP_READ);
//提示上线了
System.out.println(sc.getRemoteAddress()+"上线了。。。");
}
if (key.isReadable()){
//通道发生read事件
//专门处理读数据的方法
readData(key);
}
//将当前的key删除,防止重复处理
iterator.remove();
}
}else{
System.out.println("等待....");
}
}
}catch(Exception e){
e.printStackTrace();
}finally {
//发生的异常处理
}
}
//读取客户端消息
private void readData(SelectionKey key){
//定义一个SocketChannel
SocketChannel channel = null;
try{
//取到关联的channel
channel = (SocketChannel) key.channel();
//创建缓冲buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int count = channel.read(byteBuffer);
//根据count的值做处理
if (count>0){
//把缓冲区的数据转换成字符串
String msg = new String(byteBuffer.array());
//输出该消息
System.out.println("from 客户端:"+ msg);
//向其他客户端转发消息,专门写一个方法处理
sendInfoToOthers(msg,channel);
}
}catch (IOException e){
//e.printStackTrace();
try {
System.out.println(channel.getRemoteAddress() + "已下线");
//取消注册
key.cancel();
//关闭通道
channel.close();
}catch (Exception r){
r.printStackTrace();
}
}
}
//转发消息给其他的客户,实际是转发给其他通道************需要排除自身
private void sendInfoToOthers(String msg, SocketChannel self) throws IOException {
//服务器转发消息
System.out.println("服务器转发消息中。。。");
//遍历所有注册到selector的socketchannel并排除自身
for (SelectionKey key: selector.keys()){
//反向获取通道
Channel targetchannel = key.channel();
//排除自身
if (targetchannel instanceof SocketChannel && targetchannel !=self){
//转型
SocketChannel dest = (SocketChannel) targetchannel;
//将msg存储到buffer中
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//将buffer中的数据写入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建一个服务器对象
GroupChatServer groupChatServer = new GroupChatServer();
//监听
groupChatServer.listen();
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class GroupChatClient {
//定义相关的属性
private final String HOST = "127.0.0.1";
private final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
//构造器,完成初始化工作
public GroupChatClient() throws IOException {
selector = Selector.open();
socketChannel = socketChannel.open(new InetSocketAddress(HOST,PORT));
socketChannel.configureBlocking(false);
//将channel注册到selector、
socketChannel.register(selector, SelectionKey.OP_READ);
//得到用户名
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ready...");
}
//向服务器发送消息
public void sendInfo(String info){
info = username + "说" + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e){
e.printStackTrace();
}
}
//从服务器端读取消息
public void readInfo(){
try {
int readChannels = selector.select();
if (readChannels > 0){
//有可用的通道
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()){
//得到相关的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一个buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取
sc.read(buffer);
//把读取到的缓冲区数据转成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove(); //删除当前的selectionKey,防止重复操作
}else {
//System.out.println("没有可用的通道");
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
//启动客户端
GroupChatClient chatClient = new GroupChatClient();
//启动一个线程,每隔3秒,读取从服务器端发送的数据
new Thread(){
public void run(){
while (true){
chatClient.readInfo();
try{
Thread.currentThread().sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
//发送数据给服务器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}