selelct函数的需要原因

先看一段代码,实现的是echo回射功能,我们从这个程序中,来模拟服务器进程先于客户进程终止后,它会发生哪些问题。。
#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_port=htosn(SERV_PORT);
	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
	Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
	Listen(listenfd,LISTENQ);
	for(;;)
	{
		clilen=sizeof(cliaddr);
		connfd=Accept(listenfd,(SA*)&cliaddr,&clilen);
		if((childpi=fork())==0)
		{
			Close(listenfd);
			str_echo(connfd);
			exit(0);
		}
		Close(connfd);
	}
}
void str_echo(int connfd)
{
	ssize_t n;
	char buf[MAXLINE];
again:
	while((n=read(connfd,buf,MAXLINE))>0)
		Write(connfd,buf,n);
	if(n<0&&errno==EINTR)
		goto again;      // 这里EINTR表示是系统调用中断,若是这种情况
	                     // 则重新调用系统调用。
						 //
	else if(n<0)
		err_sys("str_echo:read error");
}




//
//client.c
int main(int argc,char**argv)
{
	int sockfd;
	struct sockaddr_in servaddr;
	if(argc!=2)
		err_quit("usage:tcpcli<IPaddress>");
	sockfd=Socket(AF_IENT,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);
	exit(0);
}



void str_cli(FILE*fp,int sockfd)
{
    char sendline[MAXLINE],recvline[MAXLINE];
	while(Fgets(sendline,MAXLIEN,fp)!=NULL)
	{
		Write(sockfd,sendline,strlen(sendline));
		if(Read(sockfd,recvline,MAXLINE)==0)
			err_quit("str_cli:server terimnate prematily");
		Fputs(recvline,stdout);
	}
}



现在启动我们的客户/服务器对,一切正常,回射功能实现完好。


接下来,我们先杀死服务器子进程。这是在模拟服务器进程崩溃的情形,我们可查看客户将发生什么,(要区分服务器崩溃和服务器进程崩溃的情形)


1,执行kill命令,作为进程终止处理的部分工作,子进程中的所有打开的进程描述符都被关闭,这就导致向客户发送一个FIN,而客户TCP则响应一个ACK(这是TCP的自动实现机制,并不需要用户做什么)

(这里要注意用词, 区别客户进程,客户TCP ,服务器进程,服务器TCP),这就是TCP连接终止的前半部分。

2,SIGCHILD信号被发送个服务器父进程,并得到正确处理。

3,这时候的状态是,FIN已经到了客户的TCP,即到了接受缓冲区了,但是还没有到用户缓冲区,客户程序没有读取,而服务器端TCP已经接受了ACK,进入了FIN_WAIT2

状态了。 且,因为客户TCP接受到了FIN分节,故它进入了CLOSE_WAIT状态。 可以用netstat查看。


4, 然而,问题在于,这个FIN分节并没有被客户进程读取,它还存在与接受缓冲区,这时客户进程阻塞在fgets上面,等待用户从终端输入。

当我们键入 “另外一行” ,这时候客户进程调用write,紧接着,客户TCP把数据发送出去。(TCP允许这么做,因为客户TCP受到FIN,只是表示服务器进程已经关闭了连接的

服务器端,从而不再向其中发送数据而已,但还可有接受数据,FIN的接受并没有告知客户TCP,服务器进程,(注意用词,TCP和进程)已经终止了,不过它在本例中确实终止了,因为我们已经kill了。)


5,当服务器TCP接受到客户的数据时候,既然先前打开的那个套接字的进程已经终止了,于是,服务器TCP会响应一个RST。告知对端,端口已经关闭。(这时候服务器子进程已经终止,是内核中的TCP协议来做的这件事)。


5,然而,客户进程确看不见这个RST,原因在于:因为它调用write后,立即会调用read从接受缓冲区中接受数据,我们还记得,在接受缓冲区中存在一个分节,即

还没有别客户进程读取的服务器TCP发送的FIN分节,那么这时候调用read,它就会读到这个FIN,然后,read理解返回0,于是打印“server terminated prematruely”,

6,自此,客户终止进程了(调用err_quit),所有打开的描述符都关闭了 。这个 客户进程永远也读不到这个RST分节。


本例子的问题在于:当FIN达到套接字缓冲区时候,客户正阻塞在fgets调用。客户实际上在应对两个描述符----套接字和用户输入。事实上,这正视select和poll两个函数的目的之一。不然进程阻塞在某个特定的输入上。    一旦服务器杀死子进程,客户就会立刻从缓冲区读出FIN。



从这个例子也很好地反映了             TCP进程和TCP 的一些关联与区别。                以及 描述符与流的关系  缓冲区 的关系。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值