前言
学习了NIO的基本原理及使用方法之后,开始尝试写一个NIO实现的聊天室,练习一下代码流程。
服务器端
服务器端主要负责接受各客户端的连接,接收客户端发来的信息,并且将其广播给所有已连接客户端。
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
public class NioServer {
public void start() throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器启动成功");
for (; ; ) { int readyChannels = selector.select();
if (readyChannels == 0) continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next();
iterator.remove();
if (selectionKey.isAcceptable()) { acceptHandler(serverSocketChannel, selector); }
if (selectionKey.isReadable()) { readHandler(selectionKey, selector); }
} }
}
private void acceptHandler(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException { SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
socketChannel.write(Charset.forName("UTF-8") .encode("你与聊天室其他人都不是朋友关系,请注意隐私安全"));
}
private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
String request = ""; while (socketChannel.read(byteBuffer) > 0) { byteBuffer.flip();
request += Charset.forName("UTF-8").decode(byteBuffer);
} socketChannel.register(selector, SelectionKey.OP_READ);
if (request.length() > 0) { broadCast(selector, socketChannel, request); } }
private void broadCast(Selector selector, SocketChannel sourceChannel, String request) { Set<SelectionKey> selectionKeys = selector.keys();
selectionKeys.forEach(selectionKey -> {
Channel targetChannel = selectionKey.channel();
if (targetChannel instanceof SocketChannel && targetChannel != sourceChannel) { try { ((SocketChannel) targetChannel).write(Charset.forName("UTF-8").encode(request)); } catch (IOException e) { e.printStackTrace(); } }
});
}
public static void main(String[] args) { try { new NioServer().start(); } catch (IOException e) { e.printStackTrace(); } } }
|
客户端启动类
向服务器端发送数据。同时新开线程接收服务器端发来的数据。
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
|
public class NioClient {
public void start(String nickname) throws IOException {
SocketChannel socketChannel = SocketChannel.open( new InetSocketAddress("127.0.0.1", 8000));
System.out.println("客户端 " + nickname + " 启动成功!");
Selector selector = Selector.open(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); new Thread(new NioClientHandler(selector)).start();
Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String request = scanner.nextLine(); if (request != null && request.length() > 0) { socketChannel.write(Charset.forName("UTF-8").encode(nickname + " : " + request)); } }
} }
|
客户端接收服务器端数据类
负责接收服务端发来的数据。
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
|
public class NioClientHandler implements Runnable {
private Selector selector;
public NioClientHandler(Selector selector) { this.selector = selector; }
@Override public void run() { try { for (; ; ) { int readyChannels = selector.select();
if (readyChannels == 0) continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next();
iterator.remove();
if (selectionKey.isReadable()) { readHandler(selectionKey, selector); }
} } } catch (IOException e) { e.printStackTrace(); } }
private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
String response = ""; while (socketChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
response += Charset.forName("UTF-8").decode(byteBuffer);
} socketChannel.register(selector, SelectionKey.OP_READ);
if (response.length() > 0) { System.out.println(response); } } }
|
实例:A客户端、B客户端
到这里服务器端和客户端响应启动代码就已经完成了,如果我们需要连接到服务器端进入群聊,只要新开一个线程,命名即可。
假设我新建一个AClient,代码如下:
1 2 3 4 5 6 7 8 9
|
public class AClient { public static void main(String[] args) throws IOException { new NioClient().start("AClient"); } }
|
再次创建新线程流程同上面一样。
效果演示
A客户端
B客户端
最后
不得不说,NIO的程序还是挺难写的,而且还会出现许多的问题。像我写的这么简单的代码就肯定有很多bug存在,更别说复杂的业务代码了。这也应该就是大多数人不使用JDK原生NIO进行网络编程的原因了。