多线程+非阻塞通信导致多次处理读事件
有问题的代码:Echo服务端
简单的解释一下,事件处理器handler
有一个handle(SelectionKey)
方法,根据方法的不同,执行不同的操作。对于读事件,创建一个线程进行读事件处理。
public class MutiThreadServer {
private static final int PORT = 8888;
private ServerSocketChannel severSocketChannel;
private Selector selector;
private EventHandler handler;//自定义的事件处理器
public MutiThreadServer() throws IOException {
//创建serverSocketChannel
this.severSocketChannel = ServerSocketChannel.open();
//开启非阻塞方式
this.severSocketChannel.configureBlocking(false);
//绑定地址,开启监听
this.severSocketChannel.bind(new InetSocketAddress(PORT));
//创建selector
this.selector = Selector.open();
//注册accepte事件
this.severSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//创建事件处理器
this.handler = new EventHandler(selector);
}
private static class EventHandler{
private Selector selector;
public EventHandler(Selector selector) {
this.selector = selector;
}
public void handle(SelectionKey key) throws IOException {
if(key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(this.selector, SelectionKey.OP_READ, Boolean.FALSE);
}else if(key.isReadable()) {
//启动一个线程进行读事件的处理
System.out.println("触发读事件");
//读事件已经交给一个线程处理
(new ReadHandler(key)).start();
}
}
private static class ReadHandler extends Thread{
private SelectionKey selectionKey;
private SocketChannel clientChannel;
private String clientIp;
private int clientPort;
public ReadHandler(SelectionKey key) throws IOException {
this.selectionKey = key;
this.clientChannel = (SocketChannel) key.channel();
InetSocketAddress address = (InetSocketAddress)clientChannel.getLocalAddress();
clientIp = address.getHostString();
clientPort = address.getPort();
}
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
clientChannel.read(buffer);
buffer.flip();
//服务端输出
String content = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到信息 [" + this.clientIp + " : " + this.clientPort + "] " + content);
//回送给客户端
this.clientChannel.write(buffer);
//收到exit,退出
if("exit".equals(content)) {
System.out.println("与 [ " + this.clientIp + " : " + this.clientPort + "]的连接已断开");
try {
this.clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//准备下一次输入
buffer.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void service() {
//是否有事件触发
try {
while(selector.select() > 0) {
//获得已经触发的事件
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while(selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
//删除即将处理过的事件,因为使用多线程进行处理,避免多次处理
selectionKeys.remove();
handler.handle(key);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
this.severSocketChannel.close();
this.selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
(new MutiThreadServer()).service();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当客户端发送一次数据后,服务端的输出:
产生问题的原因
可以看到,读事件被处理了三次。
造成这个结果的原因:
事件处理速度 > 三次select()的间隔,也就是说,读事件还在处理的时候,下一次的select()已经运行,由于读事件还没有完成,所以读事件仍然是被触发的状态,导致了多次的处理读事件。
验证问题
产生问题的原因:事件处理速度 > select()速度
所以,增加select()之间的间隔事件。通过sleep(5000),是两次select()之间的的间隔增加到5s
while(selector.select() > 0) {
//获得已经触发的事件
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while(selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
//删除即将处理过的事件,因为使用多线程进行处理,避免多次处理
selectionKeys.remove();
handler.handle(key);
}
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
问题解决
通过线程睡眠的方式,只是验证对问题的分析是否正确,并没有真正的解决这个问题,通信的数据量是不确定的,很难确定sleep()的值,况且,通过sleep()本身就与非阻塞通信矛盾。
多次处理的原因:第二次处理事件时,不知道该事件已经被处理过了
所以,可以通过一个标记,表示这个事件已经被处理,不需要再次处理了。
每个SelectionKey
,都可以绑定一个对象,在这里,可以绑定一个Boolean,表示是否已经被处理了。只要在处理时判断它是否被处理了即可避免多次处理。在处理完成后,一定要把标记修改为False
,否则,下一个读事件触发时,就没办法处理了。
public class MutiThreadServer {
private static final int PORT = 8888;
private ServerSocketChannel severSocketChannel;
private Selector selector;
private EventHandler handler;//自定义的事件处理器
public MutiThreadServer() throws IOException {
//创建serverSocketChannel
this.severSocketChannel = ServerSocketChannel.open();
//开启非阻塞方式
this.severSocketChannel.configureBlocking(false);
//绑定地址,开启监听
this.severSocketChannel.bind(new InetSocketAddress(PORT));
//创建selector
this.selector = Selector.open();
//注册accepte事件
this.severSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//创建事件处理器
this.handler = new EventHandler(selector);
}
private static class EventHandler{
private Selector selector;
public EventHandler(Selector selector) {
this.selector = selector;
}
public void handle(SelectionKey key) throws IOException {
if(key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(this.selector, SelectionKey.OP_READ, Boolean.FALSE);
}else if(key.isReadable()) {
if((Boolean)key.attachment()) {
return ;
}
//启动一个线程进行读事件的处理
System.out.println("触发读事件");
//读事件已经交给一个线程处理
key.attach(Boolean.TRUE);
(new ReadHandler(key)).start();
}
}
private static class ReadHandler extends Thread{
private SelectionKey selectionKey;
private SocketChannel clientChannel;
private String clientIp;
private int clientPort;
public ReadHandler(SelectionKey key) throws IOException {
this.selectionKey = key;
this.clientChannel = (SocketChannel) key.channel();
InetSocketAddress address = (InetSocketAddress)clientChannel.getLocalAddress();
clientIp = address.getHostString();
clientPort = address.getPort();
}
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
clientChannel.read(buffer);
buffer.flip();
//服务端输出
String content = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到信息 [" + this.clientIp + " : " + this.clientPort + "] " + content);
//回送给客户端
this.clientChannel.write(buffer);
//收到exit,退出
if("exit".equals(content)) {
System.out.println("与 [ " + this.clientIp + " : " + this.clientPort + "]的连接已断开");
try {
this.clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//准备下一次输出
buffer.clear();
//事件处理完成,下一次触发该事件时,是未处理状态
this.selectionKey.attach(Boolean.FALSE);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void service() {
//是否有事件触发
try {
while(selector.select() > 0) {
//获得已经触发的事件
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while(selectionKeys.hasNext()) {
SelectionKey key = selectionKeys.next();
//删除即将处理过的事件,因为使用多线程进行处理,避免多次处理
selectionKeys.remove();
handler.handle(key);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
this.severSocketChannel.close();
this.selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
(new MutiThreadServer()).service();
} catch (IOException e) {
e.printStackTrace();
}
}
}