NIO的三个概念 buffer,channel,selector
buffer的三个参数:
position 初始值0 代表下一次写入位置,读了类似,
limit 写模式下limit=capacity 读模式下等于数据实际大小
capacity 缓冲区容量
channel:
FileChannel文件通道 不支持阻塞
DatagramChannel UDP 接收和发送
SocketChannel TCP连接通道
ServerSocketChannel TCP服务端 监听端口
selector
主要分为三步 开启selector channel注册到selector上 调用select()方法获取通道信息
下面是代码部分。
服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
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.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
public class NioService {
/* 缓冲区大小 */
private static final int BLOCK_SIZE = 4096;
/* 接收数据缓冲区大小 */
private ByteBuffer recieveBuffer = ByteBuffer.allocate(BLOCK_SIZE);
/* 发送数据缓冲区大小 */
private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK_SIZE);
/*持有一个Selector*/
private Selector selector;
public NioService(){
}
public NioService(int port) throws IOException{
/*SocketServerChannel:可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样*/
/*创建服务器套接字通道:SocketServerChannel,内部就是实例化了一个SocketServerChannelImpl*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
/* 服务器配置为非阻塞 */
serverSocketChannel.configureBlocking(Boolean.FALSE);
/* 获取一个socket */
ServerSocket serverSocket = serverSocketChannel.socket();
/* 通过socket 绑定地址 */
serverSocket.bind(new InetSocketAddress("localhost",port));
/* 获取Selector:实际上window是创建了WindowsSelectorImpl实例 */
selector = Selector.open();
/* 注册到accept事件到 selector,等待连接 */
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Server Start.........................");
}
/**一直不断的监听*/
public void listen() throws IOException {
while (true){
// 监控注册在Selector上的ServerSocketChannel,返回值代表有多少channel已经就绪,可以进行I/O操作了
int ready = selector.select();
if (ready > 0) {
/*
* selectedKeys()返回一个SelectionKey的集合,因为完全有可能在这个Selector注册多个SelectionKey
* 比如OP_ACCEPT,OP_READ,OP_WRITE等
* 其中每个SelectionKey代表了一个可以进行IO操作的channel
* 一个ServerSocketChannel可以进行IO操作意味着有新的TCP连接连入了
*/
Set<SelectionKey> keys = selector.selectedKeys();
SelectionKey key = null;
for(Iterator<SelectionKey> it = keys.iterator();it.hasNext();){
key = (SelectionKey)it.next();
// 需要将处理过的key从selectedKeys这个集合中删除
it.remove();
handleKey(key);
}
}
}
}
/**处理请求*/
public void handleKey(SelectionKey key) throws IOException{
if (key == null) {
System.out.println("SelectionKey is null");
return;
}
ServerSocketChannel serverSocketChannel = null;
SocketChannel socketChannel = null;
Socket socket = null;
int count = 0;
/* 判断这个SelectionKey对应的channel是否已准备好接收新的TCP连接 */
if (key.isAcceptable()) {//监听的连接事件
/* 从SelectionKey得到对应的channel */
serverSocketChannel = (ServerSocketChannel)key.channel();
/* 接受新的TCP连接(来自客户端) */
socketChannel = serverSocketChannel.accept();
/* 配置为非阻塞 */
socketChannel.configureBlocking(Boolean.FALSE);
/* 注册到selector,等待读取 */
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { //判断该通道是否已准备好读 监听的可读事件
/* 返回为之创建此键的通道 */
socketChannel = (SocketChannel)key.channel();
/* 将接收数据的缓冲区清空以备下次读取 */
recieveBuffer.clear();
/* 读取服务器发送的数据到recieveBuffer */
count = socketChannel.read(recieveBuffer);
if (count >0) {
String recvText = new String(recieveBuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--: " + recvText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (key.isWritable()) {//判断该通道是否已准备好写 监听的可写事件
socketChannel = (SocketChannel)key.channel();
/* 将缓冲区清空以备下次写入 */
sendBuffer.clear();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String data = dateFormat.format(new Date());
/* 向缓冲区中输入数据 */
sendBuffer.put(data.getBytes(Charset.defaultCharset()));
/* 将数据输出到通道*/
socketChannel.write(sendBuffer);
System.out.println("服务器端向客户端发送数据--: " + data);
/* 又注册到selector,等待读取 */
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
public static void main(String[] args) throws IOException {
int port = 22222;
NioService server = new NioService(port);
server.listen();
}
}
客户端
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.Set;
import java.util.concurrent.ArrayBlockingQueue;
public class NioClient {
/* 缓冲区大小 */
private static final int BLOCK_SIZE = 4096;
/* 接收数据缓冲区大小 */
private ByteBuffer recieveBuffer = ByteBuffer.allocate(BLOCK_SIZE);
/* 发送数据缓冲区大小 */
private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK_SIZE);
/* 服务器端地址 */
private final static InetSocketAddress SERVER_ADDRESS =
new InetSocketAddress("localhost", 22222);
public void handle() throws IOException{
/* 创建一个SocketChannel */
SocketChannel socketChannel = SocketChannel.open();
/* 设为非阻塞模式 */
socketChannel.configureBlocking(Boolean.FALSE);
/* 创建Selector */
Selector selector = Selector.open();
/* 注册连接服务端socket动作 */
socketChannel.register(selector, SelectionKey.OP_CONNECT);
/* 连接到服务器 */
socketChannel.connect(SERVER_ADDRESS);
Set<SelectionKey> keys = null;
Iterator<SelectionKey> it = null;
SelectionKey key = null;
int count = 0;
String recvText = null;
while (true) {
/* 监控注册在Selector上的SocketChannel,返回值代表有多少channel已经就绪,可以进行I/O操作了*/
int ready = selector.select();
/* 其中每个SelectionKey代表了一个可以进行IO操作的channel */
keys = selector.selectedKeys();
it = keys.iterator();
while (it.hasNext()) {
key = (SelectionKey)it.next();
// 判断该通道是否可以进行连接操作
if (key.isConnectable()) {
System.out.println("客户端开始连接......");
/* 返回为之创建此键的通道 */
socketChannel = (SocketChannel)key.channel();
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
System.out.println("完成连接!");
sendBuffer.clear();
sendBuffer.put("我可以连接吗".getBytes(Charset.defaultCharset()));
sendBuffer.flip();
socketChannel.write(sendBuffer);
}
/* 又注册到selector,等待读取 */
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { //判断该通道是否可以读
/* 返回为之创建此键的通道 */
socketChannel = (SocketChannel)key.channel();
//将接收数据缓冲区清空以备下次读取
recieveBuffer.clear();
//读取服务器发送来的数据到缓冲区中
count = socketChannel.read(recieveBuffer);
if (count > 0) {
recvText = new String(recieveBuffer.array(),0,count,Charset.defaultCharset());
System.out.println("客户端接受服务器端数据=> "+recvText);
/* 又注册到selector,等待写入 */
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (key.isWritable()) { //判断该通道是否可以写
/* 返回为之创建此键的通道 */
socketChannel = (SocketChannel)key.channel();
//将发送数据缓冲区清空以备下次读取
sendBuffer.clear();
String data = String.valueOf(Math.round(Math.random() * 100 + 1));
sendBuffer.put(data.getBytes(Charset.defaultCharset()));
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendBuffer.flip();
socketChannel.write(sendBuffer);
System.out.println("客户端向服务器端发送数据=> " + data);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
keys.clear();
}
}
public static void main(String[] args) throws IOException {
NioClient client = new NioClient();
client.handle();
}
}