https://blog.csdn.net/qq_18860653/article/details/53406723

Netty的使用或许我们看着官网user guide还是很容易入门的。因为java nio使用非常的繁琐,netty对java nio进行了大量的封装。对于Netty的理解,我们首先需要了解NIO的原理和使用。所以,我也特别渴望去了解NIO这种通信模式。

官方的定义是:nio 是non-blocking的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。是不是很抽象?

在阅读《NIO入门》这篇技术文档之后,收获了很多。包括对Java NIO的理解和使用,所以也特别的感谢作者。

首先,还是来回顾以下从这篇文档中学到的要点。

为什么要使用 NIO?
NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

NIO最重要的组成部分

通道 Channels
缓冲区 Buffers
选择器 Selectors

Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。

在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

Channel是一个对象,可以通过它读取和写入数据

看完下面这个例子,基本上就理解buffer和channel的作用了

  1.  
    package yyf.java.nio.ibm;
  2.  
     
  3.  
    import java.io.*;
  4.  
    import java.nio.*;
  5.  
    import java.nio.channels.*;
  6.  
     
  7.  
    public class CopyFile {
  8.  
    static public void main(String args[]) throws Exception {
  9.  
     
  10.  
    String infile = "c://test/nio_copy.txt";
  11.  
    String outfile = "c://test/result.txt";
  12.  
     
  13.  
    FileInputStream fin = new FileInputStream(infile);
  14.  
    FileOutputStream fout = new FileOutputStream(outfile);
  15.  
    // 获取读的通道
  16.  
    FileChannel fcin = fin.getChannel();
  17.  
    // 获取写的通道
  18.  
    FileChannel fcout = fout.getChannel();
  19.  
    // 定义缓冲区,并指定大小
  20.  
    ByteBuffer buffer = ByteBuffer.allocate(1024);
  21.  
     
  22.  
    while (true) {
  23.  
    // 清空缓冲区
  24.  
    buffer.clear();
  25.  
    //从通道读取一个数据到缓冲区
  26.  
    int r = fcin.read(buffer);
  27.  
    //判断是否有从通道读到数据
  28.  
    if (r == -1) {
  29.  
    break;
  30.  
    }
  31.  
    //将buffer指针指向头部
  32.  
    buffer.flip();
  33.  
    //把缓冲区数据写入通道
  34.  
    fcout.write(buffer);
  35.  
    }
  36.  
    }
  37.  
    }

缓冲区主要是三个变量

position
limit
capacity
这三个变量一起可以跟踪缓冲区的状态和它所包含的数据。我们将在下面的小节中详细分析每一个变量,还要介绍它们如何适应典型的读/写(输入/输出)进程。在这个例子中,我们假定要将数据从一个输入通道拷贝到一个输出通道。
Position
您可以回想一下,缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。
同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。
Limit
limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
position 总是小于或者等于 limit。
Capacity
缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
limit 决不能大于 capacity。

缓冲区作为一个数组,这三个变量就是其中数据的标记,也很好理解。

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

接下来来看看具体的使用把,我创建了一个直接收消息的服务器(一边接收一边写数据可能对于新手不好理解)

服务端:

  1.  
    package yyf.java.nio.test;
  2.  
     
  3.  
    import java.net.InetSocketAddress;
  4.  
    import java.net.ServerSocket;
  5.  
    import java.nio.ByteBuffer;
  6.  
    import java.nio.channels.SelectionKey;
  7.  
    import java.nio.channels.Selector;
  8.  
    import java.nio.channels.ServerSocketChannel;
  9.  
    import java.nio.channels.SocketChannel;
  10.  
    import java.util.Iterator;
  11.  
    import java.util.Set;
  12.  
     
  13.  
    public class NioReceiver {
  14.  
    @SuppressWarnings("null")
  15.  
    public static void main(String[] args) throws Exception {
  16.  
    ByteBuffer echoBuffer = ByteBuffer.allocate(8);
  17.  
    ServerSocketChannel ssc = ServerSocketChannel.open();
  18.  
    Selector selector = Selector.open();
  19.  
    ssc.configureBlocking(false);
  20.  
    ServerSocket ss = ssc.socket();
  21.  
    InetSocketAddress address = new InetSocketAddress(8080);
  22.  
    ss.bind(address);
  23.  
    SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
  24.  
    System.out.println("开始监听……");
  25.  
    while (true) {
  26.  
    int num = selector.select();
  27.  
    Set selectedKeys = selector.selectedKeys();
  28.  
    Iterator it = selectedKeys.iterator();
  29.  
    while (it.hasNext()) {
  30.  
    SelectionKey sKey = (SelectionKey) it.next();
  31.  
    SocketChannel channel = null;
  32.  
    if (sKey.isAcceptable()) {
  33.  
    ServerSocketChannel sc = (ServerSocketChannel) key.channel();
  34.  
    channel = sc.accept();// 接受连接请求
  35.  
    channel.configureBlocking(false);
  36.  
    channel.register(selector, SelectionKey.OP_READ);
  37.  
    it.remove();
  38.  
    } else if (sKey.isReadable()) {
  39.  
    channel = (SocketChannel) sKey.channel();
  40.  
    while (true) {
  41.  
    echoBuffer.clear();
  42.  
    int r = channel.read(echoBuffer);
  43.  
    if (r <= 0) {
  44.  
    channel.close();
  45.  
    System.out.println("接收完毕,断开连接");
  46.  
    break;
  47.  
    }
  48.  
    System.out.println("##" + r + " " + new String(echoBuffer.array(), 0, echoBuffer.position()));
  49.  
    echoBuffer.flip();
  50.  
    }
  51.  
    it.remove();
  52.  
    } else {
  53.  
    channel.close();
  54.  
    }
  55.  
    }
  56.  
    }
  57.  
     
  58.  
    }
  59.  
     
  60.  
    }

客户端(NIO):

  1.  
    package yyf.java.nio.test;
  2.  
     
  3.  
    import java.net.InetSocketAddress;
  4.  
    import java.nio.ByteBuffer;
  5.  
    import java.nio.channels.SelectionKey;
  6.  
    import java.nio.channels.Selector;
  7.  
    import java.nio.channels.SocketChannel;
  8.  
    import java.util.Iterator;
  9.  
    import java.util.Set;
  10.  
     
  11.  
    public class NioTest {
  12.  
    public static void main(String[] args) throws Exception {
  13.  
    ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
  14.  
    SocketChannel channel = null;
  15.  
    Selector selector = null;
  16.  
    channel = SocketChannel.open();
  17.  
    channel.configureBlocking(false);
  18.  
    // 请求连接
  19.  
    channel.connect(new InetSocketAddress("localhost", 8080));
  20.  
    selector = Selector.open();
  21.  
    channel.register(selector, SelectionKey.OP_CONNECT);
  22.  
    int num = selector.select();
  23.  
    Set selectedKeys = selector.selectedKeys();
  24.  
    Iterator it = selectedKeys.iterator();
  25.  
    while (it.hasNext()) {
  26.  
    SelectionKey key = (SelectionKey) it.next();
  27.  
    it.remove();
  28.  
    if (key.isConnectable()) {
  29.  
    if (channel.isConnectionPending()) {
  30.  
    if (channel.finishConnect()) {
  31.  
    // 只有当连接成功后才能注册OP_READ事件
  32.  
    key.interestOps(SelectionKey.OP_READ);
  33.  
    echoBuffer.put("123456789abcdefghijklmnopq".getBytes());
  34.  
    echoBuffer.flip();
  35.  
    System.out.println("##" + new String(echoBuffer.array()));
  36.  
    channel.write(echoBuffer);
  37.  
    System.out.println("写入完毕");
  38.  
    } else {
  39.  
    key.cancel();
  40.  
    }
  41.  
    }
  42.  
    }
  43.  
    }
  44.  
     
  45.  
    }
  46.  
    }

运行结果:

  1.  
    开始监听……
  2.  
    ##8 12345678
  3.  
    ##8 9abcdefg
  4.  
    ##8 hijklmno
  5.  
    ##2 pq
  6.  
    接收完毕,断开连接

当然,BIO的客户端也可以,开启10个BIO客户端线程

  1.  
    package yyf.java.nio.test;
  2.  
     
  3.  
    import java.io.ByteArrayOutputStream;
  4.  
    import java.io.IOException;
  5.  
    import java.io.InputStream;
  6.  
    import java.io.OutputStream;
  7.  
    import java.net.InetAddress;
  8.  
    import java.net.InetSocketAddress;
  9.  
    import java.net.Socket;
  10.  
    import java.net.UnknownHostException;
  11.  
    import java.util.Random;
  12.  
     
  13.  
    import yyf.java.test.Main;
  14.  
     
  15.  
    public class BioClientTest {
  16.  
    public static void main(String[] args) throws Exception {
  17.  
    BioClient n = new BioClient();
  18.  
    for (int i = 0; i < 10; i++) {
  19.  
    Thread t1 = new Thread(n);
  20.  
    t1.start();
  21.  
    }
  22.  
    }
  23.  
    }
  24.  
     
  25.  
    class BioClient implements Runnable {
  26.  
    @Override
  27.  
    public void run() {
  28.  
     
  29.  
    try {
  30.  
    Socket socket = new Socket("127.0.0.1", 8080);
  31.  
    OutputStream os = socket.getOutputStream();
  32.  
    InputStream is = socket.getInputStream();
  33.  
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
  34.  
    String str = Thread.currentThread().getName() + "...........sadsadasJava";
  35.  
    os.write(str.getBytes());
  36.  
    StringBuffer sb = new StringBuffer();
  37.  
    byte[] b = new byte[1024];
  38.  
    int len;
  39.  
    while ((len = is.read(b)) != -1) {
  40.  
    bos.write(b, 0, len);
  41.  
    }
  42.  
    is.close();
  43.  
    os.close();
  44.  
    socket.close();
  45.  
    System.out.println(Thread.currentThread().getName() + " 写入完毕 " + new String(bos.toByteArray()));
  46.  
    } catch (Exception e) {
  47.  
    e.printStackTrace();
  48.  
    }
  49.  
    }
  50.  
     
  51.  
    }

运行结果:

  1.  
    ##8 Thread-4
  2.  
    ##8 ........
  3.  
    ##8 ...sadsa
  4.  
    ##7 dasJava
  5.  
    接收完毕,断开连接
  6.  
    ##8 Thread-3
  7.  
    ##8 ........
  8.  
    ##8 ...sadsa
  9.  
    ##7 dasJava
  10.  
    接收完毕,断开连接
  11.  
    ##8 Thread-9
  12.  
    ##8 ........
  13.  
    ##8 ...sadsa
  14.  
    ##7 dasJava
  15.  
    接收完毕,断开连接
  16.  
    ##8 Thread-7
  17.  
    ##8 ........
  18.  
    ##8 ...sadsa
  19.  
    ##7 dasJava
  20.  
    接收完毕,断开连接
  21.  
    ##8 Thread-0
  22.  
    ##8 ........
  23.  
    ##8 ...sadsa
  24.  
    ##7 dasJava
  25.  
    接收完毕,断开连接
  26.  
    ##8 Thread-5
  27.  
    ##8 ........
  28.  
    ##8 ...sadsa
  29.  
    ##7 dasJava
  30.  
    接收完毕,断开连接
  31.  
    ##8 Thread-2
  32.  
    ##8 ........
  33.  
    ##8 ...sadsa
  34.  
    ##7 dasJava
  35.  
    接收完毕,断开连接
  36.  
    ##8 Thread-8
  37.  
    ##8 ........
  38.  
    ##8 ...sadsa
  39.  
    ##7 dasJava
  40.  
    接收完毕,断开连接
  41.  
    ##8 Thread-1
  42.  
    ##8 ........
  43.  
    ##8 ...sadsa
  44.  
    ##7 dasJava
  45.  
    接收完毕,断开连接
  46.  
    ##8 Thread-6
  47.  
    ##8 ........
  48.  
    ##8 ...sadsa
  49.  
    ##7 dasJava
  50.  
    接收完毕,断开连接

当然,这只是一个测试,对于一个服务器,是有读取,也有写出的,这是文档给的一个服务端例子

  1.  
    package yyf.java.nio.ibm;
  2.  
     
  3.  
    import java.io.*;
  4.  
    import java.net.*;
  5.  
    import java.nio.*;
  6.  
    import java.nio.channels.*;
  7.  
    import java.util.*;
  8.  
     
  9.  
    public class MultiPortEcho {
  10.  
    private int ports[];
  11.  
    private ByteBuffer echoBuffer = ByteBuffer.allocate(5);
  12.  
     
  13.  
    public MultiPortEcho(int ports[]) throws IOException {
  14.  
    this.ports = ports;
  15.  
    go();
  16.  
    }
  17.  
     
  18.  
    private void go() throws IOException {
  19.  
    Selector selector = Selector.open();
  20.  
    for (int i = 0; i < ports.length; ++i) {
  21.  
    ServerSocketChannel ssc = ServerSocketChannel.open();
  22.  
    ssc.configureBlocking(false);
  23.  
    ServerSocket ss = ssc.socket();
  24.  
    InetSocketAddress address = new InetSocketAddress(ports[i]);
  25.  
    ss.bind(address);
  26.  
    SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
  27.  
    System.out.println("Going to listen on " + ports[i]);
  28.  
    }
  29.  
     
  30.  
    while (true) {
  31.  
    int num = selector.select();
  32.  
    Set selectedKeys = selector.selectedKeys();
  33.  
    Iterator it = selectedKeys.iterator();
  34.  
    while (it.hasNext()) {
  35.  
    SelectionKey key = (SelectionKey) it.next();
  36.  
    if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
  37.  
    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  38.  
    SocketChannel sc = ssc.accept();
  39.  
    sc.configureBlocking(false);
  40.  
    SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
  41.  
    it.remove();
  42.  
    System.out.println("Got connection from " + sc);
  43.  
    } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
  44.  
    SocketChannel sc = (SocketChannel) key.channel();
  45.  
    int bytesEchoed = 0;
  46.  
    while (true) {
  47.  
    echoBuffer.clear();
  48.  
    int r = sc.read(echoBuffer);
  49.  
    if (r <= 0) {
  50.  
    sc.close();
  51.  
    break;
  52.  
    }
  53.  
    echoBuffer.flip();
  54.  
    sc.write(echoBuffer);
  55.  
    bytesEchoed += r;
  56.  
    }
  57.  
    System.out.println("Echoed " + bytesEchoed + " from " + sc);
  58.  
    it.remove();
  59.  
    }
  60.  
     
  61.  
    }
  62.  
    // System.out.println( "going to clear" );
  63.  
    // selectedKeys.clear();
  64.  
    // System.out.println( "cleared" );
  65.  
    }
  66.  
    }
  67.  
     
  68.  
    static public void main(String args[]) throws Exception {
  69.  
    int ports[] = new int[] { 8080 };
  70.  
    for (int i = 0; i < args.length; ++i) {
  71.  
    ports[i] = Integer.parseInt(args[i]);
  72.  
    }
  73.  
    new MultiPortEcho(ports);
  74.  
    }
  75.  
    }

现在,我们就写个客户端去跟服务器通信,把发过去的返回来:

  1.  
    package yyf.java.nio;
  2.  
     
  3.  
    import java.io.IOException;
  4.  
    import java.net.InetSocketAddress;
  5.  
    import java.net.SocketAddress;
  6.  
    import java.nio.ByteBuffer;
  7.  
    import java.nio.channels.SocketChannel;
  8.  
     
  9.  
    import javax.swing.ButtonGroup;
  10.  
     
  11.  
    public class NioClient {
  12.  
    public static void main(String[] args) throws IOException {
  13.  
    SocketChannel socketChannel = SocketChannel.open();
  14.  
    SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
  15.  
    socketChannel.connect(socketAddress);
  16.  
    String str = "你好a";
  17.  
    ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
  18.  
    socketChannel.write(buffer);
  19.  
    socketChannel.socket().shutdownOutput();
  20.  
     
  21.  
    buffer.clear();
  22.  
    byte[] bytes;
  23.  
    int count = 0;
  24.  
    while ((count = socketChannel.read(buffer)) > 0) {
  25.  
    buffer.flip();
  26.  
    bytes = new byte[count];
  27.  
    buffer.get(bytes);
  28.  
    System.out.println(new String(buffer.array()));
  29.  
    buffer.clear();
  30.  
    }
  31.  
    socketChannel.socket().shutdownInput();
  32.  
    socketChannel.socket().close();
  33.  
    socketChannel.close();
  34.  
    }
  35.  
    }

运行结果

server:

  1.  
    Going to listen on 8080
  2.  
    Got connection from java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:63584]
  3.  
    Echoed 7 from java.nio.channels.SocketChannel[closed]

client:

你好a

对于NIO的入门就先到这里。

Java NIO理解与使用的更多相关文章

  1. java NIO理解分析与基本使用

    我前段时间的一篇博客java网络编程--多线程数据收发并行总结了服务端与客户端之间的收发并行实践.原理很简单,就是针对单一客户端,服务端起两个线程分别负责read和write操作,然后线程保持阻塞等待 ...

  2. Java nio 理解

    Java nio 称为Java new IO ,对Java io而言的.他有两个主要的概念:缓存.通道. 在程序中,数据的来源或写入,要么网络.要么硬盘.所有通道分为:文件通道.TCP通道.UDP通道 ...

  3. JAVA IO 以及 NIO 理解

    由于Netty,了解了一些异步IO的知识,JAVA里面NIO就是原来的IO的一个补充,本文主要记录下在JAVA中IO的底层实现原理,以及对Zerocopy技术介绍. IO,其实意味着:数据不停地搬入搬 ...

  4. 深入理解Java NIO

    初识NIO: 在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存 ...

  5. 一文让你彻底理解 Java NIO 核心组件

    背景知识 同步.异步.阻塞.非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下. 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节). 异步:相对于同 ...

  6. 一文理解 Java NIO 核心组件

    同步.异步.阻塞.非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下[1]. 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节). 异步:相对于同步, ...

  7. java nio最白话理解

    JAVA NIO是同步非阻塞io.同步和异步说的是消息的通知机制,阻塞非阻塞说的是线程的状态 .下面说说我的理解,client和服务器建立了socket连接:1.同步阻塞io:client在调用rea ...

  8. Java NIO之理解I/O模型

    前言 自己以前在Java NIO这块儿,一直都是比较薄弱的,以前还因为这点知识而错失了一个机会.所以最近打算好好学习一下这部分内容,我想应该也会有朋友像我一样,一直想闹明白这块儿内容.但是一直无从下手 ...

  9. 理解Java NIO

    基础概念• 缓冲区操作缓冲区及操作是所有I/O的基础,进程执行I/O操作,归结起来就是向操作系统发出请求,让它要么把缓冲区里的数据排干(写),要么把缓冲区填满(读).如下图• 内核空间.用户空间 上图 ...

随机推荐

  1. [省选模拟]Rhyme

    考的时候脑子各种短路,用个SAM瞎搞了半天没搞出来,最后中午火急火燎的打了个SPFA才混了点分. 其实这个可以把每个模式串长度为$K-1$的字符串看作一个状态,这个用字符串Hash实现,然后我们发现这 ...

  2. java读书笔记二

    这是我的一些读书笔记: 我研究了一下面向对象: 面向对象符合人类看待事物的一般规律,对象的方法的实现细节是包装的,只有对象方法的实现者了解细节 我觉得面向过程是由过程.步骤.函数组成,过程是核心,面向 ...

  3. 【转】linux之cp/scp命令+scp命令详解

    linux之cp/scp命令+scp命令详解   名称:cp 使用权限:所有使用者 使用方式: cp [options] source dest cp [options] source... dire ...

  4. Python3基础 str endswith 是否以指定字符串结束

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  5. P3870 [TJOI2009]开关

    思路 重题 代码 #include <iostream> #include <vector> #include <cstdio> #include <cstr ...

  6. POJ 1679 The Unique MST (次小生成树)题解

    题意:构成MST是否唯一 思路: 问最小生成树是否唯一.我们可以先用Prim找到一棵最小生成树,然后保存好MST中任意两个点i到j的这条路径中的最大边的权值Max[i][j],如果我们能找到一条边满足 ...

  7. [Shiro] - shiro之SSM中的使用

    在学习shiro的途中,在github发现了一个开源项目,所需的控件刚好是自己要学习的方向. 虽然还要学习完ssm的shiro与springboot的shiro,以及接下来的种种控件和类库,但学习这个 ...

  8. VS2010下创建WEBSERVICE,第二天 ----你会在C#的类库中添加web service引用吗?

    本文并不是什么高深的文章,只是VS2008应用中的一小部分,但小部分你不一定会,要不你试试: 本人对于分布式开发应用的并不多,这次正好有一个项目要应用web service,我的开发环境是vs2008 ...

  9. 【Coursera】Security Introduction -Ninth Week(2)

    对于公钥系统,我们现在已经有了保证它 Confidentially 的一种方法:SSL.SSL利用了公钥的概念. 那么 who we are talking to? Integrity Certifi ...

  10. NS3 一个小问题

    可能会在执行./waf 命令的时候遇到这个问题,比如我想编译 /home/wasdns/Documents/NS3/ns-3.17/scratch 目录下的一个文件:newnsthree.cpp 编译 ...