实践中,发现直接在命令行终端运行cli_st时,能够得到预期的结果,但一将它放到crontab中,则只收到:
bye
相关的一段clit_st源代码如下:
if (FD_ISSET(0, &rfds)) { int rc; int len = sizeof(buffer) - bufoff -1; if (len <= 0) { fprintf(stderr, "Can't handle lines that long! "); exit(2); } rc = read(0, buffer+bufoff, len); if (rc <= 0) { fprintf(stderr, "bye "); break; } bufoff += rc; buffer[bufoff] = ' '; while (strchr(buffer, ' ')) { char *ptr = strchr(buffer, ' '); *ptr = ' '; processline(buffer); ptr++; memmove(buffer, ptr, strlen(ptr)+1); bufoff = 0; } } zookeeper_process(zh, events); |
经推断和测试,以及借助strace工具调查,发现问题出在了“if (FD_ISSET(0, &rfds)) {”一处。正常它应当不成立的。
这导致cli_st主动断开了与zookeeper服务端的连接,从zookeeper的服务端日志文件可以看到这个动作:
caught end of stream exception Unable to read additional data from client sessionid 0x2513c8566c1000b, likely client has closed socket |
这段日志显示,cli_st关闭了连接。
问题的原因即是:
cron在fork子进程后,运行命令之前,会关闭stdin,这样导致clit_st中“if (FD_ISSET(0, &rfds)) {”成立,致使连接被关闭。
可以通过简单程序观察cron会关闭或重定向了stdint:
#include <errno.h> #include <unistd.h> #include <stdio.h> int main() { char buf[1024] = {0}; int n = read(0, buf, sizeof(buf)-1); printf("n=%d, errno=%d: %m ", n, errno); return 0; } |
stdin正常,上面代码的进程会挂住,直接读取到stdin或stdin被关闭。但实际结果是:
n=0, errno=0: Success
read的返回值为0,表示stdin已关闭或重定向了。
可借助dup2让stdin复活:
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int main() { char buf[1024] = {0}; int n = read(0, buf, sizeof(buf)-1); printf("n=%d, errno=%d: %m ", n, errno); int fd = open("/tmp/abcde", O_RDONLY); printf("fd=%d ", fd); if (-1 == dup2(fd, 0)) printf("dup2 error: %m "); n = read(0, buf, sizeof(buf)-1); printf("n=%d, errno=%d: %m ", n, errno); if (n>0) { buf[n]=0; printf("%s ", buf); } return 0; } |
上面这段代码运行结果:
n=0, errno=0: Success fd=3 n=7, errno=0: Success dsfsfd |
要解决Zookeeper客户端cli_st在cron中运行的问题,最简单的办法是注释掉下段代码,然后重新编译,以跳过读标准输入:
bufoff=0; // 当注释下段代码时,需要加上它应付编译器 buffer[0]=0; // 当注释下段代码时,需要加上它应付编译器 #if 0 if (FD_ISSET(0, &rfds)) { int rc; int len = sizeof(buffer) - bufoff -1; if (len <= 0) { fprintf(stderr, "Can't handle lines that long! "); exit(2); } rc = read(0, buffer+bufoff, len); if (rc <= 0) { fprintf(stderr, "bye "); break; } bufoff += rc; buffer[bufoff] = ' '; while (strchr(buffer, ' ')) { char *ptr = strchr(buffer, ' '); *ptr = ' '; processline(buffer); ptr++; memmove(buffer, ptr, strlen(ptr)+1); bufoff = 0; } } #endif |
cron的实现大致如下,它会将标准输入、输出和出错重定向到/dev/null,这导致后面对stdin的read返回0。有关cron的实现,可以浏览cron.c(http://blog.chinaunix.net/uid-20682147-id-5521210.html):
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int main() { int n; char buf[1024]; // 重定向stdin到/dev/null int fd = open("/dev/null", O_RDWR, 0); dup2(fd, 0); // 重定向0到fd,0即为stdin pid_t pid = fork(); if (0 == pid) { n = read(0, buf, sizeof(buf)-1); // 返回0 printf("n=%d, errno=%d: %m ", n, errno); exit(0); } return 0; } |
相关文章:
http://blog.chinaunix.net/uid-20682147-id-4977039.html(Cron运行原理)
dup&dup2:
fid = dup(fildes);
等同于
fid = fcntl(fildes, F_DUPFD, 0);
fid和fildes都指向fildes。
fid = dup2(fildes, fildes2); // 重定向fildes2到fildes
等同于:
close(fildes2);
fid = fcntl(fildes, F_DUPFD, fildes2);
fid、fildes和fildes2指向fildes。