Java网络编程——NIO编程

2023-11-01

目录

第一部分:NIO介绍

1.NIO三大核心部分

2.NIO的工作机制

3.Java NIO的非阻塞模式

第二部分:NIO和BIO的比较

第三部分:NIO三大核心原理

第四部分:缓冲区(Buffer)

1.缓冲区基本介绍

2.Buffer常用的API

1.Buffer类以及它的子类

2.缓冲区对象创建

3.demo:创建缓冲区 

4. 缓冲区对象添加数据

 5.demo:练习缓冲区对象添加数据的常用方法

 6.缓冲区对象读取数据

7.demo:缓冲区对象读取数据常用方法练习

第五部分:通道(Channel)

1.通道(Channel)的介绍

2.Channel常用类

1.Channel接口

2.文件通道

1.FileChannel的创建

 2.FileChannel的常用方法

3.demo: FileChannel

4. demp:transferFrom()

 5.transferTo()

6.demo: 向本地文件写数据

7.demo: 从本地文件读数据

8.demo: 文件拷贝

3.Socket通道

1.常用的Socket通道

4.ServerSocketChannel

1.ServerSocketChannel常用的API

2. 服务端实现步骤:

3.小demo 

5.SocketChannel

1.SocketChannel常用的API

2.客户端实现步骤 

3.小demo 

第六部分:Selector(选择器)

1.Selector基本介绍

1.没有选择器的情况

2.有选择器的情况

2.Selector常用API介绍

1.Selector常用方法:

2. SelectionKey常用方法:

3.SelectionKey中定义的4种事件:

3.Selector服务端实现步骤

4.DEMO :服务器—选择器


第一部分:NIO介绍

Java NIO 全称java non-blocking IO ,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的.

1.NIO三大核心部分

Channel(通道)

Buffer(缓冲区)

Selector(选择器)

2.NIO的工作机制

NIO是 面向缓冲区编程的。

数据读取到一个缓冲区中,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络

3.Java NIO的非阻塞模式

Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。

非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入, 这个线程同时可以去做别的事情。

通俗理解:NIO 是可以做到用一个线程来处理多个操作的

假设有 10000 个请求过来,根据实际情况,可以分配50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。

第二部分:NIO和BIO的比较

1. BIO 以的方式处理数据,而 NIO 以缓冲区的方式处理数据,缓冲区 I/O 的效率比流 I/O 高很多
2. BIO 是阻塞的,NIO则是非阻塞的。
3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求, 数据到达等),因此使用单个线程就可以监听多个客户端通道。

第三部分:NIO三大核心原理

看一张图就明白啦

1. 每个 channel 都会对应一个 Buffer;
2. Selector 对应一个线程, 一个线程对应多个 channel(连接);
3. 每个 channel 都注册到 Selector选择器上;
4. Selector不断轮询查看Channel上的事件, 事件是通道Channel非常重要的概念;
5. Selector 会根据不同的事件,完成不同的处理操作;
6. Buffer 就是一个内存块 , 底层是有一个数组;
7. 数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO 的 Buffer 是可以读也可以写 , channel 是双向的.

第四部分:缓冲区(Buffer)

1.缓冲区基本介绍

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个数组,该对象
提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer.

2.Buffer常用的API

1.Buffer类以及它的子类

在 NIO 中,Buffer是一个顶层父类,它是一个抽象类,

常用的缓冲区

ByteBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,CharBuffer

分别对应byte,short, int, long,float,double,char 7种.

2.缓冲区对象创建

方法名 说明
static ByteBuffer allocate(长度) 创建byte类型的指定长度的缓冲区
static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区

3.demo:创建缓冲区 

package java2.buffer.temp.temp1;

import java.nio.ByteBuffer;

//创建缓冲区
public class CreateBufferDemo {
    public static void main(String[] args) {

        //1.创建一个指定长度的缓冲区,以ByteBuffer为例
        //什么内容都没有存,会初始化为0
        ByteBuffer byteBuffer =ByteBuffer.allocate(5);
        for(int i=0;i<5;i++){
            System.out.println(byteBuffer.get());
        }

        //在这里调用会报错——等到后面再读缓冲区的时候我们来了解
        //System.out.println(byteBuffer.get());

        //2.创建一个有内容的缓冲区
        ByteBuffer wrap =ByteBuffer.wrap("lagou".getBytes());
        for (int i = 0; i <5; i++) {
            System.out.println(wrap.get());
        }
    }
}
//输出:
//        0
//        0
//        0
//        0
//        0
//        108
//        97
//        103
//        111
//        117

4. 缓冲区对象添加数据

方法名 说明
int position() 获得当前要操作的索引
int  position(int  newPosition) 修改当前要操作的索引位置
int limit() 最多能操作到哪个索引
int  limit(int newLimit) 修改最多能操作的索引位置
int capacity() 返回缓冲区的总长度
int remaining() 还有多少能操作索引个数
boolean  hasRemaining() 是否还有能操作
put(byte b) 添加一个字节
put(byte[] src) 添加字节数组

看张图了解一下叭 

 5.demo:练习缓冲区对象添加数据的常用方法

package java2.buffer.temp.temp2;

import java.nio.ByteBuffer;

public class PutBufferDemo {
    public static void main(String[] args){
        //1.创建一个指定长度的缓冲区,比如:ByteBuffer
        ByteBuffer byteBuffer =ByteBuffer.allocate(10);

        //1.获取当前索引所在位置
        System.out.println("当前索引所在位置  " +
                byteBuffer.position());

        //2.最多能操作到哪个索引
        System.out.println("最多能操作到哪个索引  " +
                byteBuffer.limit());

        //3.返回缓冲区总长度
        System.out.println("返回缓冲区总长度  " +
                byteBuffer.capacity());

        //4.还有多少个能操作
        System.out.println("还有多少个能操作  " +
                byteBuffer.remaining());


//      修改当前索引位置
//      byteBuffer.position(1);
//      修改最多能操作到哪个索引位置
//      byteBuffer.limit(9);
//      System.out.println(byteBuffer.position());//1 获取当前索引所在位置
//      System.out.println(byteBuffer.limit());//9 最多能操作到哪个索引
//      System.out.println(byteBuffer.capacity());//10 返回缓冲区总长度
//      System.out.println(byteBuffer.remaining());//8 还有多少个能操作

        //添加一个字节
        byteBuffer.put((byte) 97);
        byteBuffer.put((byte) 97);
        System.out.println(byteBuffer.position());//1 获取当前索引所在位置
        System.out.println(byteBuffer.limit());//10 最多能操作到哪个索引
        System.out.println(byteBuffer.capacity());//10 返回缓冲区总长度
        System.out.println(byteBuffer.remaining());//9 还有多少个能操作
        
        
//添加一个字节数组
        byteBuffer.put("abc".getBytes());
        System.out.println(byteBuffer.position());//4 获取当前索引所在位置
        System.out.println(byteBuffer.limit());//10 最多能操作到哪个索引
        System.out.println(byteBuffer.capacity());//10 返回缓冲区总长度
        System.out.println(byteBuffer.remaining());//6 还有多少个能操作
        
        
//当添加超过缓冲区的长度时会报错
        byteBuffer.put("123456".getBytes());
        System.out.println(byteBuffer.position());//10 获取当前索引所在位置
        System.out.println(byteBuffer.limit());//10 最多能操作到哪个索引
        System.out.println(byteBuffer.capacity());//10 返回缓冲区总长度
        System.out.println(byteBuffer.remaining());//0 还有多少个能操作
        System.out.println(byteBuffer.hasRemaining());// false 是否还能有操作的数组
        
        
// 如果缓存区存满后, 可以调整position位置可以重复写,这样会覆盖之前存入索引的对应的值
        byteBuffer.position(0);
        byteBuffer.put("123456".getBytes());
    }
}

 6.缓冲区对象读取数据

方法名 介绍
flip() 写切换模式; limit设置为position位置, position设置0
get() 读一个字节
get(byte[] dst) 读多个字节
get(int index) 读指定索引的字节
rewind() 将position设置为0,可以重复读
clear() 切换模式 ;position设置为0 , limit 设置为 capacity的位置
array() 将缓冲区转换成字节数组返回

 flip()方法:切换读模式

clear()方法:切换写模式

!!!!这两种方法其实就是重新设置position()和limit()的位置

7.demo:缓冲区对象读取数据常用方法练习

package java2.buffer.temp.temp3;

import java.nio.ByteBuffer;

public class GetBufferDemo {
    public static void main(String[] args) {
        //1.创建一个指定长度的缓冲区
        ByteBuffer allocate = ByteBuffer.allocate(10);
        allocate.put("0123".getBytes());
        System.out.println("position:" + allocate.position());
        System.out.println("limit:" + allocate.limit());
        System.out.println("capacity:" + allocate.capacity());
        System.out.println("remaining:" + allocate.remaining());

        //切换读模式
        System.out.println("读取数据--------");
        allocate.flip();//获取缓冲区的数据之前要调用flip方法
        System.out.println("position:" + allocate.position());
        System.out.println("limit:" + allocate.limit());
        System.out.println("capacity:" + allocate.capacity());
        System.out.println("remaining:" + allocate.remaining());
        for(int i=0;i<allocate.limit();i++){
            System.out.println(allocate.get());//读的是ASCII码值
        }
        //读取完了之后,继续读取就会把错,超过limit值
        //System.out.println(allocate.get());

        //读取指定索引字节
        System.out.println("读取指定索引字节----------");
        System.out.println(allocate.get(1));

        System.out.println("读取多个字节--------");

        //重复读取
        allocate.rewind();
        byte[] bytes = new byte[4];
        allocate.get(bytes);
        System.out.println(new String(bytes));

        //将缓冲区转化字节数组返回
        System.out.println("将缓冲区转化字节数组返回---------");
        byte[] array =allocate.array();
        System.out.println(new String(array));

        //切换写模式,覆盖之前索引所在位置的值
        System.out.println("写模式--------");
        allocate.clear();
        allocate.put("abc".getBytes());
        System.out.println(new String(allocate.array()));

    }
}
//输出
//        position:4
//        limit:10
//        capacity:10
//        remaining:6
//        读取数据--------
//        position:0
//        limit:4
//        capacity:10
//        remaining:4
//        48(0的ASCII)
//        49
//        50
//        51
//        读取指定索引字节----------
//        49
//        读取多个字节--------
//        0123
//        将缓冲区转化字节数组返回---------
//        0123
//        写模式--------
//        abc3

注意!!!!

1. capacity:容量(长度)

    limit: 界限(最多能读/写到哪里)

    posotion:位置(读/写哪个索引)
2. 获取缓冲区里面数据之前,需要调用flip方法
3. 再次写数据之前,需要调用clear方法但是数据还未消失,等再次写入数据,被覆盖了
才会消失。

第五部分:通道(Channel)

1.通道(Channel)的介绍

通常来说NIO中的所有IO都是从 Channel(通道) 开始的

NIO 的通道类似于流,但有些区别如下:

1. 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候,需要分别创建一个输入流和一个输出流);
2. 通道可以异步读写;
3. 通道总是基于缓冲区Buffer来读写.

2.Channel常用类

1.Channel接口

常 用 的Channel实现类有 :FileChannel , DatagramChannel ,ServerSocketChannel和
SocketChannel 。

FileChannel 用于文件的数据读写;

DatagramChannel 用于 UDP 的数据读写;

ServerSocketChannel 和SocketChannel 用于 TCP 的数据读写。

2.文件通道

  文件通道的主要实现是FileChannel。文件通道总是阻塞的,因此不能被置于非阻塞模式。

1.FileChannel的创建

FileChannel对象不能直接创建。

一个FileChannel实例只能通过一个打开的file对象(RandomAccessFil、FileInputStraem、FileOutputStream等)上调用getChannel()方法获取。

调用getChannel()方法返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有于file对象相同的访问权限。

  //创建一个RandomAccessFile(随机访问文件)对象,
  RandomAccessFile raf=new RandomAccessFile("/home/shizhanli/szl/luck", "rw");
 
  //通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
  FileChannel inChannel=raf.getChannel();

 2.FileChannel的常用方法

        注意哦!!   // This is a partial API listing
                             // All methods listed here can throw java.io.IOException


       1.  //从FileChannel读取数据
     
  public abstract int read(ByteBuffer dst)
        public abstract int read (ByteBuffer dst, long position);

 
        2.//向FileChannel写数据
       
public abstract int write(ByteBuffer src)
        public abstract int write (ByteBuffer src, long position);
 

        3.//获取文件大小
       
public abstract long size();
 
        4.//获取位置
     
  public abstract long position();
 
       5.//设置位置
       
public abstract void position (long newPosition);
 
        6.//用于文件截取
       
public abstract void truncate (long size);
 
        7.//将通道里尚未写入磁盘的数据强制写到磁盘上
       
public abstract void force (boolean metaData);
 
        8.//文件锁定,position-开始位置,size-锁定区域的大小,shared-表示锁是否共享(false为独占),lock()锁定整个文件
       
public final FileLock lock();
        public abstract FileLock lock (long position, long size, boolean shared);
        public final FileLock tryLock();
        public abstract FileLock tryLock (long position, long size, boolean shared);

 
       9. //内存映射文件
     
  public abstract MappedByteBuffer map (MapMode mode, long position, long size);
        public static class MapMode;
        public static final MapMode READ_ONLY;
        public static final MapMode READ_WRITE;
        public static final MapMode PRIVATE;

 
       10. //用于通道之间的数据传输
       
public abstract long transferTo (long position, long count, WritableByteChannel target);
        public abstract long transferFrom (ReadableByteChannel src, long position, long count);

   

3.demo: FileChannel

package java2.buffer.temp.temp4;

//FileChannel示例

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelTest {

    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("/home/shizhanli/szl/happy.txt", "rw");
        FileChannel inChannel = aFile.getChannel();
        ByteBuffer buf = ByteBuffer.allocate(48);
        int bytesRead = inChannel.read(buf);
        while (bytesRead != -1) {
            System.out.println("Read " + bytesRead);
            buf.flip();
            while (buf.hasRemaining()) {
                System.out.print((char) buf.get());
            }


            buf.clear();
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
        System.out.println("读完啦");
    }
}
//输出:
//Read 12
//12345678910(这是我文件中的内容)
//读完啦

 *~*public abstract MappedByteBuffer map (MapMode mode, long position, long size)

map()方法可以在一个打开的文件和一个特殊类型的ByteBuffer之间建立虚拟内存映射

通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都高。

transferTo()

将数据从FileChannel传输到其他的Channel中。

transferFrom()

从其他Channel获取数据

transferTo()和transferFrom()方法允许将一个通道交叉连接到另一个通道,而不需要通过一个中间缓存来传递数据。只有FileChannel类有这两个方法。

4. demp:transferFrom()

package java2.buffer.temp.temp4;

//transferFrom()示例:transferFrom()从其他Channel获取数据
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;


public class FileChannelTest2 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("/home/shizhanli/szl/happy.txt", "rw");
        FileChannel fromChannel = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("/home/shizhanli/szl/happy222.txt", "rw");
        FileChannel toChannel = bFile.getChannel();
        long position = 0;
        long count = fromChannel.size();
        toChannel.transferFrom(fromChannel, position, count);
        aFile.close();
        bFile.close();
        System.out.println("over!");
    }
}

 5.transferTo()

package java2.buffer.temp.temp4;

//transferTo()将数据从FileChannel传输到其他的Channel中。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class FileChannelTest3 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("/home/shizhanli/szl/happy.txt", "rw");
        FileChannel fromChannel = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("/home/shizhanli/szl/happy333.txt", "rw");
        FileChannel toChannel = bFile.getChannel();
        long position = 0;
        long count = fromChannel.size();
        fromChannel.transferTo(position, count, toChannel);
        aFile.close();
        bFile.close();
        System.out.println("over!");
    }

}

6.demo: 向本地文件写数据

package java2.buffer.temp.temp5;
//向本地文件写数据
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileWrite {
    public static void main(String[] args)  throws IOException{
        FileOutputStream out = new FileOutputStream("/home/shizhanli/szl/happy.txt");
        FileChannel channel = out.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("aurora123456789".getBytes());

// 需要切换为读模式,因为下面调用write方法时相当于从buffer里面读数据
        buffer.flip();

// 向channel写数据
        int len = channel.write(buffer);
        System.out.println("字节数:"+len);

        out.close();


    }
}

7.demo: 从本地文件读数据

package java2.buffer.temp.temp5;

//从本地文件读数据

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

public class FileRead {
    public static void main(String[] args)  throws IOException {
        FileInputStream in = new FileInputStream("/home/shizhanli/szl/happy.txt");
        FileChannel channel = in.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(8);

        int len = -1;
        List<Byte> list = new ArrayList<>();
        byte[] bytes = new byte[8];

        // 循环读取数据
        while ((len=channel.read(buffer))!=-1){

            // 下面要从buffer中读数据,因此切换为读模式
            buffer.flip();

            buffer.get(bytes,0,len);
            for (int i = 0; i < len; i++) {
                list.add(bytes[i]);
            }

            // 下一个循环需要先向buffer写数据,因此切换为写模式
            buffer.clear();
        }
        in.close();

      // 转为byte数组
        byte[] resBytes = new byte[list.size()];
        for (int i = 0; i < list.size(); i++) {
            resBytes[i] = list.get(i);
        }

        // 以字符串形式打印
        System.out.println(new String(resBytes));

    }
}

8.demo: 文件拷贝

package java2.buffer.temp.temp5;

//文件拷贝

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class FileCopy {
    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("/home/shizhanli/szl/happy.txt");
        FileOutputStream out = new FileOutputStream("/home/shizhanli/szl/happycopy.txt");
        FileChannel src = in.getChannel();
        FileChannel dst = out.getChannel();

        src.transferTo(0,src.size(),dst);
        // 或dst.transferFrom(src,0,src.size());

        in.close();
        out.close();

    }
}

3.Socket通道

1.常用的Socket通道

DatagramChannel:用于UDP的数据读写;

SocketChannel: 用于TCP的数据读写,一般是客户端实现

ServerSocketChannel: 允许我们监听TCP连接请求每个请求会创建会一个SocketChannel,一般是服务器实现。
 

上面的Channel都继承AbstractSelectableChannel,于是这三个Channel都是可以设置成非阻塞模式的。

Socket通道(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对等的socket对象,即我们熟悉的java.net的类型(Socket、ServerSocket和DatagramSocket)。 

4.ServerSocketChannel

 ServerSocketChannel用于监听TCP连接请求。

1.ServerSocketChannel常用的API

//静态方法,用于创建一个新的ServerSocketChannel对象,后续还需要跟ServerSocket进行绑定操作
public static ServerSocketChannel open()
 
//获取关联该ServerSocketChannel的server socket
public abstract ServerSocket socket()
 
//当创建ServerSocketChannel对象并绑定一个ServerSocket关联的通道之后,
//调用该方法可以监听客户端的连接请求
public abstract SocketChannel accept()
 
//并绑定指定端口的ServerSocket,(jdk1.7以上才有)
public final ServerSocketChannel bind(SocketAddress local)
 
//同选择器一起使用,获取感兴趣的操作
public final int validOps()

2. 服务端实现步骤:

1. 打开一个服务端通道
2. 绑定对应的端口号
3. 通道默认是阻塞的,需要设置为非阻塞
4. 检查是否有客户端连接 有客户端连接会返回对应的通道
5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
6. 给客户端回写数据
7. 释放资源

3.小demo 

package java2.buffer.temp.temp6;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

public class NIOServer {
    public static void main(String[] args) throws IOException,InterruptedException{
        //1.打开一个服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2.绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));

        //3.通道默认是阻塞的,需要设置为非阻塞
        // true:通道阻塞   false:通道非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服务器启动成功.......");
        while(true){
            //4.检查是否有客户端连接,有客户连接会返回对应的通道,否则的话返回Null;
            SocketChannel socketChannel =serverSocketChannel.accept();
            if(socketChannel==null){
                System.out.println("没有客户端连接...我去做别的事");
                Thread.sleep(3000);
                continue;
            }

            //5.获取客户端传递过来的数据,并且把数据放在byteBuffer这个缓冲区中
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            //返回值:
            //正数:表示本次读到的有效字节的个数
            //0  :表示本次没有读到有效字节
            //-1 :表示读到了末尾

            int read =socketChannel.read(byteBuffer);
            System.out.println("客户端消息:"+
                    new String(byteBuffer.array(),0,read, StandardCharsets.UTF_8));

            //6.给客户端回写数据
            socketChannel.write(ByteBuffer.wrap("没钱".getBytes(StandardCharsets.UTF_8)));

            //7.释放资源
            socketChannel.close();
        }

    }
}

5.SocketChannel

1.SocketChannel常用的API

 //静态方法,打开套接字通道(创建SocketChannel实例)
        public static SocketChannel open()
 
        //静态方法,打开套接字通道并将其连接到远程地址
        public static SocketChannel open(SocketAddress remote)
 

        //返回一个操作集,标识此通道所支持的操作
        public final int validOps()
 
        //用于将Socket绑定到一个端口
        public abstract SocketChannel bind(SocketAddress local)
 
        //获取该SocketChannel关联的Socket(套接字)
        public abstract Socket socket()
 
        //判断是否已连接此通道的网络套接字
        public abstract boolean isConnected()
 
        //判断此通道上是否正在进行连接操作。
        public abstract boolean isConnectionPending()
 
        //用于SocketChannel连接到远程地址
        public abstract boolean connect(SocketAddress remote)
 
        //从通道中读取数据到缓冲区中
        public abstract int read(ByteBuffer dst)
        public abstract long read(ByteBuffer[] dsts, int offset, int length)

 
        //将缓冲区的数据写入到通道中
        public abstract int write(ByteBuffer src)
        public abstract long write(ByteBuffer[] srcs, int offset, int length)

创建SocketChannel对象并连接到远程地址

       SocketChannel  sc = SocketChannel.open(new InetSocketAddress(ip,port));

等价于

      SocketChannel sc = SocketChannel.open();

       sc.connect(new InetSocketAddress(ip,port));

       线程在连接建立好或超时之前都保持阻塞。
 

2.客户端实现步骤 

1. 打开通道
2. 设置连接IP和端口号
3. 写出数据
4. 读取服务器写回的数据

5. 释放资源

3.小demo 

package java2.buffer.temp.temp6;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //1.打开通道
        SocketChannel socketChannel =SocketChannel.open();

        //2.设置连接IP和端口号
        socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));

        //3.写数据
        socketChannel.write(ByteBuffer.wrap("老板,该还钱了".getBytes(StandardCharsets.UTF_8)));

        //4.读取服务器写回的数据
        ByteBuffer readBuffer =ByteBuffer.allocate(1024);
        int read =socketChannel.read(readBuffer);
        System.out.println("服务端消息:" + new String(readBuffer.array(),0,read,StandardCharsets.UTF_8));

        //5.释放资源
        socketChannel.close();


    }
}

第六部分:Selector(选择器)

1.Selector基本介绍

可以用一个线程,处理多个的客户端连接,就会使用到NIO的Selector(选择器).

Selector 能够检测多个注册的服务端通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

1.没有选择器的情况

在这种没有选择器的情况下,对应每个连接对应一个处理线程. 但是连接并不能马上就会发送信息,所以还会产生资源浪费。

2.有选择器的情况

只有在通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程, 避免了多线程之间的上下文切换导致的开销。

2.Selector常用API介绍

Selector是一个抽象类。

1.Selector常用方法:

Selector.open()

得到一个选择器对象;
selector.select()

阻塞 监控所有注册的通道,当有对应的事件操作时, 会将SelectionKey放入集合内部并返回事件数量;
selector.select(1000)

阻塞 1000 毫秒,监控所有注册的通道,当有对应的事件操作时, 会将SelectionKey放入集合内部并返回;

selector.selectedKeys()

返回存有SelectionKey的集合

 2. SelectionKey常用方法:

SelectionKey.isAcceptable()

是否是连接继续事件
SelectionKey.isConnectable()

是否是连接就绪事件
SelectionKey.isReadable()

是否是读就绪事件
SelectionKey.isWritable()

是否是写就绪事件

3.SelectionKey中定义的4种事件:

SelectionKey.OP_ACCEPT

—— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
SelectionKey.OP_CONNECT

—— 连接就绪事件,表示客户端与服务器的连接已经建立成功
SelectionKey.OP_READ

—— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
SelectionKey.OP_WRITE

—— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)

3.Selector服务端实现步骤

1. 打开一个服务端通道
2. 绑定对应的端口号
3. 通道默认是阻塞的,需要设置为非阻塞
4. 创建选择器
5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
6. 检查选择器是否有事件
7. 获取事件集合
8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
11. 得到客户端通道,读取数据到缓冲区
12. 给客户端回写数据
13. 从集合中删除对应的事件, 因为防止二次处理.

4.DEMO :服务器——选择器

package java2.buffer.temp.temp7;

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.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

public class NIOSelectorServer {
    public static void main(String[] args) throws IOException,InterruptedException {

        //1.打开一个服务器通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2.绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));

        //3.通道默认是阻塞的,需要设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //4.创建选择器
        Selector selector=Selector.open();

        //5.将服务器通道注册到选择器上,并且指定注册监听事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功......");

        while(true){
            //6.检查选择器是否有事件
            int select =selector.select(2000);
            if(select==0){
                continue;
            }

            //7.获取事件集合
            Set<SelectionKey> selectionKeys =selector.selectedKeys();
            Iterator<SelectionKey> iterator =selectionKeys.iterator();
            while(iterator.hasNext()){
                //8.判断事件是否是客户端连接事件SelectionKey.isAcceptable()
                SelectionKey key =iterator.next();

                //9.得到客户端通道,并将通道注册到选择器上,并且指定监听事件为OP_READ
                if(key.isAcceptable()){
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端已连接......" + socketChannel);

                    //必须设置通道为非阻塞, 因为selector需要轮询监听每个通道的事件
                    socketChannel.configureBlocking(false);

                    //并指定监听事件为OP_READ
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }

                //10.判断是否是客户端读就绪事件SelectionKey.isReadable()
                if (key.isReadable()) {

                //11.得到客户端通道,读取数据到缓冲区
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = socketChannel.read(byteBuffer);
                    if (read > 0) {System.out.println("客户端消息:" +
                            new String(byteBuffer.array(), 0, read,
                                    StandardCharsets.UTF_8));

                    //12.给客户端回写数据
                        socketChannel.write(ByteBuffer.wrap("没钱".getBytes(StandardCharsets.UTF_8)));
                                socketChannel.close();
                    }
                }

                    //13.从集合中删除对应的事件, 因为防止二次处理.
                   iterator.remove();
            }
            }
        }
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java网络编程——NIO编程 的相关文章

随机推荐

  • 方法-学术翻译工具

    1 推荐谷歌 2 其它
  • python学习笔记03

    函数 def functionname parameters 函数文档字符串 functionsuite return expression args 可变参数 可以是从零个到任意个 自动组装成元组 kw 关键字参数 可以是从零个到任意个
  • numpy

    一 什么是维度 需要几个维度描述一个空间 一维 x 二维 x y 三维 x y z 1 1 创建NumPy数组的多种方式 创建NumPy数组的多种方式 array 将输入数据转换为ndarray 推断dtype或显示指定 arange 类似
  • 机器学习—关联规则分析之Apriori算法及其python实现

    文章目录 引言 一 一些概念 1 关联规则的一般形式 2 最小支持度和最小置信度 3 项集 4 支持度计数 二 Apriori算法 使用候选产生频繁项集 1 Apriori的性质 2 Apriori算法实现过程 3 Apriori算法实现过
  • Pandsa时间序列采样频率滑窗及重采样

    目录 Pandas时间序列采样频率滑窗 1 滑窗函数rolling 获取近7天的销售总量 2 shift 及 diff 重采样 resample pandas时间戳及时间差 pandas日期处理DT对象 Pandas时间序列采样频率滑窗 1
  • SpringBoot发送邮件

    目录 1 获取授权码 2 jar包引入 3 配置application 4 代码实现 1 获取授权码 以126邮箱为例 点开设置 选择POP3 SMTP IMAP 开启POP3 SMTP服务 新增授权密码 扫码二维码 发送要求的短信内容到指
  • 狂神说Mybatis课程笔记

    文章目录 1 第一个Mybatis程序 1 1 搭建环境 1 2 创建一个模块 1 3 编写代码 1 4 测试 2 CURD 增删改查 2 1 namespace 2 2 Select insert update delete 2 3 分析
  • 测试多线程任务

    作业需求 任务1 定义一个全局变量 int a 10 主线程能否访问到 分支线程能否访问到 任务2 分支线程中修改上述的a 20 问主线程中访问该a 是10还是20 任务3 在主线程定义一个局部变量int b 1 分支线程能否访问到b 任务
  • 光纤光缆基础知识

    光纤介绍 光纤布线中使用光波的几个波段 800nm 900nm 短波波段 1250nm 13500nm 短波波段 1500nm 1600nm 短波波段 多模光纤运行波长为850nm或1300nm 而单模光纤运行波长则为1310nm或1550
  • 微服务网关鉴权:gateway使用、网关限流使用、用户密码加密、JWT鉴权

    点击关注 芋道源码 2022 09 05 10 32 发表于上海 收录于合集 芋道源码1000个 点击上方 芋道源码 选择 设为星标 管她前浪 还是后浪 能浪的浪 才是好浪 每天 10 33 更新文章 每天掉亿点点头发 源码精品专栏 原创
  • 使用BPMN和微服务进行编排 ——是好做法还是坏做法?

    马丁 Martin Fowler 在他著名的微服务文章中建议 敏捷的终端和愚笨的管道 他指出 微服务社区赞成另一种方法 敏捷的终端和愚笨的管道 从微服务构建的应用程序旨在尽可能地解耦和衔接 他们拥有自己的域逻辑 而更多地在经典的Unix意义
  • c语言指针实现冒泡排序及其优化

    冒泡排序是一个十分容易实现的算法 简单说明一下 假设数组长度为 N 要求从小到大排序 1从第一个数开始比较相邻两个元素 如果前面的数据大于后面的数据 就将二个数据交换 2对数组元素进行一次第一次遍历后 最大的数据就 沉 到了数组最后一个位置
  • 03_GCC与Makefile的使用

    windows下c语言的编译 1 预处理 把 h c展开形成一个文件 宏定义直接替换 还有头文件 库文件的展开形成 i文件 对应的GCC gcc e hello c o hello i 2 汇编 生成汇编文件 gcc s hello i o
  • 【Elasticsearch】ElasticSearch 7.8 多字段权重排序

    1 概述 转载并且补充 https mp weixin qq com s 0g86s o7kgn8ZUxA3UBc0w 请看原文 读者提问 ES 的权重排序有没有示列 参考参考 刚好之前也稍微接触过 于是写了这篇文章 可以简单参考下 在很多
  • msi文件安装MySQL

    文章目录 步骤如下 1 官网下载msi安装文件 2 运行MySQL installer 3 通过MySQL installer配置服务 4 验证 5 安装目录介绍 6 修改指定的数据文件 步骤如下 1 官网下载msi安装文件 官网地址 上述
  • 爬虫入门(三)连接mongodb

    连接mongodb 虽然说我们前面写了一个比较健壮的爬虫了 但是人生难免有意外 万一中断了 我们又要重新开始爬虫下载图片了 抓狂 那么我们想呢 怎么写一个判断图片有没有下载过呢 显然我们不能在文件夹里遍历 会慢到爆炸的 那么我们就可以借助数
  • 深度学习目标检测工具箱mmdetection,训练自己的数据

    文章目录 一 简介 二 安装教程 1 使用conda创建Python虚拟环境 可选 2 安装PyTorch 1 1 3 安装Cython 4 安装mmcv 5 安装mmdetection 6 测试Demo 7 准备自己的数据 8 训练 一
  • 一篇文章掌握整个JVM,JVM超详细解析!!!

    不懂JVM看完这一篇文章你就会非常懂了 文章很长 非常详细 先想想一些问题 1 我们开发人员编写的Java代码是怎么让电脑认识的 首先先了解电脑是二进制的系统 他只认识 01010101 比如我们经常要编写 HelloWord java 电
  • R语言-解决问题:程辑包‘xxx’是用R版本3.3.4 来建造的

    用R的时候会碰到这种情形 warning 程辑包 xxx 是用R版本3 3 4 来建造的 尽管R这样提示 但是不影响这个包的使用 因此是可以继续用的 只是它会有这样的提示而已 出现这种警告的原因是自己电脑上的R版本不是最新的了 需要更新 如
  • Java网络编程——NIO编程

    目录 第一部分 NIO介绍 1 NIO三大核心部分 2 NIO的工作机制 3 Java NIO的非阻塞模式 第二部分 NIO和BIO的比较 第三部分 NIO三大核心原理 第四部分 缓冲区 Buffer 1 缓冲区基本介绍 2 Buffer常