NIO
1.NIO为通道(Channel)——缓冲区(Buffer)运行模式。
2.NIO的三大要点:通道(Channel)、缓冲区(Buffer)、选择器(Selecrot)
打开缓冲区的方式,通过调用对应Buffer的静态方法allocate(int size)方法设置缓冲区的大小,以ByteBuffer为例:
ByteBuffer dst = ByteBuffer.allocate(1024)//创建一个1024个字节大小的缓冲区。
缓冲区(Buffer)有三个重要的属性: capacity position limit
这三个属性分别表示 capacity:当前缓冲区的大小
position:当前缓冲区的位置
limit:缓冲区的输入最大限制。
对Buffer的操作有三个常用方法:
clear():清空缓冲区
flip():将缓冲区从写入模式切换到读取模式,即把limit的值置为position的值,position的值置为0。
rewind():从新读取此缓冲区。
缓冲区分为直接缓冲区和非直接缓冲区,直接缓冲区则由allocateDirect(int size)创建;
直接缓冲区与非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。直接字节缓冲区可以通过调用此类的 allocateDirect 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。直接字节缓冲区还可以通过 mapping 将文件区域直接映射到内存中来创建。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
通道分为:
文件通道:FileInputStream\FileOutputStream\RandomAccessFile的 FileChannel
网络IO:SocketChannle ServerSocketChannel DatagramChannel;
网络IO的通道继承于SelectableChannel类。FielChannel和SelectableChannel的父类都是AbstractInterruptibleChannel。一般都是通道配合缓冲区使用(transferTo、transferFrom 是通道间直接数据交换)。
创建文件通道(FileChannel)可以通过FileInputStream\FileOutputStream\RandomAccessFile 的getChannel()方法可以获得。创建网络IO通道:可以通过open方法获得。
Selector管理多个socketChannel。把具体的SocketChannel注册到Selecrot,同时声明监听事件(有四个监听事件①connect客户端链接服务端事件。
②accept服务端接受客户端事件。
③read读事件。
④write写事件。)
每次请求到达服务器都是从connect开始,然后服务端进入accept(准备)状态。开始读数据并处理,然后返回。
selectKeys方法和keys方法
selectKeys()方法返回的是选择器中的所有已选择(已准备好了的)的键集。
keys()方法获得的是选择器中所有的键的集合。
1.在多线程中,处理完本次监听事件后,需要给当前的键设置为继续当前事件状态。
2.用完或处理完这个监听的事件后,需要把键从这个已选择的键的集合中删除,如果不删除,则当前的线程会一直占用这个键,得不到释放,下个线程就会阻塞。
基于NIO的在线聊天参考某博主的:
Client
package com.mySocket.testsocket;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
public class MySocketClient {
final static String SERVER_IP = "127.0.0.1";
final static int SERVER_PORT = 1475;
SocketChannel schannel = null;
private Selector select = null;
private final static String SEND_FLAG = "<MSG>";
private String name = "";
private Charset charset = Charset.forName("UTF-8");
public void init(){
try {
select = Selector.open();
schannel = SocketChannel.open(new InetSocketAddress(MySocketClient.SERVER_IP, MySocketClient.SERVER_PORT));
schannel.configureBlocking(false);
schannel.register(select, SelectionKey.OP_READ);
Scanner scanner = new Scanner(System.in);
new Thread(new Client()).start();
while(scanner.hasNextLine()){
String message = scanner.nextLine();
if("".equals(message))
continue;
if("".equals(name)){
name = message;
message = name + SEND_FLAG;
}else{
message = name + SEND_FLAG + message;
}
schannel.write(charset.encode(message));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[ ] args) {
// ByteBuffer dst = ByteBuffer.wrap("zhangsan".getBytes());
// ByteBuffer dst = ByteBuffer.allocate(1024);
// dst.put("增长撒".getBytes());
// StringBuffer sb = new StringBuffer();
// dst.flip();
// sb.append(new String(dst.array(),0,dst.limit()));
// System.out.println(sb.toString());
MySocketClient client = new MySocketClient();
client.init();
}
private class Client implements Runnable{
@Override
public void run() {
try {
while(true){
if(select.select() == 0)
continue;
Iterator<SelectionKey> keys = select.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
keys.remove();
process(key);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void process(SelectionKey key){
if(key.isReadable()){
SocketChannel schannel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(1024);
StringBuffer sbuffer = new StringBuffer();
try {
while(schannel.read(dst) > 0){
dst.flip();
sbuffer.append(new String(dst.array(),0,dst.limit()));
dst.clear();
}
System.out.println(sbuffer.toString());
} catch (IOException e) {
e.printStackTrace();
}
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
Server:
package com.mySocket.testsocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 服务端
* @author John
*
*/
public class MySocketServer {
private ServerSocketChannel serverChannel;
private Selector select;
//端口号
private static int PORT = 1475;
private static int CAPACITY_SIZE= 1024;
private final static String SEND_FLAG = "<MSG>";
private final static String ERROR_USER_MSG="输入的用户名已存在,请重新输入!。";
//存放聊天用户。
private Map<String,Object> userData = new HashMap<String, Object>();
private Charset charset = Charset.forName("UTF-8");
public void listServer(){
try {
//1.创建通道
serverChannel = ServerSocketChannel.open();
//2将通道设置为非阻塞
serverChannel.configureBlocking(false);
//3绑定端口号
serverChannel.bind(new InetSocketAddress(PORT));
//4注册到选择器
//4.1先获取选择器
select = Selector.open();
//4.2注册为准备就绪状态
serverChannel.register(select, SelectionKey.OP_ACCEPT);
//5.读取选择器,看选择器是否有新的事件
while(true){
if(select.select() == 0)
continue;
Iterator<SelectionKey> keys = select.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
//释放选择器,方便下次获取
keys.remove();
process(key);
}
}
} catch (IOException e) {
System.err.println("服务器监听异常---");
e.printStackTrace();
}
}
public void process(SelectionKey key){
try {
if(key.isAcceptable()){
//如果是准备就绪状态,则获取阻塞请求的听到,并注册一个read状态的通道
SocketChannel schannel = ((ServerSocketChannel) key.channel()).accept();
if(schannel == null)
return;
//SocketChannel schannel = serverChannel.accept();
//设置为非阻塞状态
schannel.configureBlocking(false);
//注册个读的通道。
schannel.register(select, SelectionKey.OP_READ);
//将此键设置为继续接受其他客户端请求。
key.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("接入IP地址:" + schannel.getRemoteAddress());
//schannel.close();
// schannel.write(charset.encode("Please input you Name:"));
schannel.write(ByteBuffer.wrap("请输入用户:".getBytes()));
}
if(key.isReadable()){
//如果是读状态,则读取获得的数据,
//1.获取通道,创建缓冲区准备接受数据
SocketChannel schannel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(CAPACITY_SIZE);
int len = 0;
StringBuffer message = new StringBuffer();
while((len = schannel.read(dst)) >0){
dst.flip();
message.append(new String(dst.array(),0,len));
dst.clear();
}
System.out.println("Server is listening from client " + schannel.getRemoteAddress() + " data rev is: " + message.toString());
//继续接收
key.interestOps(SelectionKey.OP_READ);
if(!"".equals(message.toString())){
//按传输规则,name+#@#+msg
String[] msgArr = message.toString().split(SEND_FLAG);
//如果是用户登录,则将用户数据保存在集合里去。
if(msgArr != null && msgArr.length == 1){
//广播到除了本身以外的所有SocketChannel
String name = msgArr[0];
if(userData.containsKey(name)){
schannel.write(ByteBuffer.wrap(ERROR_USER_MSG.getBytes()));
}else{
userData.put(name, new Date());
//广播到其他
int count = getCountUser();
String msg = "欢迎" + name + "进入聊天室!当前聊天室人数:" + count;
for(SelectionKey itkey : select.keys()){
Channel targetChannel = itkey.channel();
if(targetChannel instanceof SocketChannel){
((SocketChannel) targetChannel).write(ByteBuffer.wrap(msg.getBytes()));
}
}
}
}else{
//发生的消息,
String sendMsg = msgArr[0] + ":" + msgArr[1];
//广播到除了本身以外的所有SocketChannel
for(SelectionKey itkey : select.keys()){
Channel targetChannel = itkey.channel();
if(targetChannel instanceof SocketChannel){
((SocketChannel) targetChannel).write(ByteBuffer.wrap(sendMsg.getBytes()));
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public int getCountUser(){
int count = 0;
for(SelectionKey key : select.keys()){
Channel channel = key.channel();
if(channel instanceof SocketChannel){
count++;
}
}
return count;
}
public static void main(String[] args) {
MySocketServer server = new MySocketServer();
System.out.println("服务器启动...");
server.listServer();
}
}