<audio id="audio" title="09丨答疑篇：学习网络编程前，需要准备哪些东西？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/45/2f/45e5cbc5292b5391f39846f45ed7ed2f.mp3"></audio>

你好，我是盛延敏，这里是网络编程实战第9讲，欢迎回来。

今天是基础篇的最后一讲。在这一讲中，我将会针对基础篇中大家提出的普遍问题进行总结和答疑，让我们整理一下，再接着学习下一个模块的内容。

## 代码和环境

既然我希望通过学习，可以带你进行网络编程实战，那么就得有一个环境，可以运行文章中的例子，并加以活学活用。

我已经将代码上传到GitHub中，你可以访问以下地址来获得最新的代码。

[https://github.com/froghui/yolanda](https://github.com/froghui/yolanda)

代码按照章节组织，比如chap-7就对应第七篇文章。

代码按照CMake组织，CMake是一个跨平台的编译管理系统，使用CMake可以方便地在Linux等类UNIX系统下动态生成Makefile，再由make工具编译、链接生成二进制文件。当然，CMake也可以支持Windows系统下的C/C++编译，这里我们就不展开了。

所有的代码我都已经测试过，可以运行在Linux和MacOS上。

### Ubuntu系统

在Linux下，如果你是Ubuntu系统，需要安装Cmake、make和gcc/g++等编译系统和工具。

```
sudo apt-get install gcc g++ make cmake

```

如果是CentOS或Red Hat，需要执行yum install命令：

```
sudo yum install gcc g++ make cmake

```

使用CMake编译程序需要两步，第一步执行Cmake生成配置文件，主要是Makefile；具体做法是执行如下的cmake命令，之后在build目录下，会发现CMake根据系统环境如编译器、头文件等自动生成了一份Makefile：

```
cd build &amp;&amp; cmake -f ../

```

接下来执行第二步，在build目录运行make，让make驱动gcc编译、链接生成二进制可执行程序，这个过程可能会持续几分钟。最后在build/bin目录下，会生成所有可运行的二进制程序。

```
-rwxr-xr-x 1 vagrant vagrant 13944 Aug 18 13:45 addressused*
-rwxr-xr-x 1 vagrant vagrant 14000 Aug 18 13:45 addressused02*
-rwxr-xr-x 1 vagrant vagrant 13848 Aug 18 13:45 batchwrite*
-rwxr-xr-x 1 vagrant vagrant 13800 Aug 18 13:45 bufferclient*
-rwxr-xr-x 1 vagrant vagrant 14192 Aug 18 13:45 graceclient*
-rwxr-xr-x 1 vagrant vagrant 14096 Aug 18 13:45 graceserver*
-rwxr-xr-x 1 vagrant vagrant  8960 Aug 18 13:45 make_socket*
-rwxr-xr-x 1 vagrant vagrant 13920 Aug 18 13:45 pingclient*
-rwxr-xr-x 1 vagrant vagrant 14176 Aug 18 13:45 pingserver*
-rwxr-xr-x 1 vagrant vagrant 13976 Aug 18 13:45 reliable_client01*
-rwxr-xr-x 1 vagrant vagrant 13832 Aug 18 13:45 reliable_client02*
-rwxr-xr-x 1 vagrant vagrant 14120 Aug 18 13:45 reliable_server01*
-rwxr-xr-x 1 vagrant vagrant 14040 Aug 18 13:45 reliable_server02*
-rwxr-xr-x 1 vagrant vagrant 14136 Aug 18 13:45 samplebuffer01*
-rwxr-xr-x 1 vagrant vagrant 13864 Aug 18 13:45 samplebuffer02*
-rwxr-xr-x 1 vagrant vagrant 14392 Aug 18 13:45 samplebuffer03*
-rwxr-xr-x 1 vagrant vagrant 13848 Aug 18 13:45 streamclient*
-rwxr-xr-x 1 vagrant vagrant 14392 Aug 18 13:45 streamserver*
-rwxr-xr-x 1 vagrant vagrant 13784 Aug 18 13:45 tcpclient*
-rwxr-xr-x 1 vagrant vagrant 13856 Aug 18 13:45 tcpserver*
-rwxr-xr-x 1 vagrant vagrant 13936 Aug 18 13:45 udpclient*
-rwxr-xr-x 1 vagrant vagrant 13320 Aug 18 13:45 udpserver*
-rwxr-xr-x 1 vagrant vagrant 13936 Aug 18 13:45 unixdataclient*
-rwxr-xr-x 1 vagrant vagrant 13896 Aug 18 13:45 unixdataserver*
-rwxr-xr-x 1 vagrant vagrant 13800 Aug 18 13:45 unixstreamclient*
-rwxr-xr-x 1 vagrant vagrant 13992 Aug 18 13:45 unixstreamserver*

```

### MacOS

在MacOS上，Cmake和make都会有Mac特定版本，并且实现的原理也是基本一致的，我们可以像上面Ubuntu系统一样手动安装、配置这些工具。

如果你的系统上没有这两个软件，可以使用brew安装Cmake和make。

```
brew install cmake 
brew install make

```

MacOS上C/C++语言的编译器不同于GNU-GCC，是一个叫做Clang的东西。Clang 背后的技术叫做LLVM（Low Level Virtual Machine）。LLVM 是以 BSD License开发的开源编译器框架系统，基于 C++ 编写而成，不仅可以支持C/C++，还可以支持Swift、Rust等语言。

如果你在MaxOS上查看Clang的版本信息，可以很明显地看到，Clang是基于LLVM开发的，并且对应的版本是多少。在我的机器上显示的LLVM版本是 10.0.0。

```
clang -v
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

```

下面是在MacOS上执行Cmake和make，使用Clang完成编译和链接的过程。

```
cd build &amp;&amp; cmake -f ../
-- The C compiler identification is AppleClang 10.0.0.10001044
-- The CXX compiler identification is AppleClang 10.0.0.10001044
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/shengym/Code/network/yolanda/test

```

```
cd build &amp;&amp; make

```

可执行程序仍然保存在 build/bin目录下面。

### CLion

对于有IDE情结的同学来说，推荐使用JetBrains公司出品的CLion进行编译和调试。

你可以在这里下载 [https://www.jetbrains.com/clion/](https://www.jetbrains.com/clion/)， 获得30天的免费使用。

CLion自带了CMake等工具，可以开箱即用。它最强大的地方，是可以直接设置断点，方便进行调试。我们进入主菜单，选择Run，再选择Debug，就可以启动程序，进行调试。

<img src="https://static001.geekbang.org/resource/image/28/25/28c40e2422c01340a8ab8b368355f825.png" alt=""><br>
有些情况下，启动程序时需要输入一些参数，这个时候需要使用“Edit Configurations”为可执行程序配置参数。下面是一个例子：

<img src="https://static001.geekbang.org/resource/image/61/5c/6191b86211dd13df6613bdb3481d8d5c.png" alt="">

## Windows

使用 Windows 系统 + CLion 的同学，可以在 Win10 应用商店中下载一个 Ubuntu 版本的 Windows 子系统，即 WSL，然后在 CLion 中配置工程的环境为 WSL 即可编译运行。

## 学习路径

也许你刚刚入门，一直对网络编程的学习路径有很大的困惑，我在这里统一回复一下。

我觉得学习网络编程技术，必须要过一道语言关，这个语言就是C语言。专栏本身也是基于C语言的。虽然现代工业化语言如Java、Golang等已经足够强大，但是你要知道，在这些语言的背后，无一例外的总是有C语言的影子。C语言是可以和系统直接交互的语言，无论是系统调用，还是内核实现，都和C语言有非常直接的联系，比如Java本身就是用C++实现的，Golang虽然现在可以自举，也就是可以使用Golang实现Golang本身，但是它的第一版也是用C/C++实现的。

我不建议一开始就学习C++语言，在我看来, C++语言在C语言原来的基础上做了很多语言层面的增强，而这些增强的语言特性，例如模板、继承、虚函数、boost语言库等，对于刚开始接触底层的人显得有些艰深。

学习一门编程语言，显然不是学习这门语言的控制流或者变量类型，而是抓住这门语言的精髓。我认为C语言的精髓包括数组和指针、结构体和函数。

C语言的地址、数组、指针可以帮助我们详细地理解计算机的体系结构，一段数据怎样在内存中摆放，怎么去访问等，你可以在学习它们的过程中得到锤炼，了解这些基础的编程理念。

有些同学一上来就啃“TCP/IP协议”，我觉得对于实战来说，显得过于着急。我们可以把“TCP/IP协议”当作编程过程中答疑解惑的好帮手，有问题之后再从中寻找答案，而不是急急忙忙就来啃这类书籍。说实话，这类书籍理论性偏强，有时候大段读下来也少有收获。

最好的办法，还是自己跟随一些入门书籍，或者我的这篇实战，尝试动手去写、去调试代码，这中间你会不断获得一些反馈，然后再和大家一起探讨，不断加深了解。

当你学到了一定阶段，就可以给自己开一些小的任务，比如写一个聊天室程序，或者写一个HTTP服务器端程序，带着任务去学习，获得成就感的同时，对网络编程的理解也随之更上一层楼了。

## 书籍推荐

我希望你可以通过这个专栏更好地了解网络编程，但是深入的学习还需要你自行去找更多的资料。我在这里给你推荐一些书，这些书是各个领域的经典。

C语言入门方面，我推荐 《C程序设计语言》，这里是豆瓣链接，你可以看下大家的评价以及他们的学习方式：  [https://book.douban.com/subject/1139336/](https://book.douban.com/subject/1139336/)

UNIX网络编程方面，强烈推荐Stevens大神的两卷本《UNIX网络编程》，其中第一卷是讲套接字的，第二卷是讲IPC进程间通信的。这套书也随书配备了源代码，你如果有兴趣的话，可以对代码进行改写和调试。

豆瓣链接在此： [https://book.douban.com/subject/1500149/](https://book.douban.com/subject/1500149/)

这套书的卷一基本上面面俱到地讲述了UNIX网络编程的方方面面，但有时候稍显啰嗦，特别是高性能高并发这块，已经跟不上时代，但你可以把注意力放在卷一的前半部分。

这套书翻译了好几版，就我的体验来说，比较推荐杨继张翻译的版本。

TCP/IP协议方面，当然是推荐Stevens的大作《TCP/IP详解》, 这套书总共有三卷，第一卷讲协议，第二卷讲实现，第三卷讲TCP事务。我在这里推荐第一卷，第二卷的实现是基于BSD的代码讲解的，就不推荐了。我想如果你想看源码的话，还是推荐看Linux的，毕竟我们用的比较多。第三卷涉及的内容比较少见，也不推荐了。

这套书各个出版社翻译了好多版本，你可以去豆瓣自行查看哪个版本评分比较高。

《TCP/IP详解 卷1：协议》豆瓣链接如下：

[https://book.douban.com/subject/1088054/](https://book.douban.com/subject/1088054/)

最后除了书籍外，还有一个非常好的了解TCP的方法，那就是查看RFC文档，对于有一定英文能力的同学来说，可以说是一个捷径。RFC最大的好处可以帮我们了解TCP发展的背景和脉络。

## 疑难解答

前面的内容算是我对你学习网络编程提供的一些小建议或者小帮助。接下来，我们正式进入到文章本身的内容。

在第5讲思考题部分中，我出了这么一道题目“一段数据流从应用程序发送端，一直到应用程序接收端，总共经过了多少次拷贝？”大家的回答五花八门。

我的本意可以用一张图来表示，还记得TCP/IP层次模型么？我想通过这么一个问题，来展示TCP/IP分层的思想。

<img src="https://static001.geekbang.org/resource/image/50/8f/50c05c8509a1d8436273adbf8701bb8f.png" alt=""><br>
让我们先看发送端，当应用程序将数据发送到发送缓冲区时，调用的是send或write方法，如果缓存中没有空间，系统调用就会失败或者阻塞。我们说，这个动作事实上是一次“显式拷贝”。而在这之后，数据将会按照TCP/IP的分层再次进行拷贝，这层的拷贝对我们来说就不是显式的了。

接下来轮到TCP协议栈工作，创建Packet报文，并把报文发送到传输队列中（qdisc），传输队列是一个典型的 FIFO 队列，队列的最大值可以通过 ifconfig 命令输出的 txqueuelen 来查看。通常情况下，这个值有几千报文大小。

TX ring 在网络驱动和网卡之间，也是一个传输请求的队列。

网卡作为物理设备工作在物理层，主要工作是把要发送的报文保存到内部的缓存中，并发送出去。

接下来再看接收端，报文首先到达网卡，由网卡保存在自己的接收缓存中，接下来报文被发送至网络驱动和网卡之间的 RX ring，网络驱动从 RX ring 获取报文 ，然后把报文发送到上层。

这里值得注意的是，网络驱动和上层之间没有缓存，因为网络驱动使用 Napi 进行数据传输。因此，可以认为上层直接从 RX ring 中读取报文。

最后，报文的数据保存在套接字接收缓存中，应用程序从套接字接收缓存中读取数据。

这就是数据流从应用程序发送端，一直到应用程序接收端的整个过程，你看懂了吗？

上面的任何一个环节稍有积压，都会对程序性能产生影响。但好消息是，内核和网络设备供应商已经帮我们把一切都打点好了，我们看到和用到的，其实只是冰山上的一角而已。

这就是基础篇的总结与答疑部分，我先对之前基础篇的内容补充了一些资料，尽可能地为你学习网络编程提供方便，然后针对大家有明显疑惑的问题进行了解答，希望对你有所帮助。
