分配被动的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服务只允许每个连接发一个响应,因而立即关闭连接的策略是可行的,但对那些在单个连接上运行多个请求到达的服务器,就必须等待客户关闭连接。