1、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
| package com.example.part_02_nio.demo001;
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator;
public class Server { private Selector selector;
public void initServer(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.socket().bind(new InetSocketAddress(port)); this.selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); }
public void listen() throws IOException { System.out.println("服务端启动成功!"); while (true) { selector.select(); Iterator<?> ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); ite.remove(); handler(key); } } }
public void handler(SelectionKey key) throws IOException { if (key.isAcceptable()) { handlerAccept(key); } else if (key.isReadable()) { handelerRead(key); } }
public void handlerAccept(SelectionKey key) throws IOException { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); channel.configureBlocking(false);
ByteBuffer outBuffer = ByteBuffer.wrap("成功建立连接".getBytes()); channel.write(outBuffer); System.out.println("新的客户端连接"); channel.register(this.selector, SelectionKey.OP_READ); }
public void handelerRead(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int read = channel.read(buffer); if(read > 0){ byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服务端收到信息:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(("好的" + msg).getBytes()); channel.write(outBuffer); }else{ System.out.println("客户端关闭"); key.cancel(); } }
public static void main(String[] args) throws IOException { Server server = new Server(); server.initServer(8899); server.listen(); }
}
|
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
| package com.example.part_02_nio.demo001;
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel;
public class Client { public static void main(String[] args) {
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8899);
SocketChannel sc = null;
ByteBuffer buffer = ByteBuffer.allocate(1024);
try { sc = SocketChannel.open(); sc.connect(address); while (true) { int read = sc.read(buffer); if(read > 0) { byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("客户端端收到信息:" + msg); buffer.clear(); } byte[] bytes = new byte[1024]; System.in.read(bytes);
buffer.put(bytes); buffer.flip(); sc.write(buffer); buffer.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { if (sc != null) { try { sc.close(); } catch (IOException e) { e.printStackTrace(); } } }
}
}
|
2、图解BIO与NIO
BIO
假设我们的应用系统是一家餐厅,那么可以把ServerSocket理解成餐厅的大门,以欢迎前来用餐的客人(即客户端),每来一个客人就需要一个服务员(线程)接待。
NIO
同样假设为餐厅,可以把Selector理解为服务员,服务员并不需要时刻陪在每一个客人身边,只有当客人有需求的时候,如点餐、上菜、结账等,才需要服务员接待。这样就节省了很多服务员(线程)。
3、关于阻塞与非阻塞
阻塞与非阻塞说的是IO。
- BIO:当发起IO的读或写操作时,均为阻塞方式,直到应用程序从流中读到数据或者将数据写入到流中。
- NIO:基于事件驱动思想,当发起IO请求时,应用程序是非阻塞的。当有流可读或写的时候,由操作系统通知应用程序,应用程序再将流读取到缓冲区或者写入系统。
4、关于同步与异步
AIO:同样基于事件驱动的思想,在进行I/O操作时,直接调用API的read或write,这两种方法均为异步。对于读操作,操作系统将数据读到缓冲区,并通知应用程序,对于写操作,操作系统将write方法传递的流写入并主动通知应用程序。它节省了NIO中遍历事件通知队列的代价。
这里注意比较NIO和AIO的不同,AIO是操作系统完成IO并通知应用程序,NIO是操作系统通知应用程序,由应用程序完成。
5、selector.select()方法
select()
方法会一直阻塞,直到有注册的事件触发。
若不想一直阻塞,可通过其他方法实现:
select(long timeout)
:超时返回
selectNow()
:立马返回
wakeup()
:唤醒阻塞的selector
6、SelectionKey.OP_WRITE
写就绪相对有一点特殊,一般来说,你不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程会占用整个CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。
当有数据在写时,将数据写到缓冲区中,并注册写事件:
1 2 3 4
| public void write(byte[] data) throws IOException { writeBuffer.put(data); key.interestOps(SelectionKey.OP_WRITE); }
|
注册写事件后,写操作就绪,这时将之前写入缓冲区的数据写入通道,并取消注册:
1 2
| channel.write(writeBuffer); key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
|