Unix/Linux编程:循环的、面向连接(TCP)服务器------DAYTIME

分配被动的TCP服务器

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <stdio.h>
#include <time.h>
#include <string.h>

#include <errno.h>


int errexit(const char *format, ...);
unsigned short	portbase = 0;	/* port base, for non-root servers	*/
/*------------------------------------------------------------------------
 * passivesock - allocate & bind a server socket using TCP or UDP
 *------------------------------------------------------------------------
 * Arguments:
 *      service   - service associated with the desired port
 *      transport - transport protocol to use ("tcp" or "udp")
 *      qlen      - maximum server request queue length
 */
int passivesock(const char *service, const char *transport, int qlen)
{
    struct servent	*pse;	/* pointer to service information entry	*/
    struct protoent *ppe;	/* pointer to protocol information entry*/
    struct sockaddr_in sin;	/* an Internet endpoint address		*/
    int	s, type;	/* socket descriptor and socket type	*/

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;

    /* Map service name to port number */
    if ( pse = getservbyname(service, transport) )
        sin.sin_port = htons(ntohs((unsigned short)pse->s_port)
                             + portbase);
    else if ((sin.sin_port=htons((unsigned short)atoi(service))) == 0)
        errexit("can't get \"%s\" service entry\n", service);

    /* Map protocol name to protocol number */
    if ( (ppe = getprotobyname(transport)) == 0)
        errexit("can't get \"%s\" protocol entry\n", transport);

    /* Use protocol to choose a socket type */
    if (strcmp(transport, "udp") == 0)
        type = SOCK_DGRAM;
    else
        type = SOCK_STREAM;

    /* Allocate a socket */
    s = socket(PF_INET, type, ppe->p_proto);
    if (s < 0)
        errexit("can't create socket: %s\n", strerror(errno));

    /* Bind the socket */
    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
        errexit("can't bind to %s port: %s\n", service,
                strerror(errno));
    if (type == SOCK_STREAM && listen(s, qlen) < 0)
        errexit("can't listen on %s port: %s\n", service,
                strerror(errno));
    return s;
}

/*------------------------------------------------------------------------
 * errexit - print an error message and exit
 *------------------------------------------------------------------------
 */
int errexit(const char *format, ...)
{
    va_list	args;

    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(1);
}


/*------------------------------------------------------------------------
 * passiveTCP - create a passive socket for use in a TCP server
 *------------------------------------------------------------------------
 * Arguments:
 *      service - service associated with the desired port
 *      qlen    - maximum server request queue length
 */
int passiveTCP(const char *service, int qlen)
{
    return passivesock(service, "tcp", qlen);
}

passiveTCP分配一个流套接字,并将其绑定到提供服务的熟知端口上

用于DAYTIME服务的服务器

DAYTIME服务器允许某台机器上的用户从另一台机器上获得当前日期和时间。由于DAYTIME服务是为人所用的,它规定服务器发送响应时,必须将日期格式化为可读的ASCII文本字符串。因为,客户在收到响应时,就可以正确的为用户显示响应结果。

由于获取和格式化日期只需要很少的处理,并且用户对此服务的需求也很少,因此,DAYTIME服务器就不必优化速率。如果在服务器忙于处理某个请求时,其他客户尝试建立连接请求,协议软件就会将这些请求排队。因此,循环实现就足够了

进程结构

如下图所示,循环、面向连接的服务器使用一个单线程。该线程永远循环,使用一个套接字处理请求,并且用另一个临时的套接字处理与客户的通信
在这里插入图片描述
使用面向连接传输的服务器在这些连接上不断循环:它在熟知端口上等待来自某客户的下一个连接、接收连接、处理连接、关闭连接,然后再次等待连接。DAYTIME服务的实现特别简单,这是因为服务器不必接收来自客户的显示请求----它根据传入连接的出现来触发响应。由于客户不发送显式请求,服务器就不必从连接上读取数据

DAYTIME服务实现

/* TCPdaytimed.c - main */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <unistd.h>
#include <stdio.h>
#include <string.h>


int		errexit(const char *format, ...);
void		TCPdaytimed(int fd);
int		passiveTCP(const char *service, int qlen);

#define QLEN	32


/*------------------------------------------------------------------------
 * main - Iterative TCP server for DAYTIME service
 *------------------------------------------------------------------------
 */
int main(int argc, char *argv[])
{
	struct	sockaddr_in fsin;	/* the from address of a client	*/
	char	*service = "daytime";	/* service name or port number	*/
	int	msock, ssock;		/* master & slave sockets	*/
	unsigned int	alen;		/* from-address length		*/

	switch (argc) {
	case	1:
		break;
	case	2:
		service = argv[1];
		break;
	default:
		errexit("usage: TCPdaytimed [port]\n");
	}

    // 
	msock = passiveTCP(service, QLEN);

	while (1) {
		alen = sizeof(fsin);
		ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
		if (ssock < 0)
			errexit("accept failed: %s\n", strerror(errno));
		TCPdaytimed(ssock);
		(void) close(ssock);
	}
}

/*------------------------------------------------------------------------
 * TCPdaytimed - do TCP DAYTIME protocol
 *------------------------------------------------------------------------
 */
void TCPdaytimed(int fd)
{
	char	*pts;			/* pointer to time string	*/
	time_t	now;			/* current time			*/

	(void) time(&now);
	pts = ctime(&now);
	(void) write(fd, pts, strlen(pts));
}

这种循环的、面向连接的服务器必须永远运行。在创建了熟知端口上的监听套接字之后,服务器进入无限循环,在循环中等待和处理连接。

passiveTCP的参数QLEN指明了主套接字的请求连接队列长度,从而允许系统在忙于回答某个客户的连接请求时,将来自其他QLEN个客户的连接请求排队

创建主套接字后,服务器的主程序将进入无限循环。在每次循环中,服务器调用accept从主套接字获得下一个连接。为了防止服务器在等待来自客户的连接时耗费资源,accept调用将一直阻塞,直到一个连接到达。当连接请求到达时,TCP协议软件为建立连接而忙于三次握手。一旦握手完成,并且系统已经为传入连接分配了一个新套接字,accept调用将返回新套接字的描述符,并允许服务器程序继续指针。如果没有连接请求到达,服务器进行将在accept调用中一直保持阻塞状态

每次在新的连接到达时,服务器将调用过程TCPdaytimed对它进行处理。TCPdaytimed中的代码以系统函数time和ctime的调用为核心。time返回一个32为的整数以表示当前时间,ctime将这个整数格式化为人可以理解的字符。一旦服务器获得ASCII字符串表示的时间和日期,就调用write将字符串通过TCP连接发送给客户。

调用TCPdaytimed一旦返回,主程序就继续循环,再次调用accept。而accept调用在另一个请求到达前将使得服务器阻塞

关闭连接

TCPdaytimed写完响应后,调用就返回。一旦调用返回,主程序就明确的关闭该连接到达的套接字

调用close需要从容关闭。具体来说就是,TCP必须保证所有数据都可靠的交付给客户,并且在连接终止前都已经被确认。因此,当调用close时,程序员不必担心正在被交付的数据

当然,TCP从容关闭的定义意味着close调用可能不立即返回------在服务器上的TCP从客户的TCP收到答复前,调用将阻塞。一旦客户确认它收到了所有数据和终止连接的请求,close调用即将返回。

连接终止和服务器的脆弱性

应用协议决定了服务器如何管理TCP连接。特别是,应用协议通常指定了终止策略的请求。比如,让服务器关闭连接DAYTIME协议来说比较合适,因为服务器知道它何时结束数据发送。

对那些具有更复杂的客户-服务器交互的应用,不能在处理一个请求后立即关闭连接,因为它们必须要等待,以便了解客户是否会发送其他请求报文。比如,对于一个ECHO服务器,由于客户决定了要服务器会送的数据量,因此客户控制了服务器的处理,由于服务器必须要处理任意多的数据,它不能在一次读和写之后就关闭连接。因此,客户必须发送信号表示已经完成了,以便让服务器直到应在何时终止连接。

运行客户控制连接的持续时间可能是危险的,因为这就是运行客户控制资源的使用。特别是,误操作的客户可能会导致服务器消耗像套接字和TCP连接之类的资源。我们举例的服务器似乎永远不会用光资源,因为它明确的关闭了连接。但我们的简单终止策略在可能会误动作的客户面前是催收的。要连接企图的原因,回想TCP定义了连接关闭后连接超时的时间是最大报文段生命期的两倍(2*MSL)。在超时期间,TCP将保存连接的记录,以便它能够正确额拒绝任何可能已经被延迟的旧分组。因此,如果客户快速、重复的发出请求,则它们可能会把服务器上的资源用光。虽然程序员可能会协议的控制是很少的,但应该理解协议是如何使分布式软件在网络故障下显示出脆弱性的,并应该在设计服务器时避免这种脆弱性

总结

循环的、面向连接的服务器每处理一个连接就循环一次。在连接请求从客户到达前,服务器将在accept的调用中出于阻塞状态。一旦下层协议软件建立了新的连接,并创建了新的套接字,调用accept将返回套接字的描述符,并使得服务器继续运行

DAYTIME协议根据每次连接的出现触发服务器的响应。客户不必发送请求,因为服务器只要检测新的连接就响应。为形成响应,服务器从OS获取当前时间,并转换为人容易读取的字符串,然后发送响应给客户。服务器在发送响应后,将关闭连接对应的套接字。由于DAYTIME服务只允许每个连接发一个响应,因而立即关闭连接的策略是可行的,但对那些在单个连接上运行多个请求到达的服务器,就必须等待客户关闭连接。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值