总结:
1.、TCP的地址复用(address reuse)问题
2、HOST_NAME_MAX 的处理方法
3、多进程socket编程中 close() 和 shutdown() 的问题
4、gethostname() 的问题
5、getaddrinfo(hostname, "ruptime", &hint, &ailist) 中 "ruptime" 服务的问题("Servname not supported for ai_socktype")
-----initserver.c
/*
* this function is also support connectionless server
* Note:
* 1. address reuse
*/
#include "initserver.h"
#include
#include
int
initserver(int type, const struct sockaddr *addr, socklen_t addrlen, int maxconn)
{
int sockfd;
int err;
int reuse = 1; /* non-zero */
if( (sockfd = socket(addr->sa_family, type, 0)) < 0 )
{
return -1;
}
/* [Note-01] address reuse(APUE-2: 16.6-Socket Options)*/
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
err = errno;
goto errout;
}
if(bind(sockfd, addr, addrlen) < 0)
{
err = errno; /* [Q-01] I don't know why.*/
goto errout;
}
/* Listen to SOCK_STREAM or SOCK_SEQPACKET socket, this function is
* also support connectionless server.
*/
if( (type == SOCK_STREAM) || (type == SOCK_SEQPACKET) )
{
if(listen(sockfd, maxconn) < 0)
{
err = errno;
goto errout;
}
}
return sockfd;
errout:
errno = err;
close(sockfd);
return -1;
}
-----ruptimed.c(server)
/* Server */
/*
Example from: APUE-2e
Note:
1. HOST_NAME_MAX(see APUE-2e)
2. shutdown() and close() in a multi-process program
3. /etc/hosts, /etc/hostname, gethostname()
4. 在多进程程序中注意关闭不用的文件描述符。在子进程中关闭从父进程继承来的不用的文件描述符,在父进程中关闭为子进程创建而自身不适用的文件描述符
*/
#include "initserver.h"
#include "daemonize.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "print_ai.h"
/* [Note-01] */
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif
#define MAXCONN 32
#define BUFLEN 256
static void sig_chld(int signo)
{
while( waitpid(-1, NULL, WNOHANG) > 0 ){}
return;
}
void
serve(int listensock)
{
int acceptsock;
pid_t pid;
FILE *fp;
char buf[BUFLEN];
while(1)
{
if( (acceptsock = accept(listensock, NULL, NULL)) < 0 )
{
syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
exit(errno);
}
if( (pid = fork()) < 0 )
{
syslog(LOG_ERR, "ruptimed: fork error: %s", strerror(errno));
exit(errno);
}
if(pid == 0) /* use multi-process */
{
close(listensock); /* close the listening socket in child process*/
if( (fp = popen("/usr/bin/uptime", "r")) == NULL)
{
sprintf(buf, "popen(\"/usr/bin/uptime\") error: %s\n", strerror(errno));
send(acceptsock, buf, strlen(buf), 0);
}
else
{
while(fgets(buf, BUFLEN, fp) != NULL)
{
send(acceptsock, buf, strlen(buf), 0);
}
sleep(20); /* test blocking */
pclose(fp);
}
// shutdown(acceptsock, SHUT_RDWR);
close(acceptsock); /* close the accept socket in child process */
exit(0);
}
/**
* [Note-02] If either of the child and the parent does not call close() to close the socket, the client will be blocked.
* Calling shutdown() in either child or parent, or calling close() in both child and parent can solve this problem.
*/
close(acceptsock); /* close the accept socket in parent process */
}
}
int main()
{
int sockfd, hostlen, errcode;
char *hostname = NULL;
struct addrinfo hint;
struct addrinfo *ailist = NULL;
struct addrinfo *aip = NULL;
/* Get hostname */
#ifdef _SC_HOST_NAME_MAX
hostlen = sysconf(_SC_HOST_NAME_MAX);
if(hostlen < 0) /* means that there is no definite limit. */
#endif
{
hostlen = HOST_NAME_MAX;
}
if( (hostname = malloc(hostlen)) < 0 )
{
perror("malloc error");
exit(errno);
}
if(gethostname(hostname, hostlen) < 0)
{
perror("gethostname error");
exit(errno);
}
// printf("hostname: %s\n", hostname);
/* get address. First, we should add the service "ruptime" to /etc/services, or there will be a error "Servname not supported for ai_socktype" */
hint.ai_flags = 0; // AI_CANONNAME
hint.ai_family = 0;
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_addr = NULL;
hint.ai_canonname = NULL;
hint.ai_next = NULL;
if( (errcode = getaddrinfo(hostname, "ruptime", &hint, &ailist)) != 0)
{
printf("ruptimed: getaddrinfo error : %s\n", gai_strerror(errcode));
exit(1);
}
/* Prevent zombies */
if( signal(SIGCHLD, sig_chld) == SIG_ERR )
{
perror("signal(SIGCHLD) error");
exit(errno);
}
print_ai(ailist);
daemonize("ruptimed");
for(aip = ailist; aip != NULL; aip = aip->ai_next)
{
if( (sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, MAXCONN)) >= 0 )
{
serve(sockfd);
close(sockfd); /* close the listening socket */
exit(0);
}
}
syslog(LOG_ERR, "%s", strerror(errcode));
return 1;
}
/*
$ ./ruptimed
flags: 0
family: inet
socket type: stream
protocol: IPPROTO_TCP
host:
address: 127.0.1.1
port: 60000
$ ./ruptime localhost
flags: 0
family: inet
socket type: stream
protocol: IPPROTO_TCP
host:
address: 127.0.0.1
port: 60000
Connection refused
cat /etc/hostname
Ubuntu-Server
$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 Ubuntu-Server.localdomain Ubuntu-Server
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
$ ./ruptime Ubuntu-Server
# 能正常连接到服务器端
[Note-03] gethostname() 获取到 hostname 为 "Ubuntu-Server", 然后根据 /etc/hosts 中得到 Ubuntu-Server 的地址为 "127.0.1.1"
*/
-----ruptime.c(client)
/* Client */
#include
#include
#include
#include
#include
#include
#include
#include "print_ai.h"
#define BUFLEN 256
int main(int argc, char *argv[])
{
struct addrinfo hint;
struct addrinfo *ailist = NULL;
struct addrinfo *aip = NULL;
int errcode, sockfd;
char buf[BUFLEN];
int n;
if(argc != 2)
{
printf("usage: ruptime \n");
exit(1);
}
hint.ai_flags = 0;
hint.ai_family = 0;
hint.ai_socktype = SOCK_STREAM;
hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_addr = NULL;
hint.ai_canonname = NULL;
hint.ai_next = NULL;
if( (errcode = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0 )
{
printf("getaddrinfo error: %s\n", gai_strerror(errcode));
exit(1);
}
print_ai(ailist);
for(aip = ailist; aip != NULL; aip = aip->ai_next)
{
if( (sockfd = socket(aip->ai_family, aip->ai_socktype, 0)) < 0 )
{
errcode = errno;
continue;
}
if(connect(sockfd, aip->ai_addr, aip->ai_addrlen) < 0)
{
errcode = errno;
continue;
}
sleep(20);
while( (n = recv(sockfd, buf, BUFLEN, 0)) > 0 )
{
write(STDOUT_FILENO, buf, n);
}
if(n < 0)
{
perror("recv error");
exit(errno);
}
return 0;
}
fprintf(stderr, "%s\n", strerror(errcode));
return 1;
}