转自: https://www.cnblogs.com/zhanglei93/p/6653923.html
Java NIO
以前写过一篇Java Socket的用法,不过觉得介绍的不够细致也不够全面,因此今天想在细谈一下Java NIO,也算是对上一篇博客的补充吧。在以前的博客中提到Java NIO的三个核心部分Buffers、Channels、Selectors,这里不再赘述三者之间的关系,接下来我们重点看看这三个核心部分。
Buffer
该区域本质是一块可以读写的数据的内存区,这组内存区被包装成NIO Buffer对象,并提供了一组方法,方便访问该块内存。为了更清楚的理解Buffer的工作原理,需要熟悉它的三个属性capacity、position、limit。capacity表示缓冲区大小。而position和limit的含义取决于Buffer处在读模式还是写模式下。在读模式下,position表示开始读的位置,limit表示最后能读的数据位置。在写模式下,position表示当前数据需要写入的位置,最大值为capacity-1。当由写模式切换到读模式时,position=0,limit=position。
抽象类Buffer具体实现类有ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。接下来我们以ByteBuffer为例来了解一下Buffer的具体用法。
public class TestByteBuffer {
public static void test(){
readFromChannel(); //从channel读取数据到Buffer中
readFromPut(); //put方法放入数据
}
public static void readFromChannel(){
try {
RandomAccessFile aFile = new RandomAccessFile("data/byte.txt","rw");
FileChannel channel = aFile.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(64); //设置buffer缓冲区的大小
int bytesRead = channel.read(buffer); //read to buffer
while(bytesRead != -1){
System.out.println("write mode position is " + buffer.position());
System.out.println("write mode limit is " + buffer.limit());
buffer.flip(); //切换到读模式,limit=posit,position=0,
System.out.println("Read mode position is " + buffer.position());
System.out.println("Read mode limit is " + buffer.limit());
while(buffer.hasRemaining()){
System.out.print((char)buffer.get()); //1byte的读数据
}
System.out.println();
buffer.clear(); //将position设置为0,limit设置成capacity值
bytesRead = channel.read(buffer);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void readFromPut(){
ByteBuffer buffer = ByteBuffer.allocate(48);
for(int i = 0; i < 12; i++){
buffer.putInt(i);
}
buffer.flip();
while(buffer.hasRemaining()){
System.out.print(buffer.get());
}
}
}
Channels
Channel充当的其实是搬运工的角色,它负责把数据搬运到Buffer中,也可以从Buffer中把数据搬运出去。具体的实现Channel接口的类有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。FileChannel从文件中读取数据到缓冲区(已经在Buffer中介绍过了),DatagramChannel能通过UDP读写网络中的数据,SocketChannel能通过TCP读写网络中的数据,ServerSocketChannel可以监听新进来的TCP连接,对每个新进来的连接都会创建一个SocketChannel。如下是利用SocketChannel和ServerSocketChannel实现客户端和服务器端(IP地址192.168.6.42)通信:
public class Server {
public static void main(String[] args){
try {
//创建一个ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
//监听8080端口
ssc.socket().bind(new InetSocketAddress(8080));
SocketChannel socketChannel = ssc.accept(); //阻塞,开始监听8080端口
ByteBuffer buffer = ByteBuffer.allocate(100); //设置buffer的capacity为100
int readBytes = socketChannel.read(buffer); //利用channel将数据写入buffer
while(readBytes != -1){
buffer.flip(); //切换为读模式
while(buffer.hasRemaining()){ //检查buffer是否读完
System.out.print((char)buffer.get()); //1byte的读数据
}
buffer.clear(); //清空buffer缓冲区
readBytes = socketChannel.read(buffer);
}
socketChannel.close(); //关闭socketChannel
ssc.close(); //关闭ServerSocketChannel
System.out.println("It it over");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端程序如下:
public class TestSocketChannel {
public static void main(String args){
SocketChannel socketChannel;
try {
//创建SocketChannel
socketChannel = SocketChannel.open();
//连接到某台服务器的某个端口
socketChannel.connect(new InetSocketAddress("192.168.6.42",8080));
String sendString = "This is a message from client, Please read it carefully. Thanke you very much";
ByteBuffer buffer = ByteBuffer.wrap(sendString.getBytes()); socketChannel.write(buffer);
//关闭通道
socketChannel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Selector
Selector是Java NIO中能够检测到一到多个NIO通道,并能够知晓通道是否为诸如读写时间做好准备的组件。这样就可以实现一个单独的线程可以管理多个Channel,从而管理多个网络连接。我们从如下服务器端和客户端的程序介绍Selector吧。
客户端程序如下,首先是一个创建SocketChannel的类如下:
public class TestSocketChannel {
/**
* 创建一个SocketChannel,其中指定连接的IP地址和端口号
*/
public static SocketChannel createSocketChannel(String ip, int port){
SocketChannel socketChannel = null;
try {
//创建SocketChannel
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); //设置为非阻塞模式
//连接到某台服务器的某个端口
socketChannel.connect(new InetSocketAddress(ip,port));
//判断是否连接完成,若未完成则等待连接
while(!socketChannel.finishConnect()){
System.out.println("It is connecting>>>>>>>>>>>>>>>>>>>>>");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//连接完成返回该SocketChannel
return socketChannel;
}
}
客户端主程序通过调用该类的createSocketChannel()方法创建一个SocketChannel对象,主程序如下:
public class Main {
public static void main(String[] args){
try {
//创建SocketChannel,连接192.168.6.42服务器的8080端口
SocketChannel sc8080 = TestSocketChannel.createSocketChannel("192.168.6.42",8080);
//创建SocketChannel,连接192.168.6.42服务器的8090端口
SocketChannel sc8090 = TestSocketChannel.createSocketChannel("192.168.6.42",8090);
//创建selector
Selector selector = Selector.open();
//向通道注册选择器,并设置selector监听Channel时对读操作感兴趣
sc8080.register(selector, SelectionKey.OP_READ);
sc8090.register(selector, SelectionKey.OP_READ);
//启动线程,监听是否从服务器端有数据传过来
Thread thread = new Thread(new MyRunnable(selector));
thread.start();
//分别向服务器的8080和8090端口发送数据
sendString(sc8080,"This message is going to send to server 8080 port");
sendString(sc8090,"This message is going to send to server 8090 port");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void sendString(SocketChannel sc, String str){
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
try {
//将buffer中的数据写入sc通道
sc.write(buffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyRunnable implements Runnable{
private Selector selector;
public MyRunnable(Selector s){
this.selector =s;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while(true){
//阻塞2000ms,判断是否有通道在注册的事件上就绪了,如果有则该返回值就绪通道的个数
if(selector.select(2000) == 0){
System.out.println("please waiting.....");
continue;
}else{
//当有通道就绪时,获取SelectionKey,并遍历
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
//判断通道中是否可读事件就绪了,如果是则isReadable()方法返回TRUE
if(key.isReadable()){
SocketChannel socketChannel = (SocketChannel) key.channel();
//默认服务器端发送的数据都小于1024byte,因此一次可以读完
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer); //利用通道将数据读入buffer中
buffer.flip(); //将buffer切换为读模式
String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
System.out.println(receiveString);
buffer.clear(); //清空缓冲区buffer
}
//设置通道对什么时间感兴趣,该设置是对“读”和“写”感兴趣
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//移除当前已经处理过的SelectionKey
keys.remove();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服务器端程序如下:
public class TestServerSocketChannel {
public ServerSocketChannel createServerSocketChannel(int port){
ServerSocketChannel ssc = null;
try {
ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(port));
ssc.configureBlocking(false);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ssc;
}
}
public class Server {
private static Selector selector = null;
public static void main(String[] args){
try {
//创建一个ServerSocketChannel,监听8080端口,非阻塞模式
ServerSocketChannel ssc8080 = (new TestServerSocketChannel()).createServerSocketChannel(8080);
//创建一个ServerSocketChannel,监听8090端口,非阻塞模式
ServerSocketChannel ssc8090 = (new TestServerSocketChannel()).createServerSocketChannel(8090);
//创建监听器
selector = Selector.open();
//向通道注册监听器
ssc8080.register(selector, SelectionKey.OP_ACCEPT);
ssc8090.register(selector, SelectionKey.OP_ACCEPT);
//开启线程,监听客户端发送过来的数据
Thread thread = new Thread(new MyRunnable());
thread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
//阻塞3s后判断selector注册的通道是否有就绪的
if(selector.select(3000) == 0){
System.out.println("正在等待请求......");
continue;
}else{
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
//判断是否有新的连接
if (key.isAcceptable()) {
HandleRequest.handleAccept(key);
} else if (key.isReadable()) { //判断是否有读操作
HandleRequest.handleRead(key);
} else if (key.isValid() && key.isWritable()) { //判断是否对写操作感兴趣
HandleRequest.handleWrite(key);
}
keys.remove(); // 移除处理过的键
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class HandleRequest {
//当有新的连接时
public static void handleAccept(SelectionKey key){
try {
//通过SelectionKey对象key创建SocketChannel对象
SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
//设置socketChannel为非阻塞模式
socketChannel.configureBlocking(false);
//向通道注册选择器和感兴趣事件
socketChannel.register(key.selector(), SelectionKey.OP_READ);
//输出数据从服务器的哪个端口传入
System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void handleRead(SelectionKey key){
SocketChannel socketChannel = (SocketChannel) key.channel();
System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
ByteBuffer buffer = ByteBuffer.allocate(32);
try {
int readBytes = socketChannel.read(buffer);
//输出数据从哪个客户端地址传入
System.out.println("receive data from " + socketChannel.socket().getRemoteSocketAddress() + ", the data are ");
//读取缓冲区中的数据
while(readBytes != 0){
buffer.flip();
String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
System.out.print(receiveString);
buffer.clear();
readBytes = socketChannel.read(buffer);
}
//更改通道感兴趣的事件为“读操作”和“写操作”
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void handleWrite(SelectionKey key){
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer writeBuffer = ByteBuffer.wrap("This message is from server".getBytes());
try {
socketChannel.write(writeBuffer);
System.out.println("The message is writen in channel");
key.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}