第五章 TCP回射客户程序

本章大意
讲解了一个回射C/S程序,并分析各种情况(正常或异常)下执行的情况。

细节摘录
1. 服务器程序代码初步分析:
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;

	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); // 填入众所周知端口

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); // 等待客户连接的完成

		if ( (childpid = Fork()) == 0) {
			Close(listenfd); // 关闭监听套接字
			str_echo(connfd); // 客户处理函数
			exit(0); 
		}
		Close(connfd); //关闭已连接套接字(但还在子进程中“生效”着)
	}
}
2. 客户程序代码初步分析:
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: tcpcli <IPaddress>");

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET; 
	servaddr.sin_port = htons(SERV_PORT); // 取得端口号
	Inet_pton(AF_INET, argv[1], &servaddr.si n_addr);

	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

	str_cli(stdin, sockfd); // 回射处理

	exit(0);
}
3. 在客户程序给出标准输入以前,服务器进程,服务器子进程,以及客户程序都将阻塞。
4. netstat -a可以打印出当前的所有TCP连接
5. 子进程被捕获一定要及时作出处理,否则将导致僵死。
6. 信号处理函数由信号值这个单一的整数参数调用,没有返回值。
7. 信号注册函数解析:
#include	"unp.h"

Sigfunc *
signal(int signo, Sigfunc *func)
{
	struct sigaction	act, oact; // 定义了注册结构

	act.sa_handler = func; // 定义信号处理函数
	sigemptyset(&act.sa_mask); // 表示信号函数执行期间,不阻塞额外的信号。
	act.sa_flags = 0;  
	/*
	 * 决定系统调用是被中断还是自动重启
	*/
	if (signo == SIGALRM) {
#ifdef	SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT; 
#endif
	} else {
#ifdef	SA_RESTART
		act.sa_flags |= SA_RESTART;		/* SVR4, 44BSD */
#endif
	}
	/*
	 * 正式注册
	*/
	if (sigaction(signo, &act, &oact) < 0)
		return(SIG_ERR);
	return(oact.sa_handler);
}
8. 下面是一个处理僵死进程的示例:
#include	"unp.h"

void
sig_chld(int signo) // 参数为信号,唯一整数。
{
	pid_t	pid; // 进程标识符类型
	int		stat; // 状态标号

	pid = wait(&stat); // 撤销僵死进程,改变状态变量,赋值标识符变量。
	printf("child %d terminated\n", pid); // 输出标识符
	return;
}
9. 当心慢阻塞系统调用,需要自动手工实现重启。(注册时直接声明为自动重启可以吗?还没弄清)
10. 重点区分wait和waitpid两个函数。
11. 使用waitpid版本的信号处理函数:
#include	"unp.h"

void
sig_chld(int signo)
{
	pid_t	pid;
	int		stat;

        /*
	 * waitpid函数参数说明:
	 * -1表示等待第一个终止的子进程,stat保存结果状态,WNOHANG告知内核在有尚未终止的子进程时不要阻塞内核。
	*/
	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}
12. 下面是经过信号处理机制完善的服务器回射程序:
#include	"unp.h"

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;
	void				sig_chld(int);

	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);

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	Signal(SIGCHLD, sig_chld); // 注册信号函数

	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		/*
		 * 下accept函数必须处理信号机制下的系统调用中断问题(不能设置自动重启?)
		*/
		if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
			if (errno == EINTR)
				continue;		/* back to for() */
			else
				err_sys("accept error");
		}

		if ( (childpid = Fork()) == 0) {	/* child process */
			Close(listenfd);	/* close listening socket */
			str_echo(connfd);	/* process the request */
			exit(0);
		}
		Close(connfd);			/* parent closes connected socket */
	}
}
13. 了解accept返回前终止的执行情况
14. 分析服务器进程异常终止的情况
15. 当进程向某个已经收到RST的套接字写操作时,默认的处理是终止写进程。当然也可以自行去定义。
16. 如果是服务器的住据崩溃,那么服务器对请求不做任何操作。
17. 服务器主机崩溃后重启了,仍然会导致连接异常。因为重启会丢失之前的连接信息。
18. 掌握CS通信时的格式控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值