5. 添加对第三方软件的支持

并非所有人都是软件开发者,如果只是缺少一个功能,也并非所有人都会从头重写软件。也许你想为一个现有的应用程序添加 keepalive 支持,因为虽然作者可能认为这不重要,但你认为它会很有用。

首先,记住之前关于你需要 keepalive 的情况的描述。现在你需要处理面向连接的 TCP 套接字。

由于 Linux 没有提供通过内核本身启用 keepalive 支持的功能(像类 BSD 操作系统通常做的那样),唯一的办法是在创建套接字后执行 setsockopt (2) 调用。 有两种解决方案

5.1. 修改源代码

记住 keepalive 与程序无关,而是与套接字相关的,所以如果你有多个套接字,你可以分别处理每个套接字的 keepalive。第一阶段是理解程序的功能,然后在代码中搜索每个套接字。这可以使用 grep(1) 完成,如下所示

  # grep 'socket *(' *.c
      

这将或多或少地显示代码中的所有套接字。下一步是只选择正确的套接字:你需要 TCP 套接字,所以查找PF_INET(或AF_INET), SOCK_STREAMIPPROTO_TCP(或更常见的,0)在你的套接字列表的参数中,并移除不匹配的项。

另一种创建套接字的方式是通过 accept(2)。 在这种情况下,跟踪已识别的 TCP 套接字,并检查其中是否有任何是监听套接字:如果是,请记住 accept(2) 返回一个套接字描述符,该描述符必须插入到你的套接字列表中。

一旦你识别出套接字,就可以继续进行更改。最快速和直接的补丁可以通过在套接字创建块之后简单地添加 setsockopt(2 ) 函数来完成。 可选地,如果你不喜欢系统默认值,你可以包含额外的调用来设置 keepalive 参数。在实现函数的错误检查和处理程序时请小心,可以从周围的原始代码中复制样式。记住将optval设置为非零值,并在调用函数之前初始化optlen

如果你有时间或者你认为这真的很酷,尝试为你的程序添加完整的 keepalive 支持,包括命令行开关或配置参数,让用户选择是否使用 keepalive。

5.2. libkeepalive: 库预加载

通常在很多情况下,你无法修改应用程序的源代码,或者当你必须为所有程序启用 keepalive 时,修补和重新编译所有内容是不推荐的。

libkeepalive 项目的诞生是为了帮助为应用程序添加 keepalive 支持,因为 Linux 内核没有提供像 BSD 那样本地执行相同操作的能力。libkeepalive 项目主页是 http://libkeepalive.sourceforge.net/

它由一个共享库组成,该库覆盖了大多数二进制文件中的 socket 系统调用,而无需重新编译或修改它们。 该技术基于 Linux 中包含的 ld.so(8) 加载器的 预加载 功能,它允许你强制加载优先级高于正常的共享库。 程序通常使用位于glibc共享库中的 socket(2) 函数调用; 使用 libkeepalive,你可以包装它并在套接字创建后立即注入 setsockopt (2),从而返回一个已经为主程序设置了 keepalive 的套接字。 由于用于注入系统调用的机制,当 socket 函数被静态编译到二进制文件中时,这不起作用,例如在使用 gcc(1 ) 标志链接的程序中-static.

下载并安装 libkeepalive 后,你将能够为你的程序添加 keepalive 支持,而无需成为root,只需在执行程序之前设置LD_PRELOAD环境变量。 顺便说一句,超级用户也可以使用全局配置强制预加载,然后用户可以通过将KEEPALIVE环境变量设置为off.

来决定关闭它。 环境也用于设置 keepalive 参数的特定值,因此你可以有能力以不同的方式处理每个程序,在启动应用程序之前设置KEEPCNT, KEEPIDLEKEEPINTVL

这是一个 libkeepalive 用法的示例

  $ test
  SO_KEEPALIVE is OFF

  $ LD_PRELOAD=libkeepalive.so \
  > KEEPCNT=20 \
  > KEEPIDLE=180 \
  > KEEPINTVL=60 \
  > test
  SO_KEEPALIVE is ON
  TCP_KEEPCNT   = 20
  TCP_KEEPIDLE  = 180
  TCP_KEEPINTVL = 60
      

你可以使用 strace (1) 来理解发生了什么

  $ strace test
  execve("test", ["test"], [/* 26 vars */]) = 0
  [..]
  open("/lib/libc.so.6", O_RDONLY)        = 3
  [..]
  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
  getsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [0], [4]) = 0
  close(3)                                = 0
  [..]
  _exit(0)                                = ?

  $ LD_PRELOAD=libkeepalive.so \
  > strace test
  execve("test", ["test"], [/* 27 vars */]) = 0
  [..]
  open("/usr/local/lib/libkeepalive.so", O_RDONLY) = 3
  [..]
  open("/lib/libc.so.6", O_RDONLY)        = 3
  [..]
  open("/lib/libdl.so.2", O_RDONLY)       = 3
  [..]
  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
  setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
  setsockopt(3, SOL_TCP, TCP_KEEPCNT, [20], 4) = 0
  setsockopt(3, SOL_TCP, TCP_KEEPIDLE, [180], 4) = 0
  setsockopt(3, SOL_TCP, TCP_KEEPINTVL, [60], 4) = 0
  [..]
  getsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], [4]) = 0
  [..]
  getsockopt(3, SOL_TCP, TCP_KEEPCNT, [20], [4]) = 0
  [..]
  getsockopt(3, SOL_TCP, TCP_KEEPIDLE, [180], [4]) = 0
  [..]
  getsockopt(3, SOL_TCP, TCP_KEEPINTVL, [60], [4]) = 0
  [..]
  close(3)                                = 0
  [..]
  _exit(0)                                = ?
    

有关更多信息,请访问 libkeepalive 项目主页: http://libkeepalive.sourceforge.net/