Enable TCP keepalive of macOS and Linux in Ruby
はじめに
TCP keepalive を使ったプログラムを Ruby で書こうとしてはまったので,メモです.
TCP keepalive とは
TCP (ソケット) を使って pub/sub 形式のサービスに接続して,サーバからのイベントを待っていたとします. その際,サーバからのデータがしばらく届かない状態が続くと,単にイベントがないだけなのか, セッションが切れてしまっているのかを知ることができません. そこで,何の用事がなくても一定時間毎に空パケットを相手に送って返信を貰うことで, セッションの維持を確認するのが TCP keepalive です.
ちなみに,Websocket にも,ping/pong という仕組みがあり, 同様の事をより上のレイヤで実現しようとしている例です. Slack や IRC もアプリケーションのレイヤで ping/pong の仕組みを持っています.
また,HTTP にも keepalive と呼ばれる仕組みがありますが,上記の類とは別物で目的も違います.
TCP keepalive を設定するには
TCP keepalive がデフォルト有効かどうかは OS 次第のようですが, 有効であっても,タイムアウトが 2時間といった状態なので, 目的に応じてセッション (ソケット) 毎に時間を設定したほうがいいでしょう.
C言語だと,こんな感じです.Linux の TCP_KEEPIDLE
に相当するものは,
macOS (Darwin) では, TCP_KEEPALIVE
となっているので注意が必要です.これではまりました.
#include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> /* macOS (Darwin) uses TCP_KEEPALIVE, while Linux uses TCP_KEEPIDLE */ #if defined(__APPLE__) && defined(__MACH__) # define TCP_KEEPIDLE TCP_KEEPALIVE #endif void setsockopt_test(int socket) { /* After 40-sec silence, send 2 keepalives with 10-sec interval */ int enable = 1; /* set keepalive on/off */ int idle = 40; /* idle time used when SO_KEEPALIVE is enabled */ int intvl = 10; /* interval between keepalives */ int keepcnt = 2; /* number of keepalives before close */ setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)); setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)); }
Ruby だと:
require 'socket' MY_TCP_KEEPIDLE = if RUBY_PLATFORM =~ /darwin/ then 0x10 else :TCP_KEEPIDLE end def set_tcp_keepalive(sock) # After 40-sec silence, send 2 keepalives with 10-sec interval enable = true # set keepalive on/off idle = 40 # idle time used when SO_KEEPALIVE is enabled intvl = 10 # interval between keepalives keepcnt = 2 # number of keepalives before close sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, enable) sock.setsockopt(:IPPROTO_TCP, MY_TCP_KEEPIDLE, idle) sock.setsockopt(:IPPROTO_TCP, :TCP_KEEPINTVL, intvl) sock.setsockopt(:IPPROTO_TCP, :TCP_KEEPCNT, keepcnt) end
どうやら,macOS 用の TCP_KEEPALIVE
をシンボルで受け付けないようです.これではまりました.
行儀が悪いですが,0x10 を直接渡しています.(何かいい方法はないのでしょうか?)
ちなみに 0x10 は, /usr/include/netinet/tcp.h
から調べました:
// macOS 10.12.6 (Sierra) /usr/include/netinet/tcp.h #define TCP_KEEPALIVE 0x10 /* idle time used when SO_KEEPALIVE is enabled */ #define TCP_KEEPINTVL 0x101 /* interval between keepalives */ #define TCP_KEEPCNT 0x102 /* number of keepalives before close */