nio socket聊天室
io socket通过不断新线程的方式,这会导致占用大量资源。因此在jdk1.4时提出新的解决方案:NIO。
java nio的几个核心部分:
- Channel
- Buffer
- Selector
Channel:通道,是I/O操作的关系。表示与实体之间的打开连接。实体包含硬件设备、文件、网络连接或一组程序组件(能够执行一个或多个不同的I/O操作,如读/写)。常用于多线程访问是安全的
主要实现类:
- DatagramChannel:用于面向数据报的套接字的可选通道
- FileChannel:读取、写入、映射和操作文件的通道。
- ServerSocketChannel:用于面向流的侦听插座的可选通道。
- SocketChannel:用于面向流的连接套接字的可选通道。
Buffer:基于特定的原始类型的容器。缓冲区是特定原始类型元素的线性有限序列。
Selector:多路复用器。Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便
三者的关系图:
服务端:
public class NioServer { private static Map<String, SocketChannel> clientMap = new ConcurrentHashMap<>(); public static void main(String[] args) { try { //open()创建一个server socket channel,不可能为任意的、预先存在的ServerSocket创建通道。 //此只有创建,而未有bind, 调用accept()会有异常:NotYetBoundException ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //绑定server socket serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",8989)); serverSocketChannel.configureBlocking(false); //channel的一种多路复用器 Selector selector = Selector.open(); //注册一个连接监听事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("nio server start success ! "); while (true){ //阻塞操作,等待事件 selector.select(); //返回所关注事件集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); //开始只注册一个事件,当连接建立后,再进行其他事件注册 selectionKeys.forEach(key -> { try { SocketChannel socketChannel ; if (key.isAcceptable()){ //获取当前事件发生在哪个channel上 ServerSocketChannel channelServer = (ServerSocketChannel) key.channel(); //一直阻塞到有新连接到达 socketChannel = channelServer.accept(); //必须要先设置成非阻塞,否则异常:java.nio.channels.IllegalBlockingModeException socketChannel.configureBlocking(false); //channel上注册读事件 socketChannel.register(selector, SelectionKey.OP_READ); String clientKey = UUID.randomUUID().toString(); clientMap.put(clientKey,socketChannel); }else if (key.isReadable()){//接收 //创建读取缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); socketChannel = (SocketChannel) key.channel(); //连接channel的key String readerKey = "" ; for (Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){ if (entry.getValue() == socketChannel){ readerKey = entry.getKey(); break; } } int count = socketChannel.read(byteBuffer); if (count > 0){ //读取客户端数据,进行写数据 byteBuffer.flip(); byte[] data = new byte[count]; System.arraycopy(byteBuffer.array(), 0, data, 0, count); String message = String.format("%s:\t%s",readerKey,new String(data)); System.out.println(message); //向所有connection发送消息 for (Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){ SocketChannel clientSocket = entry.getValue(); ByteBuffer clientBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); clientSocket.write(clientBuffer); } } else { System.out.println("客户端关闭"); key.cancel(); clientMap.remove(readerKey); } } } catch (IOException e) { e.printStackTrace(); } }); //使用完selectKey之后一定要删除掉 //如果不清除,下次循环还是会进行处理,会报空指针异常 selectionKeys.clear(); } } catch (IOException e) { e.printStackTrace(); } } }
客户端:
try { SocketChannel client = SocketChannel.open(); client.configureBlocking(false); Selector selector = Selector.open(); client.register(selector, SelectionKey.OP_CONNECT); client.connect(new InetSocketAddress("127.0.0.1",8989)); System.out.println("client start success!"); while (true){ //阻塞,直至通道连接 selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); for (SelectionKey key : keys){ if (key.isConnectable()){ System.out.println("connection is success"); SocketChannel socketChannel = (SocketChannel) key.channel(); //连接是否挂起 //检查此通道的连接操作是否正在进行 //当且仅当此通道上的连接操作已初始化,但尚未通过调用finishConnect方法完成时为true if (socketChannel.isConnectionPending()){ //调用此方法来完成连接序列。 //如果此通道已经连接,则此方法将不会阻塞,并将立即返回true。 // 如果此通道处于非阻塞模式,则如果连接进程尚未完成,此方法将返回false。 // 如果此通道处于阻塞模式,则此方法将阻塞,直到连接完成或失败,并始终返回true或抛出描述失败的检查异常。 //这个方法可以在任何时候调用。如果在此方法的调用过程中调用了该通道上的读或写操作,则该操作将首先阻塞,直到该调用完成。 socketChannel.finishConnect(); String message = "connection success:\t"+ LocalDateTime.now(); ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)); socketChannel.write(writeBuffer); ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()); executorService.submit(()->{ while (true){ //清除这个缓冲区。位置设置为零,限制设置为容量,标记被丢弃。实际上并没有擦除缓冲区中的数据 writeBuffer.clear(); //外部输入 InputStreamReader streamReader = new InputStreamReader(System.in); BufferedReader bufferedReader = new BufferedReader(streamReader); String sendMessage = bufferedReader.readLine(); //发送 writeBuffer.put(sendMessage.getBytes(StandardCharsets.UTF_8)); writeBuffer.flip(); socketChannel.write(writeBuffer); } }); //注册读事件, 接收服务器消息 socketChannel.register(selector, SelectionKey.OP_READ); } } else if (key.isReadable()){ SocketChannel socketChannel = (SocketChannel) key.channel(); //创建读取缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int count = socketChannel.read(byteBuffer); if (count > 0){ // byteBuffer.flip(); // byte[] data = new byte[count]; // System.arraycopy(byteBuffer.array(), 0, data, 0, count); String message = new String(byteBuffer.array(),0,count); System.out.println(message); } } } keys.clear(); } } catch (IOException e) { e.printStackTrace(); }