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

UNP-UNIX网络编程 第五章:TCP客户/服务器程序示例

2018-01-16 20:10 375 查看
{
int n;
if((n = socket(family, type, protocol)) < 0)
{
printf("socket() error!");
exit(0);
}
else
return n;
}


服务器



{
ssize_t     n;
char        buf[MAXLINE];
again://接收到客户的FIN,read()返回0,函数返回,子进程终止
while ((n = read(sockfd, buf, MAXLINE)) > 0)//从套接字读(阻塞)
Writen(sockfd, buf, n);//写给客户
if (n < 0 && errno == EINTR)//异常处理
goto again;
else if (n < 0)
err_sys("str_echo: read error");
}


{//因为当父进程同时收到很多 SIGCHLD 信号时 信号函数只执行一次或多次,但是不能肯定一定是 5 次,
//所以会导致还会有僵尸进程。基于此原因,我们用 waitpid() 代替 wait() 函数,修改信号捕获函数如下:
pid_t pid;
int stat;
//pid = wait(&stat);//处理1个
//printf("child %d terminated\n", pid);
while((pid=waitpid(-1,&stat,WNOHANG)>0)//处理多个
printf("child %d terminated\n",pid);
return;
}


我们不要忘了当有尚未终止的子进程时会阻塞,而 waitpid 函数可以通过设置第三个参数为 WNOHANG 来告知 waitpid ,

当有尚未终止的子进程的时候不要阻塞,所以就可以通过循环处理所有终止的子进程,故不会有僵尸进程残留!

客户端

#include    "unp.h"
int main(int argc, char **argv)
{
int                 sockfd;
struct sockaddr_in  servaddr;

if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
//创建TCP套接字,用服务器的IP地址和端口号装填
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);
}

void str_cli(FILE *fp, int sockfd)
{
char    sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) //从客户端读入文本
{
//Writen(sockfd, sendline,1); sleep(1);//最后用不到
Writen(sockfd, sendline, strlen(sendline));//写给服务器
if (Readline(sockfd, recvline, MAXLINE) == 0)//从套接字读入回射行
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);//写到标准输出
}
}

void str_cli(FILE *fp, int sockfd)//这个函数是由select调用来驱动。而上面的函数是由fgets函数驱动的。
{
int         maxfdp1;  //第一个参数
fd_set      rset;  //读-集合指针
//char        sendline[MAXLINE], recvline[MAXLINE];
char        buf[MAXLINE];
int         n;
stdineof = 0;
FD_ZERO(&rset);  //由FD_ZERO初始化可读性描述符集
for ( ; ; )
{
if(stdineof == 0)//1.只要标志为0,主循环中总是select标准输入的可读性
FD_SET(fileno(fp), &rset);  //并用FD_SET打开两位,一个对应于标准I/O文件指针fp
FD_SET(sockfd, &rset);  //一位对应于套接口sockfd
maxfdp1 = max(fileno(fp), sockfd) + 1;  //fileno函数把标准I/O文件指针转换为其对应的描述字
Select(maxfdp1, &rset, NULL, NULL, NULL);  //上面计算出了两个描述符的较大后值,读-集合指针

//如果在selecrt返回时套接口是可读的,则由readline来读,由fputs输出。
if (FD_ISSET(sockfd, &rset))  /* socket is readable */
{
if (n=Read(sockfd, recvline, MAXLINE) == 0) //把对文本行变为对缓冲区,从套接字读入
{
if(stdineof == 1)  return;//遇到EOF正常终止
else//否则服务器就是过早终止了
err_quit("str_cli: server terminated prematurely");//同原来
Write(fileno(stdout),buf,n)  //把对文本行变为对缓冲区,写到标准输出
}
}

//如果标准输入可读,则由fgets读入一行,并用writen将其写到套接口
if (FD_ISSET(fileno(fp), &rset)) /* input is readable */
{
if ((n=Read(fileno(fp), buf,MAXLINE)) == NULL) //把对文本行变为对缓冲区,从客户端读入文本
{
stdineof =1;    //在标准输入碰到EOF,stdineof 置位1
Shutdown(sockfd,SHUT_WR);//调用SHUT_WR来send FIN
FDCLR(fileno(fp), &rset);
continue;
}
Writen(sockfd, buf,n);//把对文本行变为对缓冲区,写给服务器
}
}
}


(1)服务器进程在客户之前终止,也就是说,终止前是有给客户端发送FIN的,服务器主机关机也跟这类终止相同。

此时:客户:CLOSE_WAIT,服务器:FIN_WAIT2;

但是虽然发送的FIN可以得到客户TCP的ACK回复,但是正阻塞在fgets调用的客户端程序却没有察觉,(select和poll)

如果此时客户端输入并且将数据发送给对端,这时服务端会回复RST,为什么?

因为这个和一般的终止连接不同,对端进程已经终止,没有进程能识别这个数据。此时客户调用readline读取到刚开始的FIN,这时readline返回0,报告连接终止。

我们再继续往不存在的连接发送数据就会产生SIGPIPE信号;当一个进程向某个已经接受到RST的套接字执行写操作时,内核向应用进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止。我们调用writen两次,第一次引发RST,第二次再写产生SIGPIPE。

(2)服务器关机,会发送SIGTERM信号,类似(1)客户端通过select/poll函数,使得服务器进程一终止,就能被客户检测到。

(3)服务器主机崩溃,那么用户发送的数据(此时才检测到服务器崩溃)将使得路由器相应一个destination/ unreachable的ICMP消息。客户端将会产生数次重传。处理这种问题可以有两种方法:

1.对readline调用设置一个更短的超时

2.采用SO_KEEPALIVE选项

(4)服务器重启后,它的TCP丢失了崩溃前的所有连接消息,因此服务器TCP对于所有收到的来自客户的数据分节响应一个RST。而此时客户正阻塞在readline调用,导致该调用返回ECONNRESET
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: