UNIX网络编程卷1 时间获取程序server TCP 协议相关性

本文为senlie原创。转载请保留此地址:http://blog.****.net/zhengsenlie


最初代码: 
这是一个简单的时间获取server程序。它和时间获取程序client一道工作。


 它是 协议相关,把代码中出现的左边的字符串换为右边的,就变成了IPv6版本号的
 IPv4 --> IPv6
 sockaddr_in --> sockaddr_in6
 AF_INET --> AF_INET6
 sin_family --> sin6_family
 sin_port --> sin6_port
 sin_addr --> sin6_addr



问题1:协议相关
改善:使用 getaddrinfo 和 tcp_listen 来同一时候支持 IPv4 和 IPv6
/**
* TCP 协议无关。调用 getaddrinfo 和 tcp_listen
**/
#include	"unp.h"


int
tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
	int				listenfd, n;
	const int		on = 1;
	struct addrinfo	hints, *res, *ressave;


	//1.调用 getaddrinfo 
	//协议地址话为 AF_UNSPEC ,套接字类型为 SOCK_STREAM
	//由于本函数供server使用,所以还要加一个 AI_PASSIVE 的标志
	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;


	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("tcp_listen error for %s, %s: %s",
				 host, serv, gai_strerror(n));
	ressave = res;


	//2.尝试每一个 addrinfo 结构直到成功或到达链表尾
	do {
		//创建套接字
		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (listenfd < 0)
			continue;		/* error, try next one */


		//设置套接字选项
		Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
		
		//绑定套接字
		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
			break;			//成功
		
		//绑定失败,尝试下一个
		Close(listenfd);	
	} while ( (res = res->ai_next) != NULL);


	//3.检查是否失败
	if (res == NULL)	//socket() 或 bind() 得到的 errorno 
		err_sys("tcp_listen error for %s, %s", host, serv);


	//4.把套接字转换为监听套接字
	Listen(listenfd, LISTENQ);


	if (addrlenp)
		*addrlenp = res->ai_addrlen;	/* return size of protocol address */


	//5.调用 freeaddrinfo 清理由 getaddrinfo 返回的动态存储空间
	freeaddrinfo(ressave);


	//6.返回建立的监听套接字
	return(listenfd);
}
/* end tcp_listen */


int
Tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
	return(tcp_listen(host, serv, addrlenp));
}


/**
* TCP 协议无关,调用 getaddrinfo 和 tcp_listen
**/
#include	"unp.h"
#include	<time.h>


int
main(int argc, char **argv)
{
	int				listenfd, connfd;
	socklen_t		len;
	char			buff[MAXLINE];
	time_t			ticks;
	struct sockaddr_storage	cliaddr;


	//1.利用 Tcp_listen 得到监听套接字
	if (argc == 2)
		listenfd = Tcp_listen(NULL, argv[1], &addrlen);
	else if (argc == 3)
		listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
	else
		err_quit("usage: daytimetcpsrv1 [ <host> ] <service or port>");
	


	//2.server循环。

接受客户连接。发送应答 for ( ; ; ) { len = sizeof(cliaddr); //server堵塞在 accept 调用。等待客户连接 connfd = Accept(listenfd, (SA *)&cliaddr, &len); printf("connection from %s ", Sock_ntop((SA *)&cliaddr, len)); //发送应答 ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s ", ctime(&ticks)); Write(connfd, buff, strlen(buff)); //关闭已连接套接字 Close(connfd); } }



问题2:一次仅仅能处理一个客户
改善:大多数 UDP server是迭代的。一次仅仅处理一个客户。大多数 TCP server是并发的。


并发最简单的技术是调用 fork 函数为每一个客户创建一个子进程。其它技术包含使用线程
取代 fork,或在server启动时预先 fork 一定数量的子进程
这部分的内容在我的其它博文 回射server程序 和 server程序设计范式 里有对应的代码演示样例


问题3:长时间执行
改善:daumon  [todo]