这样的应用情形:
机器
A 上运行一个程序, 在某种情况下, 调用
rcmd 运行机器B上的一个程序(A和B两台机器需要通过安全认证, 配置这里不说). 这曾经是我碰到的项目中实现scalable 组件的形式, 也算是共享了机器B的资源.
问题: 当机器B死机时, rcmd返回的file descriptor 永远也得不到通知了. 这个file descriptor放在poll调用中不会有事件发生. 调用方也就因此没办法得到通知. 这样一来, 在B机器上执行的任务其进度状态就无法得到更新, 这是典型的bug.
在下面描述的解决方法之前, 我用的是笨办法, 就是总是对poll 设置一个超时, 避免让它无何止地等待, 在poll调用之前, 检查rcmd命令最近一次报告的进度, 这个办法依赖于一个既有的事实, 就是rcmd运行的远程命令会不断地更新其进度状态, 我加了一个数据项来记录最近一次报告进度的时间, 如果3分钟内没有报告新的进度, 就认为该进程不再响应了. 原因可能是机器突然死掉, 也可能运行了是ifdown eth0 这样的命令. 还有一种可能, 进程没死, 只是3分钟内没有反应了. 对这种情况就是一个误报.
下面是我通过一个小程序证实的, 可以通过设置TCP的 idle 时间(TCP_KEEPIDLE), 重发keep idle的间隔时间(TCP_KEEPINTVL)和断开连接之前的重发次数(TCP_KEEPCNT). 当然, 前提是得打开自动保持TCP 连接连通的功能(SO_KEEPALIVE).
默认的SO_KEEPALIVE 是关闭的.
默认的TCP_KEEPIDLE 是2小时, 也就是说, 在你的应用程序打开的TCP连接通道上, 2小时内没有活动时, TCP实现(应是在内核中实现)会自动发包探测另一方的连通性.
默认的TCP_KEEPCNT是9次. 如果通道仍然是连接的, 则TCP_KEEPALIVE 被发送后, 应该很快就接收到应答的包, 表示是连通的, 如果没有收到应答包, 则以 TCP_KEEPINTVL指定的秒数为间隔继续发, 连接9次都收不到应答时, 连接断开, 表现在poll 返回的事件上, 是 POLLIN, POLLERR, POLLHUP事件发生.
默认的TCP_KEEPINTVL值是75秒.
注意TCP_KEEPIDLE 和 TCP_KEEPINTVL 两个值. 都是秒数, 但它们共同(以及TCP_KEEPCNT)决定了用户所能感知到的断开连接前的时间流逝感.
第一次试验时, 我只把TCP_KEEPIDLE设置为了5秒, 而没有设置TCP_KEEPINTVL, 在启动rcmd命令后, 去远程机器上断开网络连接(ifdown eth0).
结果发现自己实际等待的时间远不止5秒. 开始怀疑对rcmd返回的这个fd 能否应用这种办法, 后来反复看man tcp, 加上用wireshark 抓包, 找到上述结果的原因是我去远程机器上断开网络连接时, 这5秒早已过去, 已经发送了第一个TCP_KEEPALIVE 数据包, 而接发送第二个TCP_KEEPALIVE包的时间, 却是在默认的75秒之后.
int main(int argc, char *argv[])
{
char * remote_host = "10.86.6.161";
int fd_stderr = 0;
char cmd[100] = {0};
snprintf(cmd, sizeof(cmd) - 1, "/root/rcmd_test.sh %s", argv[1]);
int fd = rcmd(&remote_host, 514, "root", "pesuser", cmd, 0 );
printf("rcmd = %d\n", fd);
fflush(stdout);
int keep_idle_secs = 30 ;
if(setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, ( char * )&keep_idle_secs, sizeof ( keep_idle_secs )) == -1) {
fprintf(stderr , "Cannot set TCP_KEEPIDLE: %s\n", strerror(errno) );
}
int keep_count = 1 ;
if(setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, ( char * )&keep_count, sizeof ( keep_count )) == -1) {
fprintf(stderr , "Cannot set TCP_KEEPCNT: %s\n", strerror(errno) );
}
int keep_intval_sec = 2 ;
if(setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, ( char * )&keep_intval_sec, sizeof ( keep_intval_sec )) == -1) {
fprintf(stderr , "Cannot set TCP_KEEPINTVL: %s\n", strerror(errno) );
}
int keep_alive = -1;
socklen_t the_len = sizeof(keep_alive);
if(getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, ( char * )&keep_alive, &the_len) == -1) {
fprintf(stderr , "Cannot set SO_KEEPALIVE: %s\n", strerror(errno) );
}
fprintf(stdout , "origin SO_KEEPALIVE: %d\n", keep_alive );
keep_alive = 1;
if(setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, ( char * )&keep_alive, sizeof ( keep_alive )) == -1) {
fprintf(stderr , "Cannot set SO_KEEPALIVE: %s\n", strerror(errno) );
}
char buf[1024] = {0};
ssize_t n_read = read(fd, buf, sizeof(buf) );
printf("stdout: %.*s\n", sizeof(buf), buf);
fflush(stdout);
struct pollfd my_fds[] = {
{ fd, POLLIN | POLLERR }
};
system("date");
int n_ret = poll( my_fds, sizeof(my_fds)/sizeof(my_fds[0]), -1 );
printf("poll ret = %d\n", n_ret);
print_revent( my_fds[0].revents);
n_read = read(my_fds[0].fd, buf, sizeof(buf) );
printf("data: %d[%.*s]\n", n_read, n_read, buf);
system("date");
#if 0
char buf_err[1024] = {0};
ssize_t n_err_read = read(fd_stderr, buf, sizeof(buf) );
printf("stderr: %.*s\n", sizeof(buf_err), buf_err);
fflush(stdout);
#endif
return 0;
}
阅读(1894) | 评论(0) | 转发(0) |