Netty入门学习笔记(一)

一、Netty介绍和应用场景

Netty的介绍

在这里插入图片描述
同步与异步
在这里插入图片描述
在这里插入图片描述

Netty的应用场景

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
https://netty.io/wiki/related-projects.html

二、Java BIO编程

I/O模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Java BIO的基本介绍

在这里插入图片描述
在这里插入图片描述

Java BIO应用实例

在这里插入图片描述

public class BIOServer {

    public static void main(String[] args) throws IOException {
        //创建线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();

        //创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务器启动完成...");

        while (true) {
            //监听,等待客户端连接
            System.out.println("等待连接...");
            final Socket socket = serverSocket.accept();
            System.out.println("已连接上客户端...");

            //创建一个线程与客户端进行通信
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    handler(socket);
                }
            });
        }
    }

    //与客户端通信
    public static void handler(Socket socket){
        try{
            System.out.println("线程信息:id=" + Thread.currentThread().getId()
                    + " name=" + Thread.currentThread().getName());

            byte[] bytes = new byte[1024];
            //通过socket获取输入流
            InputStream inputStream = socket.getInputStream();

            //循环读取客户端发送的数据
            while (true){
                System.out.println("线程信息:id=" + Thread.currentThread().getId()
                        + " name=" + Thread.currentThread().getName());

                System.out.println("read...");
                int read = inputStream.read(bytes);
                if(read != -1){
                    //输出读取的数据
                    System.out.print(new String(bytes, 0, read));
                }else{
                    break;
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                System.out.println("关闭client的连接");
                socket.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

三、Java NIO编程

Java NIO的基本介绍

在这里插入图片描述
在这里插入图片描述

案例说明NIO的Buffer

//Buffer的使用
public class BasicBuffer {
    public static void main(String[] args) {
        //创建一个intBuffer,大小为5,即存放五个int
        IntBuffer intBuffer = IntBuffer.allocate(5);

        //向buffer里存放数据
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i * 2);
        }

        //从buffer中读取数据
        //首先进行读写切换
        intBuffer.flip();
        //然后循环读取
        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
    }
}

在这里插入图片描述

NIO三大核心原理示意图

在这里插入图片描述
在这里插入图片描述

缓冲区(Buffer)

基本介绍

在这里插入图片描述

Buffer类及子类

在这里插入图片描述
在这里插入图片描述

Buffer类相关方法一览

在这里插入图片描述

ByteBuffer

在这里插入图片描述

通道(Channel)

基本介绍

在这里插入图片描述
在这里插入图片描述

FileChannel类

在channel的角度,read是写到buffer,write是写入channel。
在这里插入图片描述

应用实例

例1

在这里插入图片描述
在这里插入图片描述

public class NIOFileChannel01 {
    public static void main(String[] args) throws Exception {
        String str = "hello";

        //创建一个输出流 -> channel
        FileOutputStream fileOutputStream = new FileOutputStream("./file01.txt");

        //通过 fileOutputStream 获取对应的 filechannel
        //fileChannel的真实类型是FileChannelImpl,因为FileChannel是一个抽象类
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建一个缓冲区ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //将str放入byteBuffer
        byteBuffer.put(str.getBytes());

        //切换为读模式
        byteBuffer.flip();

        //将byteBuffer里的数据写入到fileChannel
        fileChannel.write(byteBuffer);

        //关闭资源
        fileOutputStream.close();
    }
}

在这里插入图片描述

例2

在这里插入图片描述
在这里插入图片描述

public class NIOFileChannel02 {
    public static void main(String[] args) throws Exception {

        //创建文件的输入流
        File file = new File("./file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        //通过 fileInputStream 获取对应的FileChannel(实际的类型是FileChannelImpl)
        FileChannel fileChannel = fileInputStream.getChannel();

        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());

        //将fileChannel的数据读到byteBuffer中
        fileChannel.read(byteBuffer);

        //切换为读模式
        //不进行flip也可以,因为下面直接把整个byteBuffer的数据转为string
        //byteBuffer.flip();

        //将byteBuffer的字节数据转为string并打印
        System.out.println(new String(byteBuffer.array()));

        //关闭资源
        fileInputStream.close();
    }
}

在这里插入图片描述

例3

在这里插入图片描述
在这里插入图片描述

public class NIOFileChannel03 {
    public static void main(String[] args) throws Exception {

        //文件输入流
        FileInputStream fileInputStream = new FileInputStream("./1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();

        //文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream("./2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();

        //缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(5);

        while (true){
            //返回读取的数据数,-1代表读取完毕
            int read = fileChannel01.read(byteBuffer);
            if(read == -1){
               break;
            }
            //切换为读模式
            byteBuffer.flip();

            //将数据写入到fileChannel02
            fileChannel02.write(byteBuffer);

            //清空byteBuffer
            byteBuffer.clear();
            /*
            (其实里面的数据没有删除,这是只是把属性初始化,下次写入覆盖掉原来的数据,没覆盖前是可以读到原来的数据的)
                public final Buffer clear() {
                    position = 0;
                    limit = capacity;
                    mark = -1;
                    return this;
                }
             */
        }

        //关闭资源
        fileOutputStream.close();
        fileInputStream.close();
    }
}

在这里插入图片描述

例4

在这里插入图片描述

public class NIOFileChannel04 {
    public static void main(String[] args) throws Exception {
        //创建流
        FileInputStream fileInputStream = new FileInputStream("1.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");

        //获取filechannel
        FileChannel sourceCh = fileInputStream.getChannel();
        FileChannel destCh = fileOutputStream.getChannel();

        //使用transferFrom完成拷贝    sourceCh -> destCh
        destCh.transferFrom(sourceCh, 0, sourceCh.size());
        //使用transferTo完成拷贝    sourceCh -> destCh
//        sourceCh.transferTo(0, sourceCh.size(), destCh);
        //transferFrom与transferTo相同,只是使用的对象不一样

        //关闭资源
        destCh.close();
        sourceCh.close();
        fileOutputStream.close();
        fileInputStream.close();
    }
}

在这里插入图片描述

关于Buffer和Channel的注意事项和细节

在这里插入图片描述

public class NIOByteBufferPutGet {
    public static void main(String[] args) {
        //创建一个byteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        //类型化方式放入
        buffer.putInt(100);

        //取出
        buffer.flip();

//        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
    }
}

在这里插入图片描述

public class ReadOnlyBuffer {
    public static void main(String[] args) {
        //创建byteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        //写入
        for (int i = 0; i < 64; i++) {
            buffer.put((byte) i);
        }

        //切换读模式
        buffer.flip();

        //得到一个只读buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());  //class java.nio.HeapByteBufferR

        //读取
        while (readOnlyBuffer.hasRemaining()){
            System.out.println(readOnlyBuffer.get());
        }

        //往readOnlyBuffer中写入,报错:ReadOnlyBufferException
        readOnlyBuffer.put((byte) 1);
    }
}

在这里插入图片描述

//MappedByteBuffer的好处:可让文件直接在内存(堆外内存)修改,操作系统不需要去拷贝一次
public class MappedByteBufferTest {
    public static void main(String[] args) throws Exception {
        //随机存取文件
        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        //获取通道
        FileChannel channel = randomAccessFile.getChannel();

        /*
            参数1:FileChannel.MapMode.READ_WRITE 读写模式
            参数2:可以直接修改的起始位置
            参数3:映射内存的大小(不是索引位置),即将1.txt的多少个字节映射到内存中
            即:可以直接修改的范围是 0到4
            因为MappedByteBuffer为抽象类,mappedByteBuffer实际类型为DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        mappedByteBuffer.put(0, (byte) 'A');
        mappedByteBuffer.put(1, (byte) 'B');
//        mappedByteBuffer.put(5, (byte) 'C');  //IndexOutOfBoundsException
        System.out.println("修改成功...");

        channel.close();
        randomAccessFile.close();
    }
}

在这里插入图片描述

//Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入    [分散]
//Gathering:从buffer读取数据时,可以采用buffer数组,依次读取      [聚合]
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws Exception {
        //使用 ServerSocketChannel 和 SocketChannel 网络
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        //绑定端口到socket,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);

        //创建buffer数组(不需要使用一块大的内存空间,拆成数组可以有效的利用内存碎片)
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[0] = ByteBuffer.allocate(3);

        //等待客户端连接(telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();

        //假定从客户端接收8个字节
        int messageLength = 8;
        //循环读取
        while (true){
            int byteRead = 0;

            while (byteRead < messageLength) {
                long l = socketChannel.read(byteBuffers);
                //累计读取的字节数
                byteRead += l;
                System.out.println("byteRead=" + byteRead);
                //使用流打印,看看当前的这个buffer的position和limit
                Arrays.asList(byteBuffers).stream().map(buffer -> "position=" + buffer.position()
                        + ", limit=" + buffer.limit()).forEach(System.out::println);
            }
            //将所有的buffer进行flip
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());

            //将数据读出显示到客户端
            long byteWrite = 0;
            while (byteWrite < messageLength) {
                long l = socketChannel.write(byteBuffers);
                byteWrite += l;
            }

            //将所有的buffer进行clear
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());

            System.out.println("byteRead=" + byteRead + " byteWrite=" + byteWrite + " messageLenth" + messageLength);
        }
    }
}

选择器(Selector)

基本介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Selector示意图和特点说明

在这里插入图片描述

Selector类相关的方法

在这里插入图片描述

注意事项

在这里插入图片描述

NIO 非阻塞 网络编程原理分析图

在这里插入图片描述
在这里插入图片描述
对上图的说明:
在这里插入图片描述

入门案例

在这里插入图片描述
服务器端:

//服务器端
public class NIOServer {
    public static void main(String[] args) throws Exception {
        //创建ServerSocketChannel -> ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到一个Selector对象
        Selector selector = Selector.open();

        //绑定一个端口6666,在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));

        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //把 serverSocketChannel 注册到 selector,关心的事件为OP_ACCEPT(连接事件)
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //selector.selectedKeys().size()为当前selector上现有的数量
        //selector.keys().size()为注册过的selectionKey的数量,叠加的
        System.out.println("注册后的selectionKey 数量=" + selector.keys().size());
        System.out.println("现有的selectionKey 数量=" + selector.selectedKeys().size());

        //循环等待客户端连接
        while (true){
            //阻塞1秒后返回对应的SelectionKey,如果返回0说明:1秒内没有事件发生

            //没有事件发送
            if(selector.select(1000) == 0){
                System.out.println("服务器等待了1秒,没有事件发生...");
                continue;
            }

            //如果返回大于0,就获取相关的SelectionKey集合
            //1.如果返回的值大于0,表示已经获取到关注的事件
            //2.selector.selectedKeys() 返回关注事件的集合
            //      通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            //使用迭代器遍历selectionKeys
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()){
                //获取到SelectionKey
                SelectionKey selectionKey = keyIterator.next();
                //根据selectionKey对应的通道发生的事件做相应的处理
                //如果是连接事件,有新的客户端连接(连接事件是服务器端产生的,监听服务器端的连接事件)
                if(selectionKey.isAcceptable()){
                    //给该客户端生成一个socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功,生成一个socketChannel " + socketChannel.hashCode());
                    //将socketChannel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketChannel 注册到selector,关注事件为OP_READ(读事件),同时给socketChannel关联一个buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                    System.out.println("注册后的selectionKey 数量=" + selector.keys().size());
                    System.out.println("现有的selectionKey 数量=" + selector.selectedKeys().size());
                }
                //如果是读事件(读事件是客户端产生的,监听客户端的读事件)
                if(selectionKey.isReadable()){
                    //通过 selectionKey 反向获取到对应的channel
                    SocketChannel channel = (SocketChannel)selectionKey.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer)selectionKey.attachment();
                    channel.read(buffer);
                    buffer.flip();
                    System.out.println("from 客户端:" + new String(buffer.array(), 0, buffer.limit()));
                    buffer.clear();
                }

                //手动从集合中移动当前的selectionKey,防止重复操作
                keyIterator.remove();
            }
        }
    }
}

客户端:

//客户端
public class NIOClient {
    public static void main(String[] args) throws Exception {
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);

        //提供服务器端的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);

        //如果没有成功连接上服务器
        if (!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作...");
            }
        }

        //如果连接成功
        String str = "hello";
        //wrap()传递一个byte数组到buffer中去,buffer的大小和传递进来的byte数组大小一致
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将buffer中的数据写入channel
        socketChannel.write(buffer);
        System.in.read();
    }
}

SelectionKey

在这里插入图片描述
在这里插入图片描述

ServerSocketChannel

在这里插入图片描述

SocketChannel

在这里插入图片描述

NIO网络编程应用实例——群聊系统

在这里插入图片描述

GroupChatServer
public class GroupChatServer {
    //定义属性
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;

    //构造器
    //初始化工作
    public GroupChatServer() {

        try {
            //初始化选择器
            selector = Selector.open();
            //初始化ServerSocketChannel
            listenChannel = ServerSocketChannel.open();
            //绑定端口
            listenChannel.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞模式
            listenChannel.configureBlocking(false);
            //将listenChannel注册到选择器上
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);

        }catch (IOException e){
            e.printStackTrace();
        }
    }

    //监听事件
    public void listen(){

        try {
            //循环处理
            while (true){
                int count = selector.select();
                //有事件要处理
                if(count > 0){
                    //遍历得到的selectionKey集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()){
                        //取出selectionKey
                        SelectionKey key = iterator.next();

                        //监听到accept事件
                        if(key.isAcceptable()) {
                            //获取连接的客户端SocketChannel
                            SocketChannel sc = listenChannel.accept();
                            //设置成非阻塞
                            sc.configureBlocking(false);
                            //将sc注册到selector
                            sc.register(selector, SelectionKey.OP_READ);
                            //提示客户端上线
                            System.out.println("【" + sc.getRemoteAddress() + "】上线");
                        }

                        //监听到read事件,通道是可读状态
                        if (key.isReadable()){
                            //处理读(专门写方法...)
                            readData(key);
                        }

                        //删除当前的key,防止重复处理
                        iterator.remove();
                    }
                }else{
                    System.out.println("等待中...");
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {

        }
    }


    //读取客户端消息
    private void readData(SelectionKey key){
        //定义一个socketChannel
        SocketChannel channel = null;
        try {
            //取到关联的channel
            channel = (SocketChannel) key.channel();
            //创建buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //读取通道的数据到缓冲区
            int count = channel.read(buffer);
            //根据count的值做处理
            //读取到数据
            if (count > 0){
                //切换为读模式
                buffer.flip();
                //把缓冲区的数据转成字符串
                String msg = new String(buffer.array(), 0, buffer.limit());
                //输出该消息
                System.out.println("from 客户端:" + msg);

                //向其他的客户端转发此消息(去掉自己),专门写一个方法来处理
                sendInfoToOtherClients(msg, channel);
            }

        }catch (IOException e){
            try {
                //离线提示
                //这里有bug,不能一下线就显示,下线后只有其他客户端发送消息后才能知道是已经下线并显示,不知道老师为什么能一下线就显示
                System.out.println("【" + channel.getRemoteAddress() + "】离线了...");
                //取消注册
                key.cancel();
                //关闭通道
                channel.close();

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }


    //转发信息给其他客户端(通道)
    private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
        System.out.println("服务器转发消息中...");

        //遍历所有注册到selector上的socketChannel,并排除自己self
        for (SelectionKey key : selector.keys()) {
            //通过key取出对应的socketChannel
            Channel targetChannel = key.channel();

            //因为取出来的可能是ServerSocketChannel,所以需要判断instanceof SocketChannel
            //排除自己
            if (targetChannel instanceof SocketChannel && targetChannel != self){

                //转型
                SocketChannel dest = (SocketChannel) targetChannel;
                //将msg存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer中的数据写入通道中
                dest.write(buffer);
            }
        }

    }


    public static void main(String[] args) {
        //创建服务器对象
        GroupChatServer server = new GroupChatServer();
        //监听事件
        server.listen();
    }
}
GroupChatClient
public class GroupChatClient {
    //定义相关属性
    //服务器的ip
    private final String HOST = "127.0.0.1";
    //服务器的端口
    private final int PORT = 6667;
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    //构造器,完成初始化工作
    public GroupChatClient(){

        try {
            //获取选择器
            selector = Selector.open();
            //连接服务器
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //将channel注册到selector
            socketChannel.register(selector, SelectionKey.OP_READ);
            //得到username
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(username + " is ok...");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    //向服务器发送消息
    public void sendInfo(String info){
        info = username + "说:" + info;

        try {
            //发送消息
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    //读取从服务器端回复的消息
    public void readInfo(){
        try {
            int readChannels = selector.select();
            //有可以用的通道
            if (readChannels > 0){
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                //遍历
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if(key.isReadable()){
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的数据转成字符串
                        buffer.flip();
                        String msg = new String(buffer.array(), 0, buffer.limit());
                        System.out.println(msg.trim());

                        //移除
                        iterator.remove();
                    }
                }
            }else {
                //System.out.println("没有可用的通道...");
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //启动客户端
        GroupChatClient client = new GroupChatClient();
        //启动一个线程,专门用来接收数据,每隔3秒,读取从服务器发送过来的数据
        new Thread(){
          @Override
          public void run(){
              while (true){
                  client.readInfo();
                  try {
                      Thread.sleep(3000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
        }.start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()){
            String str = scanner.nextLine();
            client.sendInfo(str);

        }

    }
}

零拷贝

基本介绍

在这里插入图片描述
零拷贝是指没有CPU拷贝,而非不拷贝,DMA拷贝是无法避免的。

传统IO数据读写

在这里插入图片描述
在这里插入图片描述
传统IO:4次拷贝,3次切换。

mmap优化

在这里插入图片描述
mmap优化:3次拷贝,3次切换。

sendFile优化

在这里插入图片描述
sendFile优化:3次拷贝,2次切换。
在这里插入图片描述
更新后的sendFile优化:2次拷贝,2次切换。

零拷贝的再次理解#

在这里插入图片描述

mmap和sendFile的区别

在这里插入图片描述

案例

在这里插入图片描述

传统IO实现代码

OldIOServer

public class OldIOServer {

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(7001);

        while (true) {
            Socket socket = serverSocket.accept();
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

            try {
                byte[] byteArray = new byte[4096];

                while (true) {
                    int readCount = dataInputStream.read(byteArray, 0, byteArray.length);

                    if (-1 == readCount) {
                        break;
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}

OldIOClient

public class OldIOClient {

    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 7001);

        String fileName = "protoc-3.6.1-win32.zip";
        InputStream inputStream = new FileInputStream(fileName);

        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

        byte[] buffer = new byte[4096];
        long readCount;
        long total = 0;

        long startTime = System.currentTimeMillis();

        while ((readCount = inputStream.read(buffer)) >= 0) {
            total += readCount;
            dataOutputStream.write(buffer);
        }

        System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));

        dataOutputStream.close();
        socket.close();
        inputStream.close();
    }
}
零拷贝实现代码

NewIOServer

public class NewIOServer {
    public static void main(String[] args) throws Exception {
        //指定端口
        InetSocketAddress address = new InetSocketAddress(7001);
        //得到ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到ServerSocket
        ServerSocket serverSocket = serverSocketChannel.socket();
        //地址绑定
        serverSocket.bind(address);
        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        //监听
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();

            int readCount = 0;
            while (-1 != readCount){
                try {
                    //读取数据
                    readCount  = socketChannel.read(byteBuffer);

                }catch (Exception e){
                    //e.printStackTrace();
                    break;
                }
                //倒带    position变为0,mark标记作废
                byteBuffer.rewind();
            }
        }
    }
}

NewIOClient

public class NewIOClient {
    public static void main(String[] args) throws Exception {
        //得到SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //连接
        socketChannel.connect(new InetSocketAddress("localhost", 7001));

        String filename = "zipkin-server-2.12.9-exec.jar";
        //得到一个文件channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();

        //准备发送
        long startTime = System.currentTimeMillis();

        //在linux/mac下,一个transferTo方法就可以完成传输
        //在windows下,一次调用transferTo方法只能发送8m,所以需要分段传输文件,分段后需要记录分段的位置,下一次从这个位置开始
        //transferTo底层使用了零拷贝,transferFrom也是

//        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
//        System.out.println("发送的总的字节数=" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));

        //分段传输,需要一个大一点的文件
        //如果小于8m,不分段
        if((int) fileChannel.size() < (8 * 1024 * 1024)){
            long transferCount = fileChannel.transferTo(0, (int) fileChannel.size(), socketChannel);
            System.out.println("发送的总的字节数=" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
        }else{
            //分段
            int sendCount = (int) (fileChannel.size() / (8 * 1024 * 1024));
            if((int) (fileChannel.size() % (8 * 1024 * 1024)) > 0){
                sendCount++;
            }

            //发送
            long transferCount = 0;
            int head = 0;
            int tail = 8 * 1024 * 1024;
            for (int i = 0; i < sendCount; i++) {
                transferCount += fileChannel.transferTo(head, tail, socketChannel);
                head = tail;
                //倒数第2段,修改tail
                if (i == sendCount - 2){
                    tail = (int) (fileChannel.size() % (8 * 1024 * 1024));
                }
            }
            System.out.println("分段数:"+ sendCount + " 发送的总的字节数:" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
        }

        //关闭通道
        fileChannel.close();
        socketChannel.close();
    }
}

Java AIO

基本介绍

在这里插入图片描述
http://www.52im.net/thread-306-1-1.html

BIO、NIO、AIO对比表

在这里插入图片描述

下一篇笔记:Netty入门学习笔记(二)

学习视频(p1-p35):https://www.bilibili.com/video/BV1DJ411m7NR?p=1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值