java nio attachment_Java NIO的总结

Java的NIO是非阻塞式的IO

(non-blocking io)

一、理解同步与异步、阻塞与非阻塞

(1)同步和异步

​   同步和异步描述的是一种消息通知的机制,主动等待消息返回还是被动接受消息。同步io指的是调用方通过主动等待获取调用返回的结果来获取消息通知,而异步io指的是被调用方通过某种方式(如,回调函数)来通知调用方获取消息。

(2)阻塞非阻塞

​   阻塞和非阻塞描述的是调用方在获取消息过程中的状态,阻塞等待还是立刻返回。阻塞io指的是调用方在获取消息的过程中会挂起阻塞,直到获取到消息,而非阻塞io指的是调用方在获取io的过程中会立刻返回而不进行挂起。

Java NIO是基于IO多路复用模型,也就是我们经常提到的select,poll,epoll。IO 多路复用本质是同步IO,其需要调用方在读写事件就绪时主动去进行读写。在Java NIO中,通过selector来获取就绪的事件,当selector上监听的channel中没有就绪的读写事件时,其可以直接返回,或者设置一段超时后返回。可以看出Java NIO可以实现非阻塞,而不像传统IO里必须阻塞当前线程直到可读或可写。

所以理解阻塞与非阻塞其实是理解传统IO区别于NIO的对于线程的阻塞与否。

Java NIO 处理连接和 Java socket 处理连接的方式:

1 //java nio

2 while(true) {3 ......4 selector.select(1);5 Set selectionKeySet=selector.selectedKeys();6 ......7 //处理selectionKeySet中事件,线程没有阻塞

8 }9

10 //java socket处理连接,线程会阻塞

11 while(true) {12 ......13 Socket socket =serverSocket.accept();14 InputStream in =socket.getInputStream();15 ......16 //处理in中内容

17 }

二、NIO的常用操作(对文件的读写)

(1)写操作

通过NIO的操作能达到和IO一样的效果,但是读写的操作是通过通道来完成的,但是通道是通过流获取的。(注意标准的步骤)

而且里面的通道比如FileChannel都是从流中获取到的。

通道中后面要读取数据并存入缓冲区,同时要分配缓冲区以一定的大小。可以认为这个通道是直接搭在数据上的。

缓冲区里得存放字节数组。

1 packageNIOTest;2

3 importjava.io.FileOutputStream;4 importjava.io.IOException;5 importjava.nio.ByteBuffer;6 importjava.nio.channels.FileChannel;7

8 importorg.junit.Test;9

10 //通过NIO实现文件IO

11 public classTestNio {12 @Test13 //往本地文件中写数据

14 public void test1() throwsIOException {15 //1、创建输出流

16 FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\1.txt");17 //2、从流中得到一个通道

18 FileChannel fileChannel =fileOutputStream.getChannel();19 //3、提供一个缓冲区

20 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);21 //4、往缓冲区中存入数据(将字符串转换成字节数组并存储到缓冲区中)

22 String string = "hello,nio";23 byteBuffer.put(string.getBytes());24 //5、反转缓冲区

25 byteBuffer.flip();26 //6、把缓冲区写到通道中

27 fileChannel.write(byteBuffer);28 //7、关闭流(关闭流即可关闭通道)

29 fileOutputStream.close();30 }31 }

效果:

ea2a5fb8e9ac59cc0d9aab7408b4bfbf.png

(2)读操作

输入输出流里面放的一般认为是文件的路径,但是实际上可以看作是一个file。

其中的file.length()是为了获得文件的数据内容的大小,但是默认返回的是长整型,所以需要通过int转换一下。关闭流就相当于关闭了通道。

1 @Test2 public void test2() throwsIOException {3

4 //封装成一个文件对象(为了返回文件中的数据内容的长度和大小)

5 File file = new File("C:\\Users\\Administrator\\Desktop\\1.txt");6 //创建输入流

7 FileInputStream fileInputStream = newFileInputStream(file);8 //创建文件通道

9 FileChannel fileChannel =fileInputStream.getChannel();10 //创建缓冲区(设置文件有多少数据缓冲区就有多大引入File)

11 ByteBuffer buffer = ByteBuffer.allocate((int) file.length());12 //从通道中读取数据并存到缓冲区中

13 fileChannel.read(buffer);14 //把缓冲区中的数据转换成字节数组并转换成String对象

15 System.out.println(newString(buffer.array()));16 fileInputStream.close();17 }

(3)文件的复制操作

文件从一个文件复制到另一个文件中去,通过通道流的数据的复制来完成。

1 @Test2 public void test3() throwsIOException {3 //创建两个流

4 FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\1.txt");5 FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\2.txt");6 //得到两个通道

7 FileChannel fileChannel_read =fileInputStream.getChannel();8 FileChannel fileChannel_write =fileOutputStream.getChannel();9 //复制(从读通道流中复制数据到写通道流)

10 fileChannel_write.transferFrom(fileChannel_read, 0, fileChannel_read.size());11 //关闭

12 fileChannel_read.close();13 fileChannel_write.close();14 }

这就是数据的交换,不需要通过缓冲区把数据取出来单独处理,而是直接通过搭建两个流通道进行复制操作就可以完成数据的转移。

三、NIO的常用操作(数据的交换)

Java NIO里面最为重要也是常用的是四大类,选择器,事件,服务端通道,客户端通道。类比处理连接区别于Socket,最大的不同就是通道的概念的引进。

主要是四大类:

(1)Selector

(2)SelectionKey

(3)ServerSocketChannel

(4)SocketChannel

782ee0295fe62eb81e20254783312a6c.png

(1)网络客户端程序:

1 //网络客户端程序

2 public classNIOClient {3 public static void main(String[] args) throwsIOException {4

5 //1、得到一个网络通道

6 SocketChannel socketChannel =SocketChannel.open();7

8 //2、设置非阻塞的方式

9 socketChannel.configureBlocking(false);10

11 //3、提供服务器端的IP地址和端口号

12 InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9999);13

14 //4、连接服务器端

15 if (socketChannel.connect(inetSocketAddress) == false) {16 while (!socketChannel.finishConnect()) {17 System.out.println("客户端重连......");18 }19 }20

21 //5、得到一个用于读写的缓冲区,并存入数据

22 String msg = "hello Server";23 ByteBuffer byteBuffer =ByteBuffer.wrap(msg.getBytes());24

25 //6、发送数据到通道

26 socketChannel.write(byteBuffer);27 System.in.read();28 }29 }

当socketChannel.connect(inetSocketAddress) = false时再次想要连接就得使用socketChannel.finishConnect(),而有可能连接不能顺利的连接上,所以要一直判断,故while (!socketChannel.finishConnect())就可以当连接失败的时候一直处于重连的情况。

不能立即把socketChannel立即关闭,不然服务器端会报异常,所以用等待控制台系统输入阻断程序完毕。

(2)服务器端程序

1 public classNIOServer {2 public static void main(String[] args) throwsException {3

4 //1、得到一个ServerSocketChannel对象 老大

5 ServerSocketChannel serverSocketChannel =ServerSocketChannel.open();6

7 //2、得到一个Selector对象

8 Selector selector =Selector.open();9

10 //3、绑定一个端口号(设置服务器的端口号)

11 serverSocketChannel.bind(new InetSocketAddress(9999));12

13 //4、设置非阻塞的方式

14 serverSocketChannel.configureBlocking(false);15

16 //5、ServerSocketChannel注册的事件就是SelectionKey.OP_ACCEPT看是否有连接,连接到ServerSocketChannel17 //这些注册的通道与事件的关系都是统一由selector调度的

18 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);19

20 //6、服务器的业务逻辑

21 while (true) {22 //select当有注册的IO可以进行操作(操作其对应的注册时的方式时)时,将对应的SelectionKey加入到内部集合并返回(非阻塞)23 //监控客户端(看是否有通道的事件被触发)

24 if (selector.select(2000) == 0) {25 System.out.println("等待连接......");26 continue;27 }28 //得到SelectionKey,判断通道里的事件

29 Iterator Iterator =selector.selectedKeys().iterator();30 while(Iterator.hasNext()) {31 SelectionKey key =Iterator.next();32 if(key.isAcceptable()) {33 //客户端连接事件

34 System.out.println("OP_READ");35 SocketChannel socketChannel =serverSocketChannel.accept();36 socketChannel.configureBlocking(false);37 socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));38 }39 if(key.isReadable()) {40 //读取客户端事件

41 SocketChannel socketChannel =(SocketChannel) key.channel();42 ByteBuffer buffer =(ByteBuffer) key.attachment();43 //从通道里面读取数据然后存放在buffer里面

44 socketChannel.read(buffer);45 //然后将buffer里面的数据转换成字节数组之后通过String包装起来后打印

46 System.out.println("客户端发来数据:" + newString(buffer.array()));47 }48 Iterator.remove();49 if(key.isWritable()) {50 //写到客户端事件

51

52 }53 }54 }55 }56 }

三、NIO的常用操作(多人聊天室实现多通道数据的连接)

(1)服务器ChatServer

接收客户端发来的数据,同时还要把这个数据广播给另外的其他的所有客户端。服务器最重要的代码是进行业务处理,也就是集中在服务器代码中处理selector,通过筛选selector的Keys来判断每一个注册到selector的所有的通道是否发生了事件,发生的事件是什么。

1 public classChatServer {2 private ServerSocketChannel listenerChannel;//监听通道

3 private Selector selector;//选择器对象

4 private static final int port = 9999;//服务器端口

5

6 publicChatServer() {7 try{8 //1、得到监听通道

9 listenerChannel =ServerSocketChannel.open();10 //2、得到选择器

11 selector =Selector.open();12 //3、绑定端口

13 listenerChannel.bind(newInetSocketAddress(port));14 //4、设置为非阻塞模式

15 listenerChannel.configureBlocking(false);16 //5、将选择器绑定到监听通道并监听accept事件

17 listenerChannel.register(selector, SelectionKey.OP_ACCEPT);18 printInfo("Chat Server is ready......");19 } catch(IOException e) {20 e.printStackTrace();21 }22 }23

24 //6、干活儿

25 public void start() throwsIOException {26 //一直监控

27 while (true) {28 if (selector.select(2000) == 0) {29 System.out.println("等待连接......");30 continue;31 }32 //我的所有的key都在selector.selectedKeys()里面,利用key触发监听事件来确定进行怎样的操作

33 Iterator iterator =selector.selectedKeys().iterator();34 while(iterator.hasNext()) {35 SelectionKey key =iterator.next();36 if(key.isAcceptable()) {37 //连接请求

38 SocketChannel socketChannel =listenerChannel.accept();39 socketChannel.configureBlocking(false);40 //返回了连接之后注册读取监听事件

41 socketChannel.register(selector, SelectionKey.OP_READ);42 System.out.println(socketChannel.getRemoteAddress().toString().substring(1) + "上线了......");43 }44 if(key.isReadable()) {45 //读取数据请求

46 readMsg(key);47 }48 iterator.remove();49 }50 }51 }52

53 //读取客户端发来的消息并广播出去

54 private void readMsg(SelectionKey key) throwsIOException {55 SocketChannel socketChannel =(SocketChannel) key.channel();56 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);57 int count =socketChannel.read(byteBuffer);58 if (count > 0) {59 String msg = newString(byteBuffer.array());60 printInfo(msg);61 //发广播(排除掉当前的通道,自己发的广播再给自己就没有意义了)62 //意思就是说一个客户端发送了消息到服务器之后,经63 //服务器读取之后转发到所有的客户端去,以这种逻辑实现聊天室

64 broadCast(socketChannel, msg);65 }66

67 }68

69 //给所有的客户端发广播

70 public void broadCast(SocketChannel socketChannel, String msg) throwsIOException {71 System.out.println("服务器发送了广播......");72 //selector.keys()得到所有就绪的通道,即连接上服务器的通道(返回的值是SelectionKey)

73 for(SelectionKey selectionKey : selector.keys()) {74 //通过key得到的通道有可能不是SocketChannel类型的不能直接强转

75 Channel targetChannel =selectionKey.channel();76 if (targetChannel instanceof SocketChannel && targetChannel !=socketChannel) {77 SocketChannel destChannel =(SocketChannel) selectionKey.channel();78 ByteBuffer byteBuffer =ByteBuffer.wrap(msg.getBytes());79 destChannel.write(byteBuffer);80 }81 }82 }83

84 //和当前系统的时间进行一个拼接输入到控制台

85 private voidprintInfo(String str) {86 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");87 System.out.println("[" + simpleDateFormat.format(new Date()) + "] ->->" +str);88 }89

90 public static void main(String[] args) throwsIOException {91 newChatServer().start();92 }93 }

(2)客户端Client

1 public classChatClient {2 private final String HOST = "127.0.0.1";//服务器地址

3 private int PORT = 9999;//服务器端口

4 private SocketChannel socketChannel;//网络通道

5 private String userName;//聊天用户名

6

7 public ChatClient() throwsIOException {8 //得到一个网络通道

9 socketChannel =SocketChannel.open();10 //设置非阻塞

11 socketChannel.configureBlocking(false);12 //提供服务器的IP地址和端口号

13 InetSocketAddress inetSocketAddress = newInetSocketAddress(HOST, PORT);14 //连接服务器端

15 if (!socketChannel.connect(inetSocketAddress)) {16 while (!socketChannel.finishConnect()) {17 System.out.println("Client:连接中...");18 }19 }20 //得到客户端IP地址和端口信息,作为聊天用户名使用

21 userName = socketChannel.getLocalAddress().toString().substring(1);22 System.out.println("----------Client(" + userName + ") is ready----------");23 }24

25 //向服务器发送数据

26 public void sendMsg(String msg) throwsIOException {27 //首先进行判断,如果客户端发送的消息是“bye”的话就关闭这个连接通道

28 if (msg == "bye") {29 socketChannel.close();30 return;31 }32 msg = userName + " "+"发送:" +msg;33 //无论是读还是写都得通过ByteBuffer缓冲区来完成

34 ByteBuffer byteBuffer =ByteBuffer.wrap(msg.getBytes());35 //调用write方法就完成了写的操作

36 socketChannel.write(byteBuffer);37 }38

39 //从服务器读取数据

40 public void receiveMsg() throwsIOException {41 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);42 /**

43 * 通道就会读取数据然后存放到byteBuffer里面 同时read方法会返回一个int类型的整数值44 */

45 int count =socketChannel.read(byteBuffer);46 if (count > 0) {47 String mString = newString(byteBuffer.array());48 System.out.println("您收到一条消息:" +mString.trim());49 }50 }51 }

(3)测试

1 public classTestChat {2 public static void main(String[] args) throwsIOException {3 ChatClient chatClient = newChatClient();4 /**

5 * 发数据还比较好,直接sendMsg就可以了6 *7 * 但是收数据的话就得一直循环去接收服务器发来的数据,所以考虑开一个线程8 */

9 newThread() {10 public voidrun() {11 while (true) {12 try{13 chatClient.receiveMsg();14 Thread.sleep(2000);15 } catch(Exception e) {16 //TODO: handle exception

17 e.printStackTrace();18 }19 }20 }21 }.start();22 Scanner scanner = newScanner(System.in);23 while(scanner.hasNextLine()) {24 String msg =scanner.nextLine();25 chatClient.sendMsg(msg);26 }27 }28 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值