TCP C/S 示例详解

时间:2021-01-31 20:47:59


下面的代码实现 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,首先,服务器启动,它调用socket,bind ,listen,accept,并阻塞与accept调用,我们还没有启动客户。此时,查看套接字情况netstat

  有一个套接字处于LISTEN的状态,

2,然后,启动客户,客户调用socket和connect,后者引发三次握手过程,当三路握手完成后,客户中的connect和accept均返回,于是链接建立。

       a,客户调用str_cli函数,该函数阻塞与fgets调用。

       b,   服务器accept返回后,调用fork,再由子进程调用str_echo。该函数阻塞与read调用,等待读入数据。

       c, 另一方面,服务器父进程再次调用accept并阻塞,等待下一个客户连接。

 自此,有三个进程都在眠,客户进程,服务器父进程,服务器子进程。

当三路握手完成后,我们首先列出客户的步骤,然后列出服务器的步骤,是有原因的:客户接受到三路握手的第二个分节(ack+syn)connect就返回了,而服务器要等到三路

握手的第三个分节才返回(ack)即在connect返回之后再过半个RTT才返回。


此时,再次查看套接字状态,可以看到,有两个ESTABLIED,一个LISTEN 态,(因为我们是在同一主机运行客户与服务器的)。


终止:




在客户端,我们键入终端字符EOF字符(ctrl+D)以终止客户,此时,如果立即执行netstat命令(立即,因为过段时间,套接字状态会有变化)。

可以看到,当前链接的客户端套接字进入了TIME_WAIT状态,服务器父进程仍在LISTEN状态。


可以总结出:

1,当我们键入EOF字符释,fgets返回一个空指针,于是str_cli函数返回。

2,当str_cli返回到客户的main函数时候,main函数通过exit终止。

3,进程终止处理的部分工作是关闭所有打开的描述符,当然包括套接字,因此客户打开的套接字由内核关闭。这导致客户tcp发送一个FIN分节给服务器,服务器则ACK响应,

这就是TCP连接终止序列的前半部分。(注意到,这时候的服务器子进程还是存在的),自此,服务器子进程套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2

状态(接受了ACK分节后从FIN_WAIT_1变迁的). 

4, 当服务器紫禁城TCP接受到FIN时候,服务器紫禁城阻塞于read调用,于是read返回0,这导致str_echo函数返回服务器子进程的main函数。

5,服务器子进程通过调用exit(0)来终止

5,服务器子进程打开的所有描述符随之关闭。由子进程来关闭已链接套接字会引发TCP链接终止的后两个分节:一个从服务器端发出的FIN后从客户端到服务器的ACK。

 自此,连接完全终止,客户套接字进入TIME_WAIT状态(需要等待一段时间才关闭),服务器子进程套接字关闭了。


6,进程终止处理的另一个部分:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。这一点在本例中发生了,但是我们没有捕获处理,既然附近成没有处理

那么紫禁城于是进入僵死状态。 这可以用ps命令看到。


玩