NIO (New I/O): NIO是一种同步非阻塞的I/O模型,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
public class NioServerHandle implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean started;
public NioServerHandle(int port) {
try {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
started = true;
System.out.println("服务端已启动,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop() {
started = false;
}
@Override
public void run() {
//循环遍历selector
while (started) {
try {
//阻塞,只有当至少一个注册的事件发生的时候才会继续
selector.select();
//获取当前有哪些事件可以使用
Set keys = selector.selectedKeys();
//转换为迭代器
Iterator iter = keys.iterator();
SelectionKey key = null;
while (iter.hasNext()) {
key = iter.next();
iter.remove();
try {
handleInput(key);
} catch (IOException e) {
e.printStackTrace();
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//selector关闭后会自动释放里面管理的资源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//处理业务逻辑
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//TODO:处理新接入的请求消息
if (key.isAcceptable()) {
//获取监听当前事件的channel
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//通过ServerSocketChannel的accept创建SocketChannel实例
//完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
SocketChannel sc = ssc.accept();
System.out.println("=====socket channel建立连接");
//设置为非阻塞
sc.configureBlocking(false);
//连接已经完成,开始监听读事件
sc.register(selector, SelectionKey.OP_READ);
}
//TODO:读取消息事件
if (key.isReadable()) {
System.out.println("=======socket channel数据准备完成,可以开始读取=======");
SocketChannel sc = (SocketChannel) key.channel();
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if (readBytes > 0) {
//将缓冲区当前的limit设置为position=0
//用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数组
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String message = new String(bytes, "UTF-8");
System.out.println("服务器接收到消息:" + message);
//处理数据
String result = response(message);
//发送应答消息
doWrite(sc, result);
}
//链路已关闭,释放资源
else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
//发送应答消息
private void doWrite(SocketChannel channel, String response) throws IOException {
//将消息编码为字节数组
byte[] bytes = response.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
buffer.put(bytes);
//flip操作
buffer.flip();
//发送缓冲区的字节数组
channel.write(buffer);
}
//返回给客户端的应答
public String response(String msg) {
return "Hello," + msg + ",Now is " + new java.util.Date(
System.currentTimeMillis()).toString();
}
}
public class NioClientHandle implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean started;
public NioClientHandle(String host, int port) {
this.host = host;
this.port = port;
try {
//创建选择器
selector = Selector.open();
//打开通道
socketChannel = SocketChannel.open();
//如果为true,则此通道将被置于阻塞模式
//如果为false,则此通道将被置于非阻塞模式
socketChannel.configureBlocking(false);
started = true;
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop() {
started = false;
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
//循环遍历selector
while (started) {
try {
//阻塞,只有当至少一个注册的事件发生的时候才会继续
selector.select();
//获取当前有哪些事件可以使用
Set keys = selector.selectedKeys();
//转换为迭代器
Iterator iter = keys.iterator();
SelectionKey key = null;
while (iter.hasNext()) {
key = iter.next();
iter.remove();
try {
handleInput(key);
} catch (IOException e) {
e.printStackTrace();
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//selector关闭后会自动释放里面管理的资源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//具体的事件处理方法
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//获得监听当前事件的channel
SocketChannel sc = (SocketChannel)key.channel();
//TODO:连接事件
if (key.isConnectable()) {
if (sc.finishConnect()) {
socketChannel.register(selector, SelectionKey.OP_READ);
} else {
System.exit(1);
}
}
//TODO:有数据可读事件
if (key.isReadable()) {
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if (readBytes > 0) {
//将缓冲区当前的limit设置为position, position = 0
//用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数据
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String result = new String(bytes, "UTF-8");
System.out.println("accept message: " + result);
} else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
//发送消息
private void doWrite(SocketChannel channel, String request) throws IOException {
//将消息编码为字节数组
byte[] bytes = request.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
writeBuffer.put(bytes);
//flip操作
writeBuffer.flip();
//发送缓冲区的字节数组
channel.write(writeBuffer);
}
//创建连接
private void doConnect() throws IOException {
if (socketChannel.connect(new InetSocketAddress(host, port))) {
//连接完成,顺序执行
} else {
//连接还未完成,因此注册连接就绪事件,向selector表示关注这个事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
//写数据对外暴露的API
public void sendMsg(String msg) throws IOException {
socketChannel.register(selector,SelectionKey.OP_READ);
doWrite(socketChannel, msg);
}
}
public class NioServer {
private static NioServerHandle nioServerHandle;
public static void start() {
if (nioServerHandle != null) {
nioServerHandle.stop();
}
nioServerHandle = new NioServerHandle(8888);
new Thread(nioServerHandle, "Server").start();
}
public static void main(String[] args) {
start();
}
}
public class NioClient {
private static NioClientHandle nioClientHandle;
public static void start() {
if (nioClientHandle != null) {
nioClientHandle.stop();
}
nioClientHandle = new NioClientHandle("127.0.0.1", 8888);
new Thread(nioClientHandle, "Client").start();
}
//向服务器发送消息
public static boolean sendMsg(String msg) throws Exception {
nioClientHandle.sendMsg(msg);
return true;
}
public static void main(String[] args) throws Exception {
start();
Scanner scanner = new Scanner(System.in);
while (NioClient.sendMsg(scanner.nextLine()));
}
}