使用 nio-selector
。
引入自写的方法类。
下载 JAVA 文件
netty 包
1 2 3 4 5
| <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.86.Final</version> </dependency>
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.redisc;
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel;
public class Client { public static void main(String[] args) throws IOException { SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("localhost", 8090)); System.out.println("waiting..."); } }
|
在第 11
行打断点。
服务端
特别注意,read 事件的注册方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
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 static com.redisc.ByteBufferUtil.debugRead;
@Slf4j(topic = "c.Test") public class Run {
public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false);
SelectionKey sscKey = ssc.register(selector, 0, null); sscKey.interestOps(SelectionKey.OP_ACCEPT); log.debug("register key:{}", sscKey);
ssc.bind(new InetSocketAddress(8090)); while (true) { selector.select(); Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); log.debug("key:{}", key); if (key.isAcceptable()) { ServerSocketChannel channel = (ServerSocketChannel) key.channel(); SocketChannel sc = channel.accept(); sc.configureBlocking(false); SelectionKey scKey = sc.register(selector, 0, null); scKey.interestOps(SelectionKey.OP_READ); log.debug("{}", sc); } else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(16); channel.read(buffer); buffer.flip(); debugRead(buffer); }
}
} }
}
|
各种情况处理
客户端断开
- 开启服务端
- 开启 debug 的客户端
- 然后关闭客户端
- MacBook 模式下会进入死循环
- 关闭的时候会发送一个空数据到 server 端,如果进入死循环可以 cancel 一下
- Macbook 不会报错
- Windows 模式下会报错「IOException 远程主机强迫关闭了一个现有连接」
所以,服务端代码改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
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 static com.redisc.ByteBufferUtil.debugRead;
@Slf4j(topic = "c.Test") public class Run {
public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false);
SelectionKey sscKey = ssc.register(selector, 0, null); sscKey.interestOps(SelectionKey.OP_ACCEPT); log.debug("register key:{}", sscKey);
ssc.bind(new InetSocketAddress(8090)); while (true) { selector.select(); Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); log.debug("key:{}", key); if (key.isAcceptable()) { ServerSocketChannel channel = (ServerSocketChannel) key.channel(); SocketChannel sc = channel.accept(); sc.configureBlocking(false); SelectionKey scKey = sc.register(selector, 0, null); scKey.interestOps(SelectionKey.OP_READ); log.debug("{}", sc); } else if (key.isReadable()) { try { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(16); int read = channel.read(buffer); if (read > 0) { buffer.flip(); debugRead(buffer); } else { log.debug("客户端关闭"); key.cancel(); }
} catch (IOException e) { e.printStackTrace(); key.cancel(); }
}
}
} }
}
|
所以处理 read
事件改成了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(16); int read = channel.read(buffer); if (read > 0) { buffer.flip(); debugRead(buffer); } else { log.debug("客户端关闭"); key.cancel(); }
} catch (IOException e) { e.printStackTrace(); key.cancel(); }
|
用 read
判断是为了处理 MacBook
上的错误,用异常是为了处理 windows
上的错误,ps: read > 0
应该也能处理 windows
上的客户端断开。
另外,还测试了一种情况,在客户端 11
位置,打断电,然后 sc
变量上执行
1
| sc.write(Charset.defaultCharset().encode(""))
|
这样的话服务端什么都不输出,也不报错,客户端断开后,依然能正确执行。
正常断开
客户端 11
处代码打断店,然后,sc
变量执行。
如果是正常断开代码
1
| int read = channel.read(buffer);
|
这里的 read = -1
第二版客户端代码依然完美运行。
消息边界问题