服务端 整体流程(这里是把Reactor线程给独立拆分成一个类)
0.启动main方法
1.main 方法 启动 添加 mainReactor(连接事件) 和 subReactor(读事件)到数组
2.initAndRegister()初始化方法 打开一个通道 绑定到Selector 上并绑定感兴趣事件accept
3.从mainReactorThreads数组中拿出一个ReactorThread去执行建立连接
4.调用拿出来的 ReactorThread的start方法 运行该线程 遍历当前绑定到Selector的channel 获取到之后 丢给具体的handler
5.获取到之后 要remove掉当前通道 否则会重复处理
6.mainReactorThreads中会 随机获取一个数组subReactorThreads中的ReactorThread去执行io操作
7.此时绑定的就是读事件
8.mainReactor中的handler方法中 会分发给具体的io handler去进行读取数据
9.返回响应的数据
代码 --------NettyServer类
public class NettyServer {
private ServerSocketChannel serverSocketChannel;
// 1、创建多个线程 - accept处理reactor线程 (accept线程)
private ReactorThread[] mainReactorThreads = new ReactorThread[1];
// 2、创建多个线程 - io处理reactor线程 (I/O线程)
private ReactorThread[] subReactorThreads = new ReactorThread[8];
/** 处理业务操作的线程 池*/
private static ExecutorService workPool = new ThreadPoolExecutor(5, Runtime.getRuntime().availableProcessors()*2, 50, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
thread.setName("Test 业务线程");
return thread;
}
});
/**
* 启动方法
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
NettyServer nioServer = new NettyServer();
nioServer.newGroup(); // 1、 创建main和sub两组线程
nioServer.initAndRegister(); // 2、 创建serverSocketChannel,注册到mainReactor线程上的selector上
nioServer.bind(); // 3、 为serverSocketChannel绑定端口
}
/**
* 初始化线程组
*/
private void newGroup() throws IOException {
// 创建IO线程,负责处理客户端连接以后socketChannel的IO读写
for (int i = 0; i < subReactorThreads.length; i++) {
subReactorThreads[i] = new ReactorThread() {
//实现handler方法
@Override
public void handler(SelectableChannel channel) throws IOException {
// work线程只负责处理IO处理,不处理accept事件
SocketChannel ch = (SocketChannel) channel;
//读取指定长度
ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
long len = -2;
while (ch.isOpen() && (len = ch.read(requestBuffer)) != -1) {
// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
if (requestBuffer.position() > 0) break;
}
if(len == -1) { // !很重要客户端关闭连接的读事件信息,不做处理则会有大量的读事件
ch.close();
return;
}
if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
requestBuffer.flip();
byte[] content = new byte[requestBuffer.limit()];
requestBuffer.get(content);
System.out.println(new String(content));
System.out.println(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());
// TODO 业务操作 数据库、接口...
StringBuffer stringBuffer=new StringBuffer();
workPool.submit(() -> {
stringBuffer.append("ceshi------");
System.out.println(Thread.currentThread().getName()+"执行业务逻辑");
});
// 响应结果 200
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Length: 11\r\n\r\n" +
"Hello World My first NettyDemo";
stringBuffer.append(response);
ByteBuffer buffer = ByteBuffer.wrap(stringBuffer.toString().getBytes());
while (buffer.hasRemaining()) {
ch.write(buffer);
}
}
};
}
// 创建mainReactor线程, 只负责处理serverSocketChannel
for (int i = 0; i < mainReactorThreads.length; i++) {
mainReactorThreads[i] = new ReactorThread() {
AtomicInteger incr = new AtomicInteger(0);
@Override
public void handler(SelectableChannel channel) throws Exception {
// 只做请求分发,不做具体的数据读取
ServerSocketChannel ch = (ServerSocketChannel) channel;
//连接
SocketChannel socketChannel = ch.accept();
//设置非阻塞
socketChannel.configureBlocking(false);
// 收到连接建立的通知之后,随机获取一个 分发给I/O线程继续去读取数据
int index = incr.getAndIncrement() % subReactorThreads.length;
//获取到一个指定的subReactorThread 去执行
ReactorThread workEventLoop = subReactorThreads[index];
//获取到io的线程
workEventLoop.doStart();
SelectionKey selectionKey = workEventLoop.register(socketChannel);
selectionKey.interestOps(SelectionKey.OP_READ);
System.out.println(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());
}
};
}
}
/**
* 初始化服务端的channel,并且开启主线程
*
* @throws IOException IO异常
*/
private void initAndRegister() throws Exception {
// 1、 创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// 2、 将serverSocketChannel注册到selector
int index = new Random().nextInt(mainReactorThreads.length);
//3.启动 执行run方法 把channel分派给不同的handler 去执行一个mainReactorThread
mainReactorThreads[index].doStart();
//注册通道到selector mainReactorThreads在第一步时就已经初始化了
SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
//绑定感兴趣事件accept
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
}
/**
* 绑定端口
*
* @throws IOException IO异常
*/
private void bind() throws IOException {
// 1、 正式绑定端口,对外服务
serverSocketChannel.bind(new InetSocketAddress(8080));
System.out.println("启动完成,端口8080");
}
}
---------ReactorThread类
abstract class ReactorThread extends Thread { Selector selector; /** * Selector监听到有事件后,调用这个方法 */ public abstract void handler(SelectableChannel channel) throws Exception; /** * 使用Selector的好处在于: * 使用更少的线程来就可以来处理通道了, * 相比使用多个线程,避免了线程上下文切换带来的开销。 * @throws IOException */ public ReactorThread() throws IOException { selector = Selector.open(); } volatile boolean running = false; @Override public void run() { // 轮询Selector事件 while (running) { try { //选择一组其相应通道准备好进行I / O操作的键。 selector.select(1000); // 返回此选择器的选择键集。 Set<SelectionKey> selected = selector.selectedKeys(); // 遍历查询结果 Iterator<SelectionKey> iter = selected.iterator(); while (iter.hasNext()) { // 被封装的查询结果 SelectionKey key = iter.next(); // 删除已选的key,以防重复处理 iter.remove(); // Ready Set是通道已经准备就绪的操作的集合,在一个选择后,你会是首先访问这个Ready Set。 int readyOps = key.readyOps(); // 关注 Read 和 Accept两个事件 监听到read 和accept时间后 调用handler if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { try { //检索当前附件。 SelectableChannel channel = (SelectableChannel) key.attachment(); //设置非阻塞 channel.configureBlocking(false); //在这个地方 把channel分发给Thread去执行 handler(channel); if (!channel.isOpen()) { System.out.println("客户端关闭了连接"); key.cancel(); // 如果关闭了,就取消这个KEY的订阅 } } catch (Exception ex) { key.cancel(); // 如果有异常,就取消这个KEY的订阅 } } } } catch (IOException e) { e.printStackTrace(); } } } protected SelectionKey register(SelectableChannel channel) throws Exception { return channel.register(selector, 0, channel); } protected void doStart() { if (!running) { running = true; start(); } } }
--------------------------------------------客户端 nioClient
public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); while (!socketChannel.finishConnect()) { // 没连接上,则一直等待 Thread.yield(); } Scanner scanner = new Scanner(System.in); System.out.println("请输入:"); // 发送内容 String msg = scanner.nextLine(); ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); while (buffer.hasRemaining()) { socketChannel.write(buffer); } // 读取响应 System.out.println("收到服务端响应:"); ByteBuffer requestBuffer = ByteBuffer.allocate(1024); while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) { // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了) if (requestBuffer.position() > 0) break; } requestBuffer.flip(); byte[] content = new byte[requestBuffer.limit()]; requestBuffer.get(content); System.out.println(new String(content)); /* scanner.close(); socketChannel.close();*/ } }
运行流程 :
1.启动 NettyServer的main方法
2.启动nioClient的main方法
3.nioClient控制台 输入内容
4.返回结果
效果
nettyServer 控制台输出:
Connected to the target VM, address: '127.0.0.1:57166', transport: 'socket'
启动完成,端口8080
Thread-8收到新连接 : /127.0.0.1:57189
123
Thread-0收到数据,来自:/127.0.0.1:57189
Test 业务线程执行业务逻辑
Disconnected from the target VM, address: '127.0.0.1:57166', transport: 'socket'
客户端控制台 输出:
请输入:
123
收到服务端响应:
ceshi------HTTP/1.1 200 OK
Content-Length: 11