UNIX Network Programming笔记之第五章上篇

    第五章的学习笔记分为上下两篇, 上篇通过分析书中一个简单的例子, 了解客户端和服务器正常启动的情形. 
    该章根据代码会深入探讨一些问题, 如:client和server在启动时发生了什么? client正常终止发生了什么?若server崩溃, client会发生什么等等, 通过理解这些问题能够帮助我们写出更加健壮的client和server代码. 

    以下是一个回射的client和server代码:

以下是server代码

#include	"unp.h"

void str_echo(int connfd)
{
ssize_t n; 
char buf[MAXLINE];
again:
while((n = read(connfd, buf, MAXLINE)) > 0)
Writen(connfd, buf, n);
if(n < 0 && errno == EINTR) goto again;
else if(n < 0) err_sys("str_echo: read error");
}
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) {	/* child process */
Close(listenfd);	/* close listening socket */
str_echo(connfd);	/* process the request */
exit(0);
}
Close(connfd);	 /* parent closes connected socket */
}
}

以下是client代码

#include "unp.h"

void str_cli(FILE *fp, int sockfd)
{
	char sendline[MAXLINE], recvline[MAXLINE];
	while(fgets(sendline, MAXLINE, fp) != NULL){
		Writen(sockfd, sendline, strlen(sendline));

		if(Readline(sockfd, recvline, MAXLINE) == 0)
			err_quit("echo_cli: server terminated permaturely");
		fputs(recvline, stdout);
	}
}

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

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

	str_cli(stdin, sockfd);		/* do it all */

	exit(0);
}
在自己实现这些代码的时候遇到了几个错误:
1. 启动server, 执行client的时候出现"socket operation on non-socket"的错误, 该错误是由connect函数产生的, 原因是套接字不对; 在检查代码时发现调用socket进行if判断时,括号位置不对, 导致sockfd不是一个套接字描述符.
2. 在输入命令时, 发现服务器不会返回内容给client, 用gdb调试发现是在server的str_echo函数中出现问题, 调用封装函数Readn, 循环read阻塞导致的.

开始分析正常启动过程: 
1. 当只运行server时, server会在accept处阻塞; 当只运行client时, connect会返回错误
2. 然后运行client, client调用connect函数触发三路握手, 在str_cli函数的fgets处阻塞,等待用户输入; 同时server的accept函数返回, 调用fork实现简单并发, 父进程监听连接,又会在accept处阻塞, 而子进程中建立连接处理请求.
3. 子进程会在str_echo函数的read处阻塞
此时使用netstat -a命令可以看到: 父进程的socket的状态为LISTEN, 而子进程的socket状态为ESTABLISHED, client的socket状态也是ESTABLISHED


上图第一行是server父进程的套接字, 第二行是client的套接字, 第三行是server子进程的套接字
正常终止的过程:
1. 当用户输入EOF字符, fgets将会返回NULL, str_cli将会返回
2. client回调用exit函数, 将client进程终止, 进程终止将会关闭所有打开的描述符, 将会引发TCP连接关闭的四路分组, client发送FIN给server,server返回ACK给client, 此时client的socket状态为FIN_WAIT_2, 而server的socket状态为CLOSE_WAIT
3. server的子进程的read会返回0, echo_str函数返回, 随后子进程调用exit函数终止
4. 子进程中打开的所有描述符将关闭, 引发TCP连接的终止序列的最后两个分节, server向client发送FIN和client向server返回ACK. 连接完全终止, client的socket进入TIME_WAIT状态
5. 子进程终止, 给父进程发送一个SIGCHLD信号, 而该信号没有被捕获, 由于父进程未加处理, 所以子进程将进入僵死状态. 
 

5263的进程就是子进程, 已经处于僵死状态, 我们必须清理僵死进程.

下篇将会介绍信号, 以及其他的边界条件




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值