Java 非阻塞式NIO 案例(实现多人聊天功能)
一、使用Java NIO完成网络通信的三个核心
1.通道(Channel):负责连接
java.nio.channels.Channel 接口:
|--SelectableChannel
|--SocketChannel
|--ServerSocketChannel
|--DatagramChannel
|--Pipe.SinkChannel
|--Pipe.SourceChannel
2.缓冲区(buffer):负责数据存取
3.选择器(Selector):是SelectableChannel 的多路复用器,用来检测SelectableChannel的IO状态
案例:使用非阻塞式实现简单的群聊天系统
一、实现客户端
1 public static void main(String[] args) throws Exception { 2 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8989)); 3 4 //2. 切换非阻塞模式 5 sChannel.configureBlocking(false); 6 7 //3. 分配指定大小的缓冲区 8 ByteBuffer buf = ByteBuffer.allocate(1024); 9 10 //4. 发送数据给服务端 11 Scanner scan = new Scanner(System.in); 12 13 while (scan.hasNext()) { 14 String str = scan.next(); 15 buf.put((new Date().toString() + "\n" + str).getBytes()); 16 buf.flip(); 17 sChannel.write(buf); 18 buf.clear(); 19 } 20 21 //5. 关闭通道 22 sChannel.close(); 23 }
二、实现服务端
@Test public void server() { ServerSocketChannel ssChannel = null; try { ssChannel = ServerSocketChannel.open(); //配置非阻塞 ssChannel.configureBlocking(false); //绑定连接 ssChannel.bind(new InetSocketAddress(8989)); Selector selector = Selector.open(); //将通道注册到监听器中,并且制定监听器的监听模式为“接受” ssChannel.register(selector, SelectionKey.OP_ACCEPT); //轮询的选择已经就绪的事件 while (selector.select() > 0) { //获取当前监听 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { //获取准备就绪的事件 SelectionKey sk = it.next(); if (sk.isAcceptable()) { //如果接受就绪,则获取客户端的连接 SocketChannel clientChannel = ssChannel.accept(); //同样配置成非阻塞式 clientChannel.configureBlocking(false); //把客户端的连接注册到选择器上 clientChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //如果读取就绪,则获取读取的通道 SocketChannel socketChannel = (SocketChannel) sk.channel(); //配置成非阻塞模式 socketChannel.configureBlocking(false); //读取数据 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int len = 0; while ((len = socketChannel.read(byteBuffer)) > 0) { byteBuffer.flip(); System.out.println(new String(byteBuffer.array(), 0, len)); byteBuffer.clear(); } } it.remove(); } } } catch (IOException e) { e.printStackTrace(); } finally { if(ssChannel!=null){ try { ssChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
注意:这里服务端用到的org.junit.Test;这个包方便测试,客户端因为需要读取输入所以写在Main函数中(@Test方法中测试出来好像不能读取输入)
需要下载包的地址如下:
链接:https://pan.baidu.com/s/14ZHHOnAD3ldNVcA3pmCoJQ
提取码:uqd9
DatagramChannel(UDP)的使用方法(和上个案例大同小异)
public static void main(String args[]) { DatagramChannel datagramChannel = null; try { datagramChannel = DatagramChannel.open(); datagramChannel.configureBlocking(false); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String str = scanner.next(); byteBuffer.put(str.getBytes()); byteBuffer.flip(); datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 9897)); byteBuffer.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { if(datagramChannel!=null){ try { datagramChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test public void server(){ DatagramChannel datagramChannel = null; try { datagramChannel=DatagramChannel.open(); datagramChannel.bind(new InetSocketAddress(9897)); datagramChannel.configureBlocking(false); Selector selector = Selector.open(); datagramChannel.register(selector, SelectionKey.OP_READ); while(selector.select()>0){ Iterator<SelectionKey> st=selector.selectedKeys().iterator(); while(st.hasNext()){ SelectionKey sk=st.next(); if(sk.isReadable()){ ByteBuffer btf=ByteBuffer.allocate(1024); datagramChannel.receive(btf); btf.flip(); System.out.println(new String(btf.array(),0,btf.limit())); btf.clear(); } } st.remove(); } } catch (IOException e) { e.printStackTrace(); } finally { if(datagramChannel!=null){ try { datagramChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
Pipe简介
pipe是两个线程之间单项数据连接,Pipe有两个数据通道,Sign通道负责写入,Source通道负责读取。
案例如下:
@Test public void test() throws Exception { Pipe pipe = Pipe.open(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put("hello world".getBytes()); Pipe.SinkChannel sinkChannel=pipe.sink(); byteBuffer.flip(); sinkChannel.write(byteBuffer); //读取 Pipe.SourceChannel sourceChannel =pipe.source(); byteBuffer.flip(); int len=sourceChannel.read(byteBuffer); System.out.println("sourceChanel:"+new String(byteBuffer.array(),0,len)); byteBuffer.clear(); sinkChannel.close(); sourceChannel.close(); }
谢谢浏览,如有问题直接评论,我会及时更改我的错误。