Linux socket-多进程面向连接的服务器客户端程序

总结:

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	<errno.h>
#include	<unistd.h>

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	<sys/socket.h>
#include	<stdlib.h>
#include	<errno.h>
#include	<string.h>
#include	<stdio.h>
#include	<unistd.h>
#include	<netdb.h>
#include	<syslog.h>
#include	<signal.h>
#include	<sys/wait.h>

#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	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<errno.h>
#include	<sys/socket.h>
#include	<netdb.h>
#include	<unistd.h>

#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 <hostname>\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;
}



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值