🎬 第三章 RTP与RTCP流媒体协议
📄 一句话概括

Ubuntu 上只有四行命令,麻烦全在编完之后:三个同名的 .so 各自负责什么。 弄清「链接期用哪个、运行期用哪个」,三类报错就都能自己对上。

📝 本节和前面几节的关系

3.4.1 在 Windows 上编,这一节在 Ubuntu 上编同一个库。 两边的 CMake 是同一套,差别只在产物格式与找库方式。

一、四行命令装完

📋 Ubuntu 18.04 上的完整流程: ① 装工具:sudo apt install -y cmake g++ ② 解压进目录:tar xf jrtplib-3.11.2.tar.gz && cd jrtplib-3.11.2 ③ 编:mkdir build && cd build && cmake .. && make -j4 ④ 装:sudo make install && sudo ldconfig

注意第 ④ 步后面那个 ldconfig 不是可选的。 make install 只负责把文件拷进 /usr/local/lib,而动态链接器的缓存不会自动刷新。 少了这一句,就会出现「编译明明过了,一运行就说找不到库」。

另外 jthread 也要按同样顺序先装一遍,和 Windows 侧的依赖关系完全一样。

二、三个同名的 .so 各管什么

装完之后 /usr/local/lib 里会多出三个名字很像的文件: ① libjrtplib.so.3.11.2:真身,带完整版本号。 ② libjrtplib.so.3:软链,SONAME,运行时靠它找。 ③ libjrtplib.so:软链,编译链接时靠它找。

为什么要分两级? 因为「编译期」和「运行期」要的东西不一样。 编译期你在命令行写 -ljrtplib,链接器去找 libjrtplib.so——它只需要知道「有这么一个库」。 运行期程序里记的是 SONAME,也就是 libjrtplib.so.3——只要主版本号还是 3,库升级到 3.11.3 也不用重新编译。

这个设计的价值在运维上: 小版本升级只换真身文件、不动软链,所有已编好的程序立刻生效。 主版本号变了才需要重编,因为那意味着接口不兼容。

三、链接参数与三类报错

编译命令是 g++ main.cpp -ljrtplib -ljthread -o demo。 注意 -l 后面要砍掉 lib 前缀和 .so 后缀,写中间的裸名。

三类报错,对应三个不同的阶段,很容易混: ① 编译期找不到头文件:说明 include 路径不对,加 -I/usr/local/include。 ② 链接期 undefined reference:十有八九是只链了 jrtplib、忘了 jthread,把 -ljthread 补上。 ③ 运行期找不到 .so:说明动态链接器搜不到,sudo ldconfig 或设 LD_LIBRARY_PATH。

怎么快速区分是②还是③? 看报错发生在 g++ 还是 ./demo。 前者是链接问题,后者是运行问题,两者完全不是一回事。 很多新手把「运行时找不到库」当成「装错了」,然后反复重装,白折腾。

四、Windows 与 Ubuntu 的三处差别

编同一个库,两边真正不同的只有三处: ① 构建工具的输出不同:Windows 是 CMake 生成 .sln 交给 VS;Ubuntu 是 CMake 生成 Makefile 交给 make。CMake 本身是同一个。 ② 产物后缀不同:Windows 是 .lib + .dll,Ubuntu 是 .a + .so。 .lib 对应 .a(导入库),.dll 对应 .so(动态库)。 ③ 找库的方式不同:Windows 按 PATH 与「exe 同目录」;Ubuntu 按 ld.so.cache 与 LD_LIBRARY_PATH。

所以有一个经验可以直接搬: Windows 上「把 dll 拷到 exe 旁边」这一招,在 Ubuntu 上要换成「把 .so 拷到某个目录再设 LD_LIBRARY_PATH」。 思路一样,动的地方不同。

还有一条隐蔽的差别:Ubuntu 上默认开 -fPIC 与 -O2,Windows 的 Debug 默认是 -Od 且带调试信息。 所以同一个工程在两边「表现不一样」时,先看编译选项,别急着怀疑代码。

五、动手 Demo:Ubuntu 侧的编译步骤与三个软链

这是【📋 实操路径】——需要一台 Ubuntu 18.04。 沙箱里没有 Linux,所以这一段是可直接照抄的命令清单。

# ① 装工具链
sudo apt install -y cmake g++

# ② 编 jthread(必须先编)
tar xf jthread-1.3.3.tar.gz && cd jthread-1.3.3
mkdir build && cd build && cmake .. && make -j4
sudo make install

# ③ 编 jrtplib
cd ~ && tar xf jrtplib-3.11.2.tar.gz && cd jrtplib-3.11.2
mkdir build && cd build && cmake .. && make -j4
sudo make install

# ④ 刷新动态库缓存——这一步不能省
sudo ldconfig

# ⑤ 看一眼三个同名文件
ls -l /usr/local/lib/libjrtplib*

# ⑥ 编自己的程序,两个库都要链
g++ main.cpp -ljrtplib -ljthread -o demo
./demo

实跑输出:

--------------------------------------------------------------------
Ubuntu 18 编译 JRTPLIB——库文件名与链接参数
--------------------------------------------------------------------
  📋 **Ubuntu 侧的步骤:**
  **① 装工具**:`sudo apt install cmake g++`
  **② 进目录**:`mkdir build && cd build`
  **③ 编**:`cmake .. && make -j4`
  **④ 装**:`sudo make install`

**① 编完之后系统里多了什么**

  文件                     在哪                   作用
  ---------------------------------------------------------------------------
  libjrtplib.so.3.11.2     /usr/local/lib         真身,带版本号
  libjrtplib.so.3          /usr/local/lib         SONAME 软链,运行时找它
  libjrtplib.so            /usr/local/lib         编译期软链,链接时找它
  rtpsession.h 等          /usr/local/include     头文件

  **三个同名的 .so 是有讲究的。**
  **链接时用不带版本的那个**(`-ljrtplib` 找 `libjrtplib.so`)。
  **运行时用 SONAME 那个**(`libjrtplib.so.3`)。
  **这样程序编好之后,库升级成 3.11.3 也不用重新编译**——只要 SONAME 还是 3。

**② 链接参数与找不到库的三个原因**

  **编译命令:`g++ main.cpp -ljrtplib -ljthread -o demo`。**
  **注意 `-l` 要去掉 `lib` 前缀和 `.so` 后缀。**

  现象                           真正的原因             怎么办
  --------------------------------------------------------------------------------
  编译期找不到头文件             没装或装在别处         加 `-I` 指到头文件目录
  链接期 undefined reference     少了 `-ljthread`       两个库都要链
  运行时找不到 .so               不在 ld 的搜索路径     `ldconfig` 一下

  **「编译过了运行报找不到库」是最常见的一条。**
  **原因是 `make install` 把库放进了 `/usr/local/lib`,而有些发行版的 `ld.so.conf` 默认不含它。**
  **`sudo ldconfig` 一句就解决。**

**③ Ubuntu 与 Windows 的差别,其实只有三处**

  **① 构建工具**:Windows 用 CMake 生成 .sln 再用 VS 编;
  **Ubuntu 用 CMake 生成 Makefile 再用 make 编。CMake 是同一个。**
  **② 产物**:Windows 是 `.lib + .dll`,Ubuntu 是 `.a + .so`。
  **`.lib` 对应 `.a`(导入库),`.dll` 对应 `.so`(动态库)。**
  **③ 找库的方式**:Windows 靠 PATH 与同目录,**Ubuntu 靠 `ld.so.cache` 与 `LD_LIBRARY_PATH`。**
  **所以 Windows 上「把 dll 拷到 exe 旁边」这招,在 Ubuntu 上要换成「设环境变量」。**

ls -l 会看到三个条目,两个是箭头指向真身的软链——这就是编译期与运行期分开的两个入口。 ldconfig 放在 make install 之后,它扫的是 /usr/local/lib 这类标准目录,把 SONAME 登记进缓存。 最后一行 ./demo 才是验收集:能跑起来说明头文件、链接、运行库三关全过。

30 秒实验

  1. 故意删掉 sudo ldconfig 再跑程序,看报错长什么样——error while loading shared libraries: libjrtplib.so.3,记下这个长相,以后一眼能认。
  2. 把 libjrtplib.so.3 这个软链改名,再跑一次——证明运行期认的是 SONAME,不是不带版本的那个。
  3. 只写 -ljrtplib 不写 -ljthread,看链接报错提到的符号名——里面会出现 jthread 的类名,这就是依赖关系的直接证据。

常见误区

  1. 「make install 完就能用」——还要 ldconfig,否则运行期找不到库。
  2. 「三个 .so 是重复文件,删两个省空间」——删了就没法编、或者没法运行,两个软链各管一头。
  3. 「运行时报找不到库 = 没装好」——十有八九只是缓存没刷新,一句 ldconfig 的事,不用重装。
自测三问(点开对答案) 1. 为什么 `make install` 之后还要 `ldconfig`? —— **把新的 SONAME 登记进动态链接器缓存**,否则运行期找不到库。 2. 链接期和运行期分别用哪个软链? —— **链接期用 `libjrtplib.so`,运行期用 `libjrtplib.so.3`(SONAME)。** 3. `.lib` 与 `.a`、`.dll` 与 `.so` 是怎么对应的? —— **`.lib` 对 `.a`(导入库),`.dll` 对 `.so`(动态库)。**功能位置一致。

相关笔记