我们都知道netty是boss worker 线程模型。底层使用Javanio。拨开netty其他的功能,我们简单的自己实现一下这个线程模型。
两个类,NioAcceptor,处理连接 单线程类似boss。NioReactor处理读写,多线程l类似worker。
NioAcceptor 监听来自客户端的链接,来了链接后把通道传递给一个NioReactor(它的个数就是线程池的大小),NioReactor把通道注册到worker 的 队列上,该任务不断轮训队列里面的通道的读事件 做出反应。
解释放在代码中
NioAcceptor
package com.zwj.myNio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;
public class NioAcceptor {
private volatile Selector selector;
private final ServerSocketChannel serverSocketChannel;
private String iP;
private int port;
// 对于以字符方式读取和处理的数据必须要进行字符集编码和解码
String encoding = System.getProperty("file.encoding");
// 加载字节编码集
Charset charse = Charset.forName(encoding);
private final NioReactor[] nioReactors;
private volatile int nextReactor;
public NioAcceptor(String ip, int port, int threadN) {
if (ip == null || ip == "") {
throw new IllegalArgumentException();
}
this.iP = ip;
if (port < 1) {
throw new IllegalArgumentException();
}
this.port = port;
if (threadN < 1) {
throw new IllegalArgumentException();
}
//新建处理读写的线程池
nioReactors = new NioReactor[threadN];
for (int i = 0; i < threadN; i++) {
nioReactors[i] = new NioReactor();
nioReactors[i].start();
}
try {
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.configureBlocking(false);
/** 设置TCP属性 */
serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024 * 16 * 2);
// backlog=100
serverSocketChannel.bind(new InetSocketAddress(ip, port), 100);
this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("server start!");
} catch (IOException e) {
e.printStackTrace();
throw new IllegalArgumentException();
}
}
private NioReactor nextNioReactor() {
int i = nextReactor++;
if (i >= nioReactors.length) {
i = nextReactor = 0;
}
return nioReactors[i];
}
public void run() {
while (true) {
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
if (!keys.isEmpty()) {
for (SelectionKey key : keys) {
keys.remove(key);
if (key.isValid() && key.isAcceptable()) {
SocketChannel ch = serverSocketChannel.accept();
ch.configureBlocking(false);
String tomessage = "welcome,this is server!";
try {
ch.write(charse.encode(tomessage));
} catch (IOException e) {
e.printStackTrace();
}
//把这个通道交给Reactor处理,获取NioReactor的方式非常原始就是轮询这个数组,从0开始那,到了末尾就又从0开始
NioReactor nioReactor = nextNioReactor();
nioReactor.postRegister(ch);
} else {
//acceptor 只接受 accept事件,如果有其他事件 把这个通道从selector 移除
key.cancel();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NioReactor 内部的worker 是一个 runnable 不断的select 检查就绪的读事件。同时有一保存了该worker需要处理通道的队列。详细解释看代码注释
package com.zwj.myNio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
public class NioReactor {
final String encoding = System.getProperty("file.encoding");
final Charset charse = Charset.forName(encoding);
private final Worker worker;
public NioReactor() {
//新建一个worker,worker 是一个 任务,实现 了Runnable接口他的功能就是不断的轮训注册在他的selector 上的通过(channel)就绪读事件
this.worker = new Worker();
}
public void postRegister(SocketChannel socketChannel) {
//把 通道放到worker 的register队列上面,每次worker轮训的时候会去检查这个队列,如果有新的通道,就把通道注册到自己的selector
this.worker.registerQueue.add(socketChannel);
this.worker.selector.wakeup();
}
public void start() {
//启动一个线程来执行worker
new Thread(worker).start();
}
private class Worker implements Runnable {
private volatile Selector selector;
private ConcurrentLinkedQueue<SocketChannel> registerQueue = new ConcurrentLinkedQueue<SocketChannel>();
public Worker() {
try {
this.selector = Selector.open();
} catch (IOException e) {
e.printStackTrace();
}
}
private void register(Selector selector) {
if (registerQueue.isEmpty()) {
return;
}
SocketChannel socketChannel = null;
while ((socketChannel = registerQueue.poll()) != null) {
try {
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while (true) {
try {
register(selector);
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
keys.remove(key);
if (key.isValid() && key.isReadable()) {
SocketChannel rchannel = null;
try {
rchannel = (SocketChannel) key.channel();
ByteBuffer readByteBuffer = ByteBuffer.allocate(2048);
String content = "";
int result= rchannel.read(readByteBuffer);
if(result == -1 ){
rchannel.close();
}else{
readByteBuffer.flip();
content += charse.decode(readByteBuffer);
//to do business
System.out.println("server rec:" + content);
String tomessage = "this is server!i have rec you mess";
rchannel.write(charse.encode(tomessage));
}
} catch (IOException e) {
if (rchannel != null) {
key.cancel();
rchannel.close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
最后一个 客户端程序 来连接 服务
package com.zwj.myNio;
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.nio.charset.Charset;
import java.util.Scanner;
public class NIOClient {
private static final int SIZE = 1024;
private static NIOClient instance = new NIOClient();
public String IP = "127.0.0.1";// 10.50.200.120
public int CLIENT_PORT = 8090;// 4444 9666
private SocketChannel channel;
private Selector selector = null;
String encoding = System.getProperty("file.encoding");
Charset charset = Charset.forName(encoding);
private NIOClient() {
}
public static NIOClient getInstance() {
return instance;
}
public void send(String content) throws IOException {
selector = Selector.open();
channel = SocketChannel.open();
// channel = SocketChannel.open(new InetSocketAddress(IP,CLIENT_PORT));
InetSocketAddress remote = new InetSocketAddress(IP, CLIENT_PORT);
channel.connect(remote);
// 设置该sc以非阻塞的方式工作
channel.configureBlocking(false);
// 将SocketChannel对象注册到指定的Selector
// SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT
channel.register(selector, SelectionKey.OP_READ);//这里注册的是read读,即从服务端读数据过来
// 启动读取服务器数据端的线程
new ClientThread().start();
channel.write(charset.encode(content));
// 创建键盘输入流
Scanner scan = new Scanner(System.in);//这里向服务端发送数据,同时启动了一个键盘监听器
while (scan.hasNextLine()) {
System.out.println("输入数据:\n");
// 读取键盘的输入
String line = scan.nextLine();
// 将键盘的内容输出到SocketChanenel中
channel.write(charset.encode(line));
}
scan.close();
}
/**
* 从服务端读入数据的线程
*/
private class ClientThread extends Thread {
@Override
public void run() {
try {
while (selector.select() > 0) {
// 遍历每个有可能的IO操作的Channel对银行的SelectionKey
for (SelectionKey sk : selector.selectedKeys()) {
// 删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
// 如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable()) {
// 使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel) sk.channel();
String content = "";
ByteBuffer bff = ByteBuffer.allocate(SIZE);
while (sc.read(bff) > 0) {
sc.read(bff);
bff.flip();
content += charset.decode(bff);
}
// 打印读取的内容
System.out.println("服务端返回数据:" + content);
// 处理下一次读
sk.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException io) {
io.printStackTrace();
}
}
}
/**
* 接受服务端的数据
*
* @param channel
* @return
* @throws Exception
*/
protected void receiveData(SocketChannel channel) throws Exception {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int count = 0;
while ((count = channel.read(buffer)) != -1) {
if (count == 0) {
Thread.sleep(100); // 等等一下
continue;
}
// 转到最开始
buffer.flip();
while (buffer.remaining() > 0) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
}
public static void main(String[] args) {
try {
NIOClient nio = new NIOClient();
nio.send("test");//向服务端发送数据
//nio.send("metrics:memory: swap: cpu: network i/o: disks i/o: tcp:\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务启动的服务端main 程序
package com.zwj.myNio;
public class MyNio {
public static void main(String[] args) {
// write your code here
NioAcceptor nioAcceptor = new NioAcceptor("127.0.0.1", 8090, 1);
nioAcceptor.run();
}
}