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

{
    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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值