1. 程式人生 > >11-服務端程序終止

11-服務端程序終止

  這一篇我們將討論伺服器程序終止的問題。

  先啟動服務端,再啟動客戶端,然後殺死服務端子程序,以此來模擬伺服器程序終止的情況。如果客戶端再向服務端傳送資料,這將會發生什麼情況?

伺服器程式:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h> #include <stdlib.h> #define SERV_PORT 10001 ////註冊SIGCHLD訊號回收子程序 void wait_child(int signo) { printf("hello SIGCHILD\n"); //非阻塞方式回收子程序 while (waitpid(0, NULL, WNOHANG) > 0); } int main(void) { pid_t pid; int lfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char
buf[BUFSIZ], clie_IP[BUFSIZ]; int n, i ,n2; lfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(lfd, (struct sockaddr *)&serv_addr, sizeof
(serv_addr)); listen(lfd, 128); while (1) { clie_addr_len = sizeof(clie_addr); cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); printf("client IP:%s, port:%d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); //建立子程序 pid = fork(); if (pid < 0) { perror("fork error"); close(cfd); exit(1); } else if (pid == 0) { close(lfd); break; } else { //父程序 close(cfd); //捕捉SIGCHLD訊號 signal(SIGCHLD, wait_child); } } //子程序 if (pid == 0) { while (1) { n = read(cfd, buf, sizeof(buf)); if (n == 0) { printf("peer close\n"); //如果read返回0,說明對端已關閉,列印退出客戶端資訊 printf("client IP:%s, port:%d ----- exit\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)) , ntohs(clie_addr.sin_port)); close(cfd); break; //子程序退出 } else if (n == -1) { perror("read error"); close(cfd); break; } else { for (i = 0; i < n; i++){ buf[i] = toupper(buf[i]); } n2 = write(cfd, buf, n); if(n2 < n){ puts("short write"); } } } } close(lfd); return 0; }

客戶端程式:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>

#define SERV_IP "192.168.0.107"
#define SERV_PORT 10001

int main(void) {
    int sfd, len , ret;
    struct sockaddr_in serv_addr;
    char buf[BUFSIZ];
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));                       
    serv_addr.sin_family = AF_INET;                         
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); 
    serv_addr.sin_port = htons(SERV_PORT);                      
    ret = connect(sfd, (struct sockaddr *)&serv_addr ,  sizeof(serv_addr));
    if(ret < 0){
        perror("connect error:");
        close(sfd);
        exit(0);
    }

    //迴圈讀寫資料
    while (1) {
        fgets(buf, sizeof(buf), stdin);
        write(sfd, buf, strlen(buf)); 
        len = read(sfd, buf, sizeof(buf));
        //服務端的RST是什麼時候到達,如果是在read返回前到達,那麼read將返回錯誤
        //如果是在read之後到達,那麼read將返回0,判斷為對端已關閉
        if(len == 0){
            puts("peer close");
            break;
        }else if(len < 0){
             perror("read error: ");
}
        write(STDOUT_FILENO, buf, len);
    }
    //關閉連結
    close(sfd);
    return 0;
}

1 . 先啟動服務端

2.然後執行./client命令,啟動客戶端,並在客戶端輸入hello,然後服務端回射HELLO,驗證客戶端和服務端之間通訊正常

這裡寫圖片描述

3.然後將服務端程序切換到後臺執行,並找到子程序,通過kill命令將子程序殺死。

這裡寫圖片描述

  當通過kill命令把服務端子程序終止後,父程序收到SIGCHLD訊號後回收了子程序,並且子程序開啟的所有套接字都被關閉,這將導致服務端子程序將傳送了一個FIN,同時客戶端響應一個ACK,這只是代表服務端到客戶端的連線關閉了而已,也就是所謂的半關閉,進入了FIN_WAIT2狀態。

這裡寫圖片描述

  從tcpdump抓取到的資料包來看,服務端子程序確實傳送了FIN,並接收到了客戶端響應的ACK。

  對此,客戶端並不做任何事情,但問題是客戶端將阻塞在read呼叫處,等待從終端讀取資料。此時我們在客戶端的終端上輸入world,因為此時只是半關閉,所以tcp允許這樣做。

這裡寫圖片描述

  當服務端子程序的tcp收到客戶端的world時,於是就響應一個RST段(這是因為之前開啟的套接字服務端子程序已經終止掉了,無法再接收資料了,此時服務端期望對端下一個傳送FIN,而不是傳送一個數據報文,所以才會以RST響應)。我們通過tcpdump檢視發現,確實驗證了這一點,重點關注最後4個報文。

這裡寫圖片描述

  但是對於客戶端來說,它並不知道這個RST,因為它執行的太快了,在呼叫了write後又立即呼叫了read,也就是說在收到對端傳送的RST段之前,它所呼叫的read就已經返回了,所以read返回0得出對端已經關閉,於是列印peer closed,退出迴圈,執行 close(sockfd)。然後向對端傳送 FIN 段,然後服務端以一個RST響應了客戶端的FIN。