《UNIX网络编程 卷1:套接字联网API》学习笔记——TCP客户/服务器程序示例

概述

在这里插入图片描述

TCP回射服务器程序:main 函数

并发服务器程序。

#include   "unp.h"  /*根据实际情况添加头文件*/
int
main(int argc, char **argv)
{
	int listenfd, connfd;
	pid_t childpid;
	socklen_t clilen;
	struct sockaddr_in cliaddr, servaddr;
	/*创建一个TCP套接字*/
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT); /*SERV_PORT 服务器的众所周知端口*/
	bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
	listen(listenfd, LISTENQ); /*listen 把该套接字转换成一个监听套接字*/
	for (; ; )
	{
	/* 服务器阻塞与 accept 调用,等待客户连接的完成 */
		clilen = sizeof(cliaddr);
		connfd = accept(listenfd, (SA *)&cliaddr, &clilen);
	/* 并发服务器 */
		if ((childpid = fork()) == 0)
		/* fork为每个客户派生一个处理它们的子进程 */
		{
			/* 子进程 */
			close(listenfd); /*子进程关闭监听套接字*/
			str_echo(connfd); /*子进程接着调用str_echo处理客户*/
			exit(0);
		}
		/*父进程关闭已连接套接字*/
		close(connfd);
	}	
}

TCP回射服务器程序:str_echo 函数

str_echo 函数执行处理每个客户的服务器:从客户读入数据,并把它们回射给客户。

#include   "unp.h"  
void
str_echo(int sockfd);
{
	ssize_t  n;
	char  buf[MAXLINE];
again:
/*read函数从套接字读入数据,writen函数把其中内容回射给客户 */
	while ((n = read(sockfd, buf, MAXLINE)) > 0)	
		writen (sockfd, buf, n);
	if (n < 0 && errno == EINTR)
		goto again;
	else if (n < 0)
		err_sys ("str_echo: read error");	
}

TCP回射客户程序: main 函数

#include   "unp.h"  
int
main(int argc, char **argv)
{
	int  sockfd;
	struct sockaddr_in servaddr;
	if (argc != 2)
		err_quit ("usage: tcpcli <IPaddress>");
	/*创建一个TCP套接字,用服务器的IP地址和端口号装填一个网际套接字地址结构*/
	sockfd = socket (AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT); /*SERV_PORT服务器的众所周知端口号*/
	inet_pton(AF_INET, ARGV[1], &servaddr.sin_addr);
	/*connect 创建与服务器的连接*/
	connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
	/*str_cli 函数完成剩余部分的客户处理工作*/
	str_cli(stdin, sockfd);
	exit(0);
}

TCP回射客户程序: str_cli 函数

str_cli 函数完成客户处理循环:从标准输入读入一行文本,写到服务器上,读回服务器对该行的回射,并把回射行写到标准输出上。

#include   "unp.h"  
void
str_cli(FILE *fd, int sockfd)
{
	char  sendline[MAXLINE], recvline[MAXLINE];
	/*fgets读入一行文本*/
	while (Fgets(sendline, MAXLINE, fp) != NULL)
	{
		/*writen 把该行发送给服务器*/
		writen(sockfd, sendline, strlen(sendline));
		/*readline 从服务器读入回射行*/
		if (readline (sockfd, recvline, MAXLINE) == 0)
			err_quit("str_cli: server terminated prematurely");
			/*fputs 把它写到标准输出*/
		Fputs(recvline, stdout);
		/*当遇到文件结束符或错误时,fgets将返回一个空指针,于是客户处理循环终止*/
	}
}

正常启动

首先,在主机Linux上后台启动服务器。

tcpserv01 &

服务器启动后,它调用 socket、bind、listen 和 accept,并阻塞于 accept 调用。
在启动客户之前,可运行 netstat 程序来检查服务器监听套接字的状态。

netstat -a

接着在同一个主机上启动客户,并指定服务器主机的IP地址为 127.0.0.1 (环回地址)。
也可以指定该地址为该主机的普通(非环回)IP地址。

tcpcli01 127.0.0.1

客户调用 socket 和 connect ,后者引起TCP的三路握手过程。当三路握手完成后,客户中的 connect 和 服务器中的 accept 均返回,连接于是建立。

接着发生的步骤如下:

  • (1)客户调用 str_cli函数,该函数将阻塞于 fgets 调用,因为还未曾键入过一行文本。
  • (2)当服务器中的 accept 返回时,服务器调用 fork ,再由子进程调用 str_rcho。该函数调用 readline,readline 调用 read ,而 read 在等待客户送入一行文本期间阻塞。
  • (3)另一方面,服务器父进程再次调用 accept 并阻塞,等待下一个客户连接。
    至此,有3个都在睡眠(即已阻塞)的进程:客户进程、服务器父进程和服务器子进程。

也可用ps命令来检查这些进程的状态和关系。

ps -t pts/6 -o pid, ppid, tty, stat, args, wchan
网络进程的 STAT 列都是“S”,表明进程在为等待某些资源而睡眠。
进程处于睡眠状态时WCHAN列指出相应的条件。
Linux在进程阻塞于 accept 或 connect 时,输出 wait_for_connect;
在进程阻塞于套接字输入或输出时,输出 tcp_data_wait;
在进程阻塞于终端 I/O时,输出 read_chan 。

正常终止

客户端与服务器连接后,键入内容,会得到回射。
键入终端 EOF 字符(Control-D)以终止客户。
此时如果立即执行 netstat 命令,将看到如下结果:
在这里插入图片描述
正常终止客户和服务器的步骤。

  • (1)当我们键入EOF字符时,fgets 返回一个空指针,于是 str_cli 函数返回。
  • (2)当 str_cli 返回到客户的main函数时,main通过调用exit终止。
  • (3)进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核关闭。
  • (4)当服务器TCP接收FIN时,服务器子进程阻塞于 readline 调用,于是 readline 返回0。
  • (5)服务器子进程通过调用exit来终止。
  • (6)服务器子进程中打开的所有描述符随之关闭。
  • (7)进程终止处理的另一部分内容是:在服务器子进程终止时,给父进程发送一个SIGCHLD 信号。

子进程状态为 “Z” ,表示僵死。

服务器主机崩溃

在不同的主机上运行客户和服务器。
先启动服务器,再启动客户,接着在客户上键入一行文本以确认连接工作正常,然后从网络上断开服务器主机,并在客户上键入另一行文本。
步骤如下所述。

  • (1)当服务器主机崩溃时,已有的网络连接上不发出任何东西。
  • (2)我们在客户上键入一行文本,它由writen写入内核,再由客户TCP作为一个数据分节送出。客户随后阻塞于 readline调用,等待回射的应答。
  • (3)如果我们用 tcpdump 观察网络就会发现,客户TCP持续重传数据分节,试图从服务器上接收一个ACK。

服务器主机崩溃后重启

假设服务器主机崩溃并重启。
模拟这种情形的最简单方法就是:先建立连接,再从网络上断开服务器主机,将它关机后再重新启动,最后把它重新连接到网络。

所发生的步骤如下所述:

  • (1)我们启动服务器和客户,并在客户键入一行文本以确认连接已经建立。
  • (2)服务器主机崩溃并重启。
  • (3)在客户上键入一行文本,它将作为一个TCP数据分节发送到服务器主机。
  • (4)当服务器主机崩溃后重启时,它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所收到的来自客户的数据分节响应以一个RST。
  • (5)当客户TCP收到该RST时,客户正阻塞于 readline 调用,导致该调用返回 ECONNRESET 错误。

服务器主机关机

Unix系统关机时,init 进程通常先给所有进程发送 SIGTERM 信号(该信号可被捕获),等待一段固定的时间(往往在5到20秒之间),然后给所有仍在运行的进程发送 SIGKILL信号(该信号不能被捕获)。

学习参考资料:

《UNIX网络编程 卷1:套接字联网API》 第3版

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>