基于NIO的多客户端群聊
分析需求
服务端
监听客户端状态
保存客户端聊天记录
将客户端的信息分发给其他客户端 群聊
客户端
连接服务端
接受服务端分发的消息
发出消息
代码编写
代码里有详细的注释,这里我们主要是看一下编写步骤
服务端
---------------------初始化------------------------------
1.开启serversocket通道
2.开启选择器
3.设置非阻塞,注册任务
--------------------监听客户端-------------------
1.判断是否有连接
2.有链接打印用户上线日志
---------------读取客户端发送到信息---------------------
1.打开对应的通道
2.打印消息日志
3.分发消息给其他客户端
//服务端通道 private ServerSocketChannel channel; // 多路复用选择器 private Selector selector; public chatServer() { try { channel = ServerSocketChannel.open(); selector = Selector.open(); SocketAddress address = new InetSocketAddress(6666); channel.socket().bind(address); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { e.printStackTrace(); } } // 监听客户端变化 public void listenClient() throws Exception { System.out.println("服务端开始监听客户端变化"); while (true) { //获取需要处理的事件 int num = selector.select(); if (num == 0) { continue; } Set<SelectionKey> set = selector.selectedKeys(); Iterator<SelectionKey> iterator = set.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isAcceptable()) { //获取连接 SocketChannel Clientchannel = channel.accept(); //设置非阻塞 Clientchannel.configureBlocking(false); //注册任务 Clientchannel.register(selector, SelectionKey.OP_READ); //打印用户上线日志 System.out.println("用户:" + Clientchannel.socket().getRemoteSocketAddress() + "上线了"); continue; } else if (key.isReadable()) { readData(key); } } } } //读取信息 public void readData(SelectionKey key) { SocketChannel channel = null; try { //获取当前的信道 channel = (SocketChannel) key.channel(); //创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); int read = channel.read(buffer); //>0代表有可读取的内容 if (read > 0) { String msg = "用户" + channel.socket().getRemoteSocketAddress() + ":" + new String(buffer.array()); //打印信息 System.out.println(msg); sendToOther(msg, channel); } } catch (Exception e) { System.out.println("用户:" + channel.socket().getRemoteSocketAddress() + "下线了"); key.cancel(); try { channel.close(); } catch (Exception e1) { e1.printStackTrace(); } } } //信息分发 public void sendToOther(String msg, SocketChannel selfSocketChannel) throws Exception { //获得所有的信道,可以理解为获取所有的用户 Set<SelectionKey> set = selector.keys(); for (SelectionKey key : set) { Channel channel = key.channel(); //判断这是一个用户,并且不是发信息的那个人 //这个是一个 socketchannel 并且不等价与我们发信息的 selfsocketchannel if (channel instanceof SocketChannel && channel != selfSocketChannel) { SocketChannel socketChannel = (SocketChannel) channel; ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); socketChannel.write(buffer); } } } //启动客户端,等待连接 public static void main(String[] args) throws Exception { chatServer chatServer = new chatServer(); chatServer.listenClient(); }
客户端
---------------------初始化------------------------------
1.连接服务端通道
2.开启选择器
3.注册任务,打印上线日志
--------------------发送信息-------------------
1.向服务端发送信息
--------------------读取信息---------------------
1.接收服务端分发的消息(自己除外)
//服务端通道 private SocketChannel channel; // 多路复用选择器 private Selector selector; public chatClient() throws Exception { channel = SocketChannel.open(); selector = Selector.open(); SocketAddress address = new InetSocketAddress("127.0.0.1", 6666); channel.connect(address); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); System.out.println("用户:" + channel.getLocalAddress() + "上线了"); } public void sendData(String msg) { try { ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); channel.write(buffer); } catch (IOException e) { e.printStackTrace(); } } public void readData() throws Exception { SocketChannel channel = null; while (true) { int num = selector.select(); if (num == 0) { continue; } Set<SelectionKey> set = selector.selectedKeys(); Iterator<SelectionKey> iterator = set.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isReadable()) { try { //获取当前的信道 channel = (SocketChannel) key.channel(); //创建缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); int read = channel.read(buffer); //>0代表有可读取的内容 if (read > 0) { String msg = new String(buffer.array()); System.out.println(msg); } } catch (Exception e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws Exception { final chatClient client = new chatClient(); new Thread() { @Override public void run() { while (true) { try { //一直阅读是否有信息 client.readData(); //每一秒阅读一次 sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } }.start(); Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String str = scanner.nextLine(); client.sendData(str); } }
案例测试
只需要创建两个类,将客户端和服务端的代码放入IDE就可以启动代码了,小冷保证开箱即用哦~
客户端想要多开的话,打开这个选项就可以开很多个客户端程序了
效果图
服务端日志
客户端看到的信息