Netty学习笔记

匆匆过客 提交于 2019-12-05 15:25:57

Netty简单认识:

  1) Netty 是由JBOSS 提供的一个Java 开源框架。

  2) Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络I0 程序。

  3) Netty 主要针对在TCP协议下的使用

  4)Netty本质是- 个NIO框架,适用于服务器通讯相关的多种应用场景

 

Netty应用:

  https://netty.io/wiki/related-projects.html这里面是和netty有关的框架

  Netty应用于网络间的通信,如阿里的dubbo框架,应用于服务之间的调用;谷歌的grpc框架;

 

I/O模型:

  netty是基于nio开发,所以我们需要了解java中的I/O模型:

  • Bio:同步阻塞io,服务器实现模式为一 一个连接-一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理

  如果这个连接不做任何事情会造成不必要的线程开销,适用于连接数小的和固定的,编程简单。

 

 

 

  • Nio:同步非阻塞,JavaNIO :同步非阻塞, 服务器实现模式为- -个线程处理多个请求(连接),即客户端发送的连接请求

  都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理,适用于连接数多的连接时间短的,编程复杂。(1.4以后版本)

 

 

 

  • Aio:异步非阻塞,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,

  一般适用于连接数较多且连接时间较长的应用,适用于连接数多的和连接时间长的,编程复杂(1.7以后版本)

 

bio:

public class ServerClient {
    public static void main(String[] args) throws IOException {
        //创建自定义线程池
        ExecutorService executorService = new ThreadPoolExecutor(
                3,
                7,
                2000,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(3),
                new ThreadPoolExecutor.CallerRunsPolicy());

        //启动服务端
        ServerSocket serverSocket = new ServerSocket(8086);
        System.out.println("服务端启动,等待连接。。。。。。。");

        while(true){
            //等待连接
            final Socket socket = serverSocket.accept();
            System.out.println("连接上一个客户。。。。。。"+socket.getPort());
            executorService.execute(()->{
                handel(socket);
            });
        }
    }

    //处理读取到的数据
    public static void handel(Socket socket){
        try{
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream();
             while (true){
                 //等待读取数据
                 int length = inputStream.read(bytes);
                 if(length != -1){
                     System.out.println("读取到数据"+new String(bytes,0,length));
                 }else{
                     break;
                 }
           }
        }catch (Exception e){
           e.printStackTrace();
        }finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

通过这个demo我们可以看到,每个客户端需要独立的线程,使用bio会在等待连接和读取数据两处进行阻塞,造成线程资源的浪费。

 

nio:

  1》 NIO 有三大核心部分: Channel(通道), Buffer(缓冲区), Selector(选择器)

  2》NIO是面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后

  移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络

  3》Java NIO的非阻塞模式,使-一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,

  如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可

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

  通俗理解: NIO是可以做到用-一个线程来处理多个操作的。假设有10000 个请求过来,根据实际情况,可以分配50或者100个线程来处理。

  不像之前的阻塞I0那样,非得分配10000个。

  4》HTTP2.0采用多路复用技术

  5》nio中buffer的demo:

 

public class NioBaise {

    public static void main(String[] args) {
        //创建容量为5的IntBuffer
        IntBuffer intBuffer = IntBuffer.allocate(5);
        for (int i = 0; i <5 ; i++) {
            intBuffer.put(i);
        }
        //切换,有写变读
        intBuffer.flip();
        //判断是否还有数据
        while (intBuffer.hasRemaining()){
            //获取数据,get()获取数据后,就会移动下标
            System.out.println(intBuffer.get());
        }
    }
}

 

NIO 和BIO的比较:

  1》BIO 是阻塞的,NIO则是非阻塞的      

  2》 BIO 以流的方式处理数据,而NIO以块的方式处理数据,块I/O 的效率比流I0高很多

  3》 BIO 基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区 )进行操作,数据总是从通道

    读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择 器)用于监听多个通道的事件(比如:连接请求,

    数据到达等),因此使用单个线程就可以监听多个客户端通道

 

nio中的buffer:

有四个重要属性

  Capacity:容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变

  Limit:表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的

  Position:位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,每次读写缓冲区数据时都会改变改值,

  Mark:标记

Buffer中常用方法:

 

 

 在Buffer中ByteBuffer是最常用的(二进制数据),主要方法如下:

 

 

 

nio中的channel:

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

  ●通道可以同时进行读写, 而流只能读或者只能写

  ●通道可以实现异步读写 数据

    ●通道可以从缓冲读数据, 也可以写数据到缓冲:

2) BIO 中的stream 是单向的,例如FilelnputStream 对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作。

3) Channel 在NIO中是一个接口 public interface Channel extends Closeable{}

4)常用的Channel 类有: FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel。

5) FileChannel 用于文件的数据读写,DatagramChannel 用于UDP 的数据读写,ServerSocketChannel 和SocketChannel用于TCP的数据读写。

 FileChannel常用方法:

FileChannel的demo1:向文件中写入字符串

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

        //创建文件流
        FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello.txt");

        //将流转为通道
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建换缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(str.getBytes());

        //将byteBuffer进行反转
        byteBuffer.flip();
       
        //将缓冲区数据写入到fileChannel通道里
        fileChannel.write(byteBuffer);

        //关闭管道
        fileChannel.close();

    }
}

 FileChannel的demo2:向文件中读取字符串

public class FileChannelDemo2 {
    public static void main(String[] args) throws IOException {


        //创建文件输入流
        FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");

        //将流转为通道
        FileChannel fileChannel = fileInputStream.getChannel();

        //创建换缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(fileInputStream.available());

        //将管道里面的数据写入到byteBuffer中
        fileChannel.read(byteBuffer);

        //将byteBuffer进行反转
        byteBuffer.flip();

        System.out.println(new String(byteBuffer.array()));

        //关闭管道
        fileChannel.close();

    }
}

FileChannel的demo3:文件复制:

public class FileChannelDemo3 {
    public static void main(String[] args) throws IOException {
        //创建文件输入流
        FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");
        FileChannel fileChannel = fileInputStream.getChannel();

        //创建文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello2.txt");
        FileChannel channel = fileOutputStream.getChannel();

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

        while (true){
            //复位
            byteBuffer.clear();
            //将管道里面的数据写入到byteBuffer中
            int read = fileChannel.read(byteBuffer);
            if(read == -1){
                break;
            }
            //将byteBuffer进行反转
            byteBuffer.flip();
            //将byteBuffer里面的数据写入到通道中
            channel.write(byteBuffer);
        }
        System.out.println("写入完成");
        //关闭管道
        fileChannel.close();
        fileOutputStream.close();
        fileInputStream.close();
    }
}

 

FileChannel的demo4:文件复制,直接使用transferFrom()方法;

public class FileChannelDemo4 {
    public static void main(String[] args) throws IOException {
        //创建文件输入流
        FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");
        FileChannel sourceChannel = fileInputStream.getChannel();

        //创建文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello2.txt");
        FileChannel targetChannel = fileOutputStream.getChannel();

        //复制
       targetChannel.transferFrom(sourceChannel,0,sourceChannel.size());

       sourceChannel.close();
       targetChannel.close();
       fileInputStream.close();
       fileOutputStream.close();
    }
}

 

Buffer和channel的注意点:

1) ByteBuffer 支持类型化的put和get, put放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否

   则可能有BufferUnderflowException 异常。

2)可以将-一个普通Buffer转成只读Buffer,使用asReadOnlyBuffer()返回ByteBuffer

  public abstract ByteBuffer asReadOnlyBuffer()

3) NIO 还提供了MappedByteBuffer, 可以让文件直接在内存 (堆外的内存)中进行修改,而 如何同步到文件由nio完成。

demo:

public class MapedByteBufferDemo {
    public static void main(String[] args) throws Exception{
        RandomAccessFile randomAccessFile = new RandomAccessFile("e:\\hello.txt","rw");

        FileChannel channel = randomAccessFile.getChannel();

        /**
         * 参数一:读写模式
         * 参数二:开始读取的位值
         * 参数三:是映射到内存的大小,而不是索引的大小
         */
        MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        map.put(0,(byte)'H');
        map.put(2,(byte)'y');

        //如果超过大小会发生异常:java.lang.IndexOutOfBoundsException
        //map.put(5,(byte)'0');

        randomAccessFile.close();
        System.out.println("修改成功");
    }
}

 

nio中的Selector:

示意图及特点:

 

 

 

 

 

 

   常用方法:

public static Selector open() throws IOException:得到一个选择器对象public int select(long timeout);//监控所有注册的通道,当其中有I0操作可以进行时,将对应的SelectionKey加入到内部集合中并返回,参数用来设置超时时间public Set<SelectionKey> selectedKeys()//从内部集合中得到所有的SelectionKey方法说明:

 过程:

1)当客户端连接时, 会通过ServerSocketChannel得到SocketChannel

2) Selector 进行监听select 方法,返回有事件发生的通道的个数.

3)将socketChannel注册到Selector上, register(Selector sel, int ops),一个selector上可以注册多个SocketChannel

4)注册后返回一个SelectionKey, 会和该Selector关联(集合)

5) 进-步得到各个SelectionKey (有事件发生)

6)在通过SelectionKey 反向 获取SocketChannel ,方法channel()

7)可以通过得到的 channel, 完成业务 处理

 

demo:NioServer(服务端)

public class NioServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //绑定网络端口
        serverSocketChannel.socket().bind(new InetSocketAddress(666));

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

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

        //将serverSocketChannel注册到selector中,selector只关心OP_ACCEPT事件
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //循环等待客户连接
        while (true){
            //没有事件就返回
            if(selector.select(1000)==0){
                System.out.println("服务端等待连接。。。。。。");
                continue;
            }
            //获取到相关SelectionKey集合,表示获取到关注的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            if(keyIterator.hasNext()){
                //获取到SelectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的事件作相应的处理
                if(key.isAcceptable()){//表示有新客户端连接
                    //每次连接一个客户端都会产生新的SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("每次连接一个客户端都会产生新的SocketChannel的hashcode"+socketChannel.hashCode());
                    //设置客户端为非阻塞的
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector,关注事件为OP_READ,同时给socketChannel关联一个buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("连接上一个客户端....");
                }
                if(key.isReadable()){//发生OP_READ事件
                    //通过key反向获取到channel
                    SocketChannel socketChannel = (SocketChannel)key.channel();
                    //获取到该channer关联的buffer
                    ByteBuffer byteBuffer =(ByteBuffer) key.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("读取到数据。。。"+new String(byteBuffer.array()));
                }
                //移除seletorKey,防止重复操作。
                keyIterator.remove();
            }

        }
    }
}

客户端:NioClient

public class NioClient {
    public static void main(String[] args) throws IOException {
        //连接服务端的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);

        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置为非阻塞的
        socketChannel.configureBlocking(false);

        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作");
            }
        }
        String str = "hello";
        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        //将buffer数据写入到channel中
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}

 

SelectKey介绍:

1) SelectionKey,表示Selector 和网络通道的注册关系,共四种:

int OP_ ACCEPT:有新的网络连接可以accept,值为16

int OP_ CONNECT:代表连接已经建立,值为8

intOP_ READ:代表读操作,值为1

intOP_ WRITE:代表写操作,值为4

源码是采用位操作符:

public static final int OP_READ= 1 << 0;

public static final int OP_WRITE= 1 << 2;

public static final int OP_CONNECT= 1 << 3;

public static final int OP_ACCEPT= 1 <<4;

 相关方法:

public abstract Selector selecto/(;/得到与之关联的Selector对象)

public abstract SelectableChannel chanel)://得到与之关联的通道

public final Object ttachment():/得到与之关联的共享数据

public abstract SelectionKey interestOps(int ops//设置或改变监听事件

public final boolean isAcceptable(;//是否可以accept

public final boolean isReadablel(;//是否可以读

public final boolean isWritable(;//是否可以写

 

ServerSocketChannel:在服务端监听客户端的连接

public static ServerSocketChannel open(),得到-一个ServerSocketChannel通道

public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号

public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值false表示采用非阻塞模式

public SocketChannel accept),接受一个连接, 返回代表这个连接的通道对象

public final SelectionKey register(Selectorsel, intops),注册一个选择器并设置监听事件

 

SocketChannel:网络io操作,负责数据的读写操作。Nio把数据从缓存中写入通道或把通道里的数据读取到缓存中。

public static SocketChannel open/://得到一个SocketChannel通道

public final SelectableChannel configureBlocking(boolean blok;//设置阻塞或非阻塞模式,取值false表示采用非阻塞模式

public boolean connect(SocketAddress remote://连接服务器

public boolean finishConnet://如果上面的方法连接失败,接下来就要通过该方法完成连接操作

public int write(ByteBuffer sc);//通道里写数据

public int read(ByteBuffer dst);//从通道里读数据

public final SelectionKey register(Selector sel, int ops, Object a://注册个选择器并设置监听事件,最后一个参数可以设置共享数据

public final void close(;//关闭通道

SocketChannel和ServerSocketChannel区别:SocketChannel更注重于数据上的操作,ServerSocketChannel注重于连接上。

 群聊demo:服务端

package com.netty.groupChat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class GroupChatServer {
    private  int port = 9999;
    private  ServerSocketChannel listenChannel;
    private  Selector selector;

    //初始化
    public GroupChatServer(){
        try{
            //创建选择器
             selector = Selector.open();
             //创建ServerSocketChannel
            listenChannel = ServerSocketChannel.open();
            //绑定端口
            listenChannel.socket().bind(new InetSocketAddress(port));
            //设置非阻塞模式
            listenChannel.configureBlocking(false);
            //将ServerSocketChannel注册到Secletor中,接受连接事件
            listenChannel.register(selector,SelectionKey.OP_ACCEPT);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //监听连接
    public  void listenEvent() {
        try {
            while (true) {
                //有事件才处理
               if(selector.select() > 0){
                   //遍历循环得到SelectionKey
                   Set<SelectionKey> selectionKeys = selector.selectedKeys();
                   Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                   while (keyIterator.hasNext()) {
                       //得到SelectKey
                       SelectionKey key = keyIterator.next();

                       //监听对应的事件,这里监听连接事件
                       if (key.isAcceptable()) {
                           //创建SocketChannel
                           SocketChannel sc =  listenChannel.accept();
                           //设置非阻塞
                           sc.configureBlocking(false);
                           //将SocketChannel注册到Selector中,处理读事件
                           sc.register(selector, SelectionKey.OP_READ);
                           //提示上线
                           System.out.println(sc.getRemoteAddress() + "上线");
                       }
                       //处理读事件
                       if (key.isReadable()) {
                               readData(key);
                       }
                       //删除当前的key,避免重复处理
                       keyIterator.remove();
                   }
               }else{
                   System.out.println("等待。。。。");
               }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {

        }
    }

    //读取数据
    public  void readData(SelectionKey key){
        //通过key获取绑定的SocketChannel
        SocketChannel socketChannel = (SocketChannel)key.channel();
        //创建缓存区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try{
            //从通道的数据读取到缓冲区中
            int read = socketChannel.read(byteBuffer);
            //当有数据时
            if(read>0){
                //获取缓冲区数据
                String msg = new String(byteBuffer.array());
                //虎丘客户端信息
                System.out.println("客户端信息。。。。。"+msg);
                //向其他用户分发信息
                forwardMsg(msg,socketChannel);
            }
        }catch (Exception e){
            try{
                //取消注册
                key.cancel();
                //关闭通道
                socketChannel.close();
                System.out.println(socketChannel.getRemoteAddress()+"下线");
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
    
    //转发信息
    public   void forwardMsg(String msg,SocketChannel self) throws IOException {
        System.out.println("消息转发。。。。。");
        //获取到所有注册到selector中的SocketChannel
        Set<SelectionKey> keys = selector.keys();
        for(SelectionKey key : keys){
            Channel targetChannel = key.channel();
            //不向自己发消息
            if(targetChannel instanceof SocketChannel && targetChannel != self){
                SocketChannel dest = (SocketChannel) targetChannel;
                ByteBuffer byteBuffer =  ByteBuffer.wrap(msg.getBytes());
                //将数据从缓冲区中读到通道中
                dest.write(byteBuffer);
            }
        }
    }
    
    public static void main(String[] args) {
        GroupChatServer groupChatServer = new GroupChatServer();
        System.out.println("服务器启动。。。");
        groupChatServer.listenEvent();

    }
}

 客户端:

package com.netty.groupChat;

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.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class GroupChatClient {
    private String host = "127.0.0.1";
    private int port = 9999;
    private SocketChannel socketChannel;
    private  Selector selector;
    private String user;

    public GroupChatClient(){
        try {
            //创建选择器
            selector = Selector.open();
            //连接服务器
            socketChannel = socketChannel.open(new InetSocketAddress(host,port));
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //将socketChannel注册到selector,处理OP_READ事件
            socketChannel.register(selector, SelectionKey.OP_READ);
            //得到用户
            user = socketChannel.getRemoteAddress().toString();
            System.out.println("ok ............");

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


    //发送消息
    public void sendInfo(String info) {
        try{
            info =  user+"说:"+info;
            ByteBuffer byteBuffer = ByteBuffer.wrap(info.getBytes());
            socketChannel.write(byteBuffer);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //读取数据
    public void readInfo() {
        try{
            int readChannel = selector.select();
            //有事件
            if(readChannel > 0){
                //遍历循环,得到SelectionKey
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    //处理读取事件
                    if(key.isReadable()){
                        //通过key获取绑定的SocketChannel
                        SocketChannel socketChannel =(SocketChannel) key.channel();
                        //创建缓冲区
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        //将通道里的数据写出到byteBuffer
                        socketChannel.read(byteBuffer);
                        //获取信息
                        System.out.println(new String(byteBuffer.array()));
                    }

                    //删除当前的SelectionKey,防止重复
                    iterator.remove();
                }
            }else{

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

    public static void main(String[] args) throws IOException {

        GroupChatClient groupChatClient = new GroupChatClient();

            new Thread(() -> {
               while (true) {
                   groupChatClient.readInfo();
                   try {
                       Thread.currentThread().sleep(3000);
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
               }
            }).start();

            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                groupChatClient.sendInfo(scanner.nextLine());
            }
        };
    }

NIO与零拷贝.

1)零拷贝是网络编程的关键,很多性能优化都离不开。

2)在Java 程序中,常用的零拷贝有mmap(内存映射)和sendFile. 那么,他们在OS里,到底是怎么样的一个

的设计?我们分析mmap和sendFile 这两个零拷贝

 

传统IO模型:

DMA: directmemory access直接内存拷则(不使用CPU)

 

mmap优化:

1) mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据.这样,在进行网

络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图

 

sendFile优化:

Linux2.1版本提供了sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到

Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。

 

零拷贝理解:

1) 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kermel buffer 有

一份数据),

2)零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享

以及无CPU校验和计算。

 

mmap和sendFile的区别:

1》mmap适合小数据量读写,sendFile 适合大文件传输。

2》mmap需要4次上下文切换,3次数据拷贝: sendFile 需要3次上下文切换,最少2次数据拷贝。

3》sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket 缓冲区)。

 

在nio中FileChannel中的transformTo()方法底层原理就是零拷贝。

demo:

public class NioCopyServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9998));
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
            int readCount = socketChannel.read(byteBuffer);
            while (readCount == -1){
                break;
            }
            byteBuffer.rewind();
        }
        
    }
}

客户端

public interface NioCopyClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));

        FileInputStream fileInputStream = new FileInputStream(new File("e:\\hello.txt"));


        FileChannel fileChannel = fileInputStream.getChannel();

        long startTimeMillis = System.currentTimeMillis();


        long transferTo = fileChannel.transferTo(0, fileChannel.size(), socketChannel);

        long endTimeMillis = System.currentTimeMillis();

        System.out.println("文件大小:"+transferTo+",耗时:"+(endTimeMillis-startTimeMillis));

    }
}

 

 待更。。。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!