本文将描述在使用inetd守护进程时,如何通过syslog函数打印消息到日志文件。
为什么需要这样做呢?根据《UNIX网络编程 卷1:套接字联网API》一书第13章的描述:由于守护进程没有控制终端,它们不能把消fprintf到stderr上。
从守护进程中登记消息的常用技巧就是调用syslog函数。
而sysolog函数需要syslogd服务的支持。因此在编译busybox时需要使能syslogd。
Linux系统启动后,需要打开syslogd服务。先来看看syslogd的命令选项:
[root@BGM /]#syslogd -h syslogd: invalid option -- 'h' BusyBox v1.16.0 (2013-04-15 16:07:27 CST) multi-call binary. Usage: syslogd [OPTIONS] System logging utility. Note that this version of syslogd ignores /etc/syslog.conf. Options: -n Run in foreground -O FILE Log to given file (default:/var/log/messages) -l n Set local log level -S Smaller logging output -s SIZE Max size (KB) before rotate (default:200KB, 0=off) -b N N rotated logs to keep (default:1, max=99, 0=purge) -R HOST[:PORT] Log to IP or hostname on PORT (default PORT=514/UDP) -L Log locally and via network (default is network only if -R) -D Drop duplicates -C[size(KiB)] Log to shared mem buffer (read it using logread)
这里我们注意到该syslogd不支持/etc/syslog.conf文件。
我们需要使用-L选项,让消息输出到本地(locally),其次其默认的本地输出文件为/var/log/message,
如果要修改输出文件,则使用-O选项。
因此,我这里使用的syslogd命令如下:
/sbin/syslogd -L
这里建议将该命令添加到系统的启动脚本中。
为了测试函数需要编写一个简单的TCP服务器程序和相应的客户程序,参照《UNIX网络编程 卷1:套接字联网API》的13.6小结编写测试用例,
程序如下:
服务器程序:
int main(void) { socklen_t len; struct sockaddr *cliaddr; char buf[MAX_TCPSERV_BUF]; int ret, tmp; openlog("bgmtcpserv", LOG_PID, 0); dc = malloc(sizeof(struct data_content)); if(!dc){ perror("Unable to malloc data_content "); exit -1; } cliaddr = malloc(sizeof(struct sockaddr_storage)); if(!cliaddr){ perror("Unable to malloc sockaddr_storage "); exit -1; } len = sizeof(struct sockaddr_storage); if (getpeername(0, cliaddr, &len) == -1){ perror("Getpeername error "); exit -1; } syslog(LOG_USER|LOG_ALERT, "Connecting from %s\n", Sock_ntop(cliaddr,len)); close(0); closelog(); exit(0); } /* include sock_ntop */ char * sock_ntop(const struct sockaddr *sa, socklen_t salen) { char portstr[8]; static char str[128]; /* Unix domain is largest */ switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *) sa; if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) return(NULL); if (ntohs(sin->sin_port) != 0) { snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port)); strcat(str, portstr); } return(str); } /* end sock_ntop */ #ifdef IPV6 case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; str[0] = '['; if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL) return(NULL); if (ntohs(sin6->sin6_port) != 0) { snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port)); strcat(str, portstr); return(str); } return (str + 1); } #endif /* #ifdef AF_UNIX case AF_UNIX: { struct sockaddr_un *unp = (struct sockaddr_un *) sa; OK to have no pathname bound to the socket: happens on every connect() unless client calls bind() first. if (unp->sun_path[0] == 0) strcpy(str, "(no pathname bound)"); else snprintf(str, sizeof(str), "%s", unp->sun_path); return(str); } #endif*/ // #ifdef HAVE_SOCKADDR_DL_STRUCT // case AF_LINK: { // struct sockaddr_dl *sdl = (struct sockaddr_dl *) sa; // if (sdl->sdl_nlen > 0) // snprintf(str, sizeof(str), "%*s (index %d)", // sdl->sdl_nlen, &sdl->sdl_data[0], sdl->sdl_index); // else // snprintf(str, sizeof(str), "AF_LINK, index=%d", sdl->sdl_index); // return(str); // } // #endif default: snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d", sa->sa_family, salen); return(str); } return (NULL); } char * Sock_ntop(const struct sockaddr *sa, socklen_t salen) { char *ptr; if ( (ptr = sock_ntop(sa, salen)) == NULL) perror("sock_ntop error"); /* inet_ntop() sets errno */ return(ptr); }
#define SERV_PORT 30001 #define SERVIPADDR "192.168.0.200" ssize_t /* Read "n" bytes from a descriptor. */ readn(int fd, void *vptr, size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; /* and call read() again */ else return(-1); } else if (nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */ } int main(void) { int sockfd, ret; struct sockaddr_in servaddr; int n; char buf[512] ; sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0){ perror("Socket error"); return -1; } memset(&servaddr, 0, sizeof(struct sockaddr_in)); //bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); ret = inet_pton(AF_INET, SERVIPADDR, &servaddr.sin_addr); if(ret < 1){ perror("Inet_pton error"); return -1; } ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); if(ret < 0){ perror("Connect error"); return -1; } close(sockfd); }
服务器程序编写完以后,为了让inetd能够调用我们的服务器程序,需要修改配置文件。
首先,修改/etc/services,增加如下:
mytcpser 30001/tcp # Used by BGM
其次,修改/etc/inetd.conf,增加如下:
mytcpser stream tcp nowait root /home/bgm/bgmtcpser bgmtcpser
这里的mytcpser需要和services文件中的第一个字段相同,其次/home/bgm/bgmtcpser为服务器程序所在的路径。
修改完配置文件后,将服务器程序bgmtcpser复制到/home/bgm目录下。
使用netstat 来查看是否inetd已经创建端口号为30001的监听套接字:
[root@BGM /]#netstat -an | grep 30001 tcp 0 0 0.0.0.0:30001 0.0.0.0:* LISTEN
Jul 23 13:13:42 BGM user.alert bgmtcpserv[2587]: Connecting from 192.168.0.200:40571