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

UNP - 第五章 TCP客户/服务器示例 - 学习笔记

2017-08-31 15:25 441 查看

1 - 前言

本章在第四章的理论知识基础上,实现一个TCP/IP客户服务器的原型程序。这个原型程序是执行以下步骤的一个回射服务器。

(1)客户从标准输入读入一行文本,并写给服务器

(2)服务器从网络输入读取这行文本,并回射给客户

(3)客户从网络输入读入这行回射文本,并显示在标注输入上

该程序的模型图:



该程序时后续我们研究套接字通信的基础模型,其他的比如I/O复用,多进程服务器,多线程服务器等均基于该程序作了相应的改动。所以十分重要。

除了这个基础的程序之外,还会分析多种特殊边界情况。例如:

1,客户和服务器启动时发生什么?

2,客户正常终止时发生什么?

3,若服务器进程在客户之前终止,则客户发生什么?

4,若服务器主机崩溃,则客户发生什么?

通过这些情况的分析,弄清楚网络层次发生了什么,以及对应套接字的API是如何处理的?

2 - tcp回射服务器程序

#include "unp.h"
#include "apue.h"

void str_echo(int sockfd){
ssize_t  n;
char    buf[MAXLINE];

again:
while((n == read(sockfd,buf,MAXLINE)) > 0){
write(sockfd,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;

if((listenfd = socket(AF_INET,SOCK_STREAM,0) )< 0){
err_quit("socket create error");
}

bzero(&servaddr,sizeof(servaddr));

servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

if(bind(listenfd,(SA *)&servaddr,sizeof(servaddr)) < 0){
err_quit("bind error");
}

if(listen(listenfd,LISTENQ) < 0){
err_quit("listen error");
}

for(;;){
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd,(SA* )&cliaddr,&clilen)) < 0){
err_sys("accept error");
}else{
if((childpid = fork()) == 0){     //child process
close(listenfd);    //close listening socket
str_echo(connfd);   //process the request
exit(0);
}
close(connfd);  //parent close conneted socket
}

}
}


分析:

服务器进程,首先创建套接字,然后绑定端口号和地址,然后调用listen系统调用,进入监听状态。然后调用accept系统调用,等待已完成的tcp连接。

在str_echo回射处理函数中,调用read系统调用从网络输入中读取数据。然后在调用write函数,将其发送给客户端。

3 - TCP回射客户程序

4 - 几种边界情况分析

4 - 1 客户和服务器启动时发生什么? ?

对于服务器来说,启动服务器后,它调用socket,bind,listen和accept,并阻塞于accept调用。在启动客户端之前,服务器套接字处于LISTEN状态。

当客户端调用socket和connect,后者引起tcp的三次握手过程。当三次握手过程完成后,客户中的connect和服务器的accept均返回、连接建立。此时双方的套接字均为established状态。

接这发生如下步骤:

1,客户调用str_cli函数,该函数阻塞于fgets调用。直到输入内容

2,当服务器中的accept返回时,服务器调用fork,在由子进程调用str_echo,该函数调用readline,readline调用read。而read在等待客户送入一行文本期间阻塞。

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

4 - 2 客户正常终止时发生什么?

正常情况的步骤如下:

1,当我们键入EOF字符时,fgets返回以空指针,于是str_cli函数返回

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

3,进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字有内核关闭。这导致客户tcp发送一个FIN给服务器。服务器TCP则以ACK响应。这就是TCP连接终止序列的前半部分。支持,服务器套接字处于CLOSE_WAIT状态。客户套接字处于FIN_WAIT_2状态。

4,当服务器TCP接收FIN,服务器子进程阻塞于readline调用,于是readline返回0。这导致str_echo 函数返回服务器子进程的main函数。

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

6,服务器子进程中国开发的所有描述符随之关闭。由子进程来关闭已经连接套接字会引起终止序列的最后两个分节:一个从服务器的FIN和一个从客户到服务器的ACK。至此,连接完全终止,客户套接字进入TIME_WAIT状态。

7,进程终止的另一部分内容是:在服务器进程终止时,给父进程一个SIGCHLD信号。在本例中,该信号产生了,但是我们在代码并没有捕捉该信号。而该信号的默认行为时被忽略。既然父进程未处理。则子进程处于僵尸状态。

4 - 3 若服务器进程在客户之前终止,则客户发生什么?

4 - 4 若服务器主机崩溃,则客户发生什么?

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