1.NIO中的一些基本概念
1.1Buffer(缓冲区)与Channel(通道)
缓冲区和通道是NIO中的核心对象,通道Channel是对原IO中流的模拟,所有数据都要通过通道进行传输;Buffer实质上是一个容器对象,发送给通道的所有对象都必须首先放到一个缓冲区中。
- 缓冲区:Buffer可以理解为一个对象,一个数组。一块连续的内存块,是NIO中数据读或写的中转地。
- 通道:Channel 是一个对象,可以通过它读取和写入数据。
两者的关系:
所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
简单的说Channel是:数据的源头或者数据的目的地,用于向buffer提供数据或者读取buffer数据,并且对I/O提供异步支持。
1.2读与写数据
1.首先给Buffer分配空间,以字节为单位
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
创建一个ByteBuffer对象并且指定内存大小
2.向Buffer中写入数据:
1).数据从Channel到Buffer:channel.read(byteBuffer);
2).数据从Client到Buffer:byteBuffer.put(...);
3.从Buffer中读取数据:
1).数据从Buffer到Channel:channel.write(byteBuffer);
2).数据从Buffer到Server:byteBuffer.get(...);
2.单线程处理客户端请求
public class Server {
public static void main(String[] args) throws IOException {
// 通道是双向的。可以同时读写
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
ssc.configureBlocking(false);
System.out.println("server start,listening on :" + ssc.getLocalAddress());
// 打开某个selector
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handle(key);
}
}
}
private static void handle(SelectionKey key) {
if (key.isAcceptable()) {
try {
// 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector上
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 在通过Selector监听Channel时对什么事件感兴趣
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
// 选择器对通道的监听事件,可以监听的时间类型
sc.register(key.selector(), interestSet);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
} else if (key.isReadable()) { //读取消息
SocketChannel sc = null;
try {
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.clear();
int len = sc.read(buffer);
if (len != -1) {
System.out.println(new String(buffer.array(), 0, len));
ByteBuffer byteBuffer = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(byteBuffer);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(sc !=null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
3.多线程处理客户端请求
public class PoolServer {
ExecutorService pool = newFixedThreadPool(20);
private Selector selectctor;
public static void main(String[] args) throws IOException {
PoolServer server = new PoolServer();
server.initServer(8000);
server.listen();
}
private void initServer(int port) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
this.selectctor = Selector.open();
// 将上述的通道管理器和通道绑定,并为该通道注册OP_ACCEPT事件
// 注册事件后,当该事件到达时,selector.select()会返回(一个key),如果该事件没到达selector.select()会一直阻塞
serverSocketChannel.register(selectctor, SelectionKey.OP_ACCEPT);
System.out.println("服务端启动成功!");
}
private void listen() throws IOException {
while (true) {
selectctor.select();
Iterator ite = this.selectctor.selectedKeys().iterator(); //如果channel有数据了,将生成的key访入keys集合中
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next(); //得到集合中的一个key实例
ite.remove();
if (key.isAcceptable()) { //判断当前key所代表的channel是否在Acceptable状态,如果是就进行接收 接收客户端发来的消息
// 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector上
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 监控read 注册OP_READ。可以接受客户端发送过来的请求。
sc.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
// 用多线程去读取数据
pool.execute(new ThreadHandlerChannel(key));
}
}
}
}
}
class ThreadHandlerChannel extends Thread {
private SelectionKey key;
public ThreadHandlerChannel(SelectionKey key) {
this.key = key;
}
@Override
public void run() {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
int size = 0;
// 有数据从Channel到Buffer
// 要从SocketChannel中读取数据,调用一个read()的方法之一。以下是例子:
// 首先,分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。 ByteBuffer buffer = ByteBuffer.allocate(1024);
// read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。
while ((size = channel.read(buffer)) > 0) {
// buffer.flip();
// // 数据从buffer写到channel
// baos.write(buffer.array(), 0, size);
// buffer.clear();
byte[] data = buffer.array();
String info = new String(data).trim();
System.out.println("从客户端发送过来的消息是:"+info);
}
System.out.println(baos.toString());
baos.close();
byte[] content = baos.toByteArray();
ByteBuffer writeBuf = ByteBuffer.allocate(content.length);
writeBuf.put(content);
writeBuf.flip();
channel.write(writeBuf);
if (size == -1) {
channel.close();
} else {
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
key.selector().wakeup();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
4.客户端发送消息
public class NIOClient {
private Selector selector; //创建一个选择器
private final static int port = 8686;
private final static int BUF_SIZE = 10240;
private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
private void initClient() throws IOException {
this.selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(port));
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()){
doConnect(key);
// 服务端发送给客户端的消息。客户端去读
}else if (key.isReadable()){
doRead(key);
}
}
}
}
public void doConnect(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()){
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
String info = "服务端你好!!";
System.out.println("连接成功");
byteBuffer.clear();
byteBuffer.put(info.getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
//clientChannel.register(key.selector(),SelectionKey.OP_READ);
clientChannel.close();
}
public void doRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.read(byteBuffer);
byte[] data = byteBuffer.array();
String msg = new String(data).trim();
System.out.println("服务端发送消息:"+msg);
clientChannel.close();
key.selector().close();
}
public static void main(String[] args) throws IOException {
NIOClient nioClient = new NIOClient();
nioClient.initClient();
}
}
5.执行流程
1.客户端isConnectable为true,即连接成功后会发送“服务端你好”。
2.服务端会先判断isAcceptable为true。并将其注册read事件。可以接收客户端发来的消息。
3.服务端判断isRead为true,会读取客户端消息,并执行其中的方法。
容易混淆的点:服务端代码:监控read 注册OP_READ。可以读取客户端发送过来的请求。 key.isReadable()读取从客户端发来的消息
客户端代码:key.isReadable()读取从服务端发来的消息