BossEventLoop
public class BossEventLoop implements Runnable{
private Selector boss;
private volatile boolean isStart = false;
private AtomicInteger index = new AtomicInteger();
private WorkerEventLoop worker;
public void register() throws IOException {
// 防止重复初始化
if (!isStart) {
// 可以理解为创建一个服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
// 监听9999端口
ssc.bind(new InetSocketAddress(9999));
// 设置为非阻塞模式
ssc.configureBlocking(false);
// 创建一个selector
boss = Selector.open();
// 将channel注册到selector中。
ssc.register(boss, SelectionKey.OP_ACCEPT);
new Thread(this, "boss").start();
worker = new WorkerEventLoop(0);
isStart = true;
}
@Override
public void run() {
while (true) {
try {
// 阻塞。 当selector注册的通道上有事件发生时继续向下运行。
boss.select();
Set<SelectionKey> selectionKeys = boss.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
// 拿到key对应的channel(其实就是上面创建的)
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
// 建立连接后,通过ServerSocketChannel获取到SocketChannel,接收后续的读事件
SocketChannel sc = channel.accept();
sc.configureBlocking(false);
// 将SocketChannel交给下一级的selector(worker)来管理。(将该SocketChannel注册到selector上)
workers.register(sc);
}
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
WorkerEventLoop
public class WorkerEventLoop implements Runnable{
Selector worker;
private volatile boolean isStart = false;
Thread thread;
int index;
public WorkerEventLoop(int index) {
this.index = index;
}
public void register(SocketChannel sc) throws IOException {
if (!isStart) {
worker = Selector.open();
new Thread(this, "worker" + index).start();
sc.register(worker, SelectionKey.OP_READ);
isStart = true;
}
}
@Override
public void run() {
while (true) {
try {
worker.select();
Set<SelectionKey> selectionKeys = worker.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer allocate = ByteBuffer.allocate(128);
int read = channel.read(allocate);
if (read == -1) {
key.cancel();
channel.close();
} else {
allocate.flip();
System.out.print("worker" + index + "-");
System.out.println(Charset.defaultCharset().decode(allocate));
}
}
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
问题剖析
问题出在WorkerEventLoop上
public void register(SocketChannel sc) throws IOException {
if (!isStart) {
worker = Selector.open();
new Thread(this, "worker" + index).start();
sc.register(worker, SelectionKey.OP_READ);
isStart = true;wo
}
}
@Override
public void run() {
while (true) {
try {
worker.select();
.......
这两个代码分别由主线程和 new Thread()出来的子线程执行, 因此顺序不可预测,
① 若worker.select()先执行, sc.register()后执行,则会阻塞,导致两边都不能继续往下走。
② 若sc.register()先执行,worker.select()后执行,则第一个客户端来连接并传送数据时可以正常运转。 而第二个客户端来时, worker,select()再次阻塞, 导致之后的程序还是无法正常执行。
解决方法–队列
private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
public void register(SocketChannel sc) throws IOException {
if (!start) {
worker = Selector.open();
new Thread(this, "worker-" + index).start();
start = true;
}
// 通过队列来保证同步。
queue.add(() -> {
try {
SelectionKey sckey = sc.register(worker, 0, null);
sckey.interestOps(SelectionKey.OP_READ);
worker.selectNow();
} catch (IOException e) {
e.printStackTrace();
}
});
// 唤醒selector
worker.wakeup();
}
@Override
public void run() {
while (true) {
try {
worker.select();
// 因为queue为ConcurrentLinkedQueue,所以不会阻塞
Runnable task = queue.poll();
if (task != null) {
task.run();
}
...