NIO编程
传统的TCP和UDP通讯:Blocking I/O(阻塞,需要等待慢的一方,效率不高)
Non-Blocking I/O
- 提供非阻塞通讯等方式
- 避免同步I/O通讯效率过低
- 一个线程可以管理多个连接
- 减少线程多的压力
三个重要的类
- Buffer缓存区
- Channel通道
- Selector多路选择器
Buffer缓存区,一个可以读写的内存区域
- ByteBuffer, CharBuffer, DoubleBuffer, IntBuffer, LongBuffer, ShortBuffer(StringBuffer 不是Buffer缓冲区)
- capacity容量,position读写位置
- limit界限,mark标记,用于重复一个读/写操作
Channel通道
- 全双工的,支持读/写(而Stream流是单向的)
- 支持异步读写
- 和Buffer配合,提高效率
- ServerSocketChannel 服务器 TCP Socket接入通道,接收客户端
- SocketChannel TCP Socket通道,可支持阻塞/非阻塞通讯
- DatagramChannel UDP 通道
- FileChannel 文件通道
Selector多路选择器
- 每隔一段时间,不断轮询注册在其上的Channel
- 如果有一个Channel有接入、读、写操作,就会被轮询出来
- 根据SelectionKey可以获取相应的Channel,然后后续IO操作
- 避免过多的线程
- SelectionKey四种类型
- OP_CONNECT
- OP_ACCEPT
- OP_READ
- OP_WRITE
NIO Server 程序
package com.lihuan.network.demo05;
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;
import java.util.Set;
public class NioServer {
public static void main(String[] args) {
int port = 8001;
Selector selector = null;
ServerSocketChannel serverChannel = null;
try {
//产生出一个多路选择器
selector = Selector.open();
//建立通道,等待客户端连接
serverChannel = ServerSocketChannel.open();
//配置为非阻塞模式
serverChannel.configureBlocking(false);
//驻守在8001端口,绑定端口
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器在8001端口等待...");
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
while (true){
try {
//轮询
selector.select(1000);
//获取有响应的selectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while (it.hasNext()){
key = it.next();
it.remove();
try{
handleInput(selector, key);
}catch (Exception e){
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void handleInput(Selector selector, SelectionKey key) throws IOException {
if(key.isValid()){
//处理新接入的请求消息
if(key.isAcceptable()){
//接收一个新的连接
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
if(key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if(readBytes > 0){
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String request = new String(bytes, "UTF-8");
System.out.println("client said: " + request);
String response = request + " 666";
doWrite(sc, response);
}else if(readBytes < 0){
key.cancel();
sc.close();
}else ;
}
}
}
private static void doWrite(SocketChannel sc, String response) throws IOException {
if(response != null && response.trim().length() > 0){
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
sc.write(writeBuffer);
}
}
}
NIO Client 程序
package com.lihuan.network.demo05;
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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
public class NioClient {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8001;
Selector selector = null;
SocketChannel socketChannel = null;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//如果连接成功,则注册到多路复用器上,发送请求消息,读应答
if(socketChannel.connect(new InetSocketAddress(host, port))){
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}else{
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (true){
try {
//轮询
selector.select(1000);
//获取有响应的selectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while (it.hasNext()){
key = it.next();
it.remove();
try {
handleInput(selector, key);
} catch (InterruptedException e) {
e.printStackTrace();
if (key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void doWrite(SocketChannel socketChannel) throws IOException {
byte[] str = UUID.randomUUID().toString().getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
writeBuffer.put(str);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
private static void handleInput(Selector selector, SelectionKey key) throws IOException, InterruptedException {
if(key.isValid()){
//判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if(key.isConnectable()){
if(sc.finishConnect()){
sc.register(selector,SelectionKey.OP_READ);
}
}
if(key.isReadable()){
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if(readBytes > 0){
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("server said: " + body);
}else if(readBytes < 0){
key.cancel();
sc.close();
}
}
Thread.sleep(3000);
doWrite(sc);
}
}
}