您的位置:首页 > 理论基础 > 计算机网络

TCP C/S 示例详解

2014-05-04 09:34 183 查看
下面的代码实现 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命令看到。

玩                           
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: