国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

《TCP IP網(wǎng)絡(luò)編程》第十章

這篇具有很好參考價(jià)值的文章主要介紹了《TCP IP網(wǎng)絡(luò)編程》第十章。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

第 10 章 多進(jìn)程服務(wù)器端

10.1 進(jìn)程概念及應(yīng)用

并發(fā)服務(wù)端的實(shí)現(xiàn)方法:

????????通過改進(jìn)服務(wù)端,使其同時(shí)向所有發(fā)起請求的客戶端提供服務(wù),以提高平均滿意度。而且,網(wǎng)絡(luò)程序中數(shù)據(jù)通信時(shí)間比 CPU 運(yùn)算時(shí)間占比更大,因此,向多個(gè)客戶端提供服務(wù)是一種有效的利用 CPU 的方式。接下來討論同時(shí)向多個(gè)客戶端提供服務(wù)的并發(fā)服務(wù)器端。下面列出的是具有代表性的并發(fā)服務(wù)端的實(shí)現(xiàn)模型和方法:

  • 多進(jìn)程服務(wù)器:通過創(chuàng)建多個(gè)進(jìn)程提供服務(wù)
  • 多路復(fù)用服務(wù)器:通過捆綁并統(tǒng)一管理 I/O 對象提供服務(wù)
  • 多線程服務(wù)器:通過生成與客戶端等量的線程提供服務(wù)

第一種方法:多進(jìn)程服務(wù)器

?理解進(jìn)程:

????????進(jìn)程的定義如下:

占用內(nèi)存空間的正在運(yùn)行的程序。

????????假如你下載了一個(gè)游戲到電腦上,此時(shí)的游戲不是進(jìn)程,而是程序。只有當(dāng)游戲被加載到主內(nèi)存并進(jìn)入運(yùn)行狀態(tài),這是才可稱為進(jìn)程。

進(jìn)程 ID:

????????無論進(jìn)程是如何創(chuàng)建的,所有的進(jìn)程都會(huì)被操作系統(tǒng)分配一個(gè) ID。此 ID 被稱為「進(jìn)程ID」,其值為大于 2 的整數(shù)。1 要分配給操作系統(tǒng)啟動(dòng)后的(用于協(xié)助操作系統(tǒng))首個(gè)進(jìn)程,因此用戶無法得到 ID 值為 1 。接下來輸入以下命令來觀察在 Linux 中運(yùn)行的進(jìn)程:

ps au

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

?????????通過上面的命令可查看當(dāng)前運(yùn)行的所有進(jìn)程。需要注意的是,該命令同時(shí)列出了 PID(進(jìn)程ID)。參數(shù) a 和 u列出了所有進(jìn)程的詳細(xì)信息。

通過調(diào)用 fork 函數(shù)創(chuàng)建進(jìn)程:

????????創(chuàng)建進(jìn)程的方式很多,此處只介紹用于創(chuàng)建多進(jìn)程服務(wù)端的 fork 函數(shù):

#include <unistd.h>
pid_t fork(void);
// 成功時(shí)返回進(jìn)程ID,失敗時(shí)返回 -1

????????fork 函數(shù)將創(chuàng)建調(diào)用的進(jìn)程副本。也就是說,并非根據(jù)完全不同的程序創(chuàng)建進(jìn)程,而是復(fù)制正在運(yùn)行的、調(diào)用 fork 函數(shù)的進(jìn)程。另外,兩個(gè)進(jìn)程都執(zhí)行 fork 函數(shù)調(diào)用后的語句(準(zhǔn)確的說是在 fork 函數(shù)返回后)。但因?yàn)槭峭ㄟ^同一個(gè)進(jìn)程、復(fù)制相同的內(nèi)存空間,之后的程序流要根據(jù) fork 函數(shù)的返回值加以區(qū)分。即利用 fork 函數(shù)的如下特點(diǎn)區(qū)分程序執(zhí)行流程。

  • 父進(jìn)程:fork 函數(shù)返回子進(jìn)程 ID
  • 子進(jìn)程:fork 函數(shù)返回 0

????????此處,「父進(jìn)程」(Parent Process)指原進(jìn)程,即調(diào)用 fork 函數(shù)的主體,而「子進(jìn)程」(Child Process)是通過父進(jìn)程調(diào)用 fork 函數(shù)復(fù)制出的進(jìn)程。接下來是調(diào)用 fork 函數(shù)后的程序運(yùn)行流程。如圖所示:????????

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

?????????從圖中可以看出,父進(jìn)程調(diào)用 fork 函數(shù)的同時(shí)復(fù)制出子進(jìn)程,并分別得到 fork 函數(shù)的返回值。但復(fù)制前,父進(jìn)程將全局變量 gval 增加到 11,將局部變量 lval 的值增加到 25,因此在這種狀態(tài)下完成進(jìn)程復(fù)制。復(fù)制完成后根據(jù) fork 函數(shù)的返回類型區(qū)分父子進(jìn)程。父進(jìn)程的 lval 的值增加 1 ,但這不會(huì)影響子進(jìn)程的 lval 值。同樣子進(jìn)程將 gval 的值增加 1 也不會(huì)影響到父進(jìn)程的 gval 。因?yàn)?fork 函數(shù)調(diào)用后分成了完全不同的進(jìn)程,只是二者共享同一段代碼而已。接下來給出一個(gè)例子:

#include <stdio.h>
#include <unistd.h>
int gval = 10;
int main(int argc, char *argv[])
{
    pid_t pid;
    int lval = 20;
    gval++, lval += 5;
    pid = fork();
    if (pid == 0)
        gval += 2, lval += 2;
    else
        gval -= 2, lval -= 2;
    if (pid == 0)
        printf("Child Proc: [%d,%d] \n", gval, lval);
    else
        printf("Parent Proc: [%d,%d] \n", gval, lval);
    return 0;
}

運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????可以看出,當(dāng)執(zhí)行了 fork 函數(shù)之后,此后就相當(dāng)于有了兩個(gè)程序在執(zhí)行代碼,對于父進(jìn)程來說,fork 函數(shù)返回的是子進(jìn)程的ID,對于子進(jìn)程來說,fork 函數(shù)返回 0。所以這兩個(gè)變量,父進(jìn)程進(jìn)行了 +2 操作 ,而子進(jìn)程進(jìn)行了 -2 操作。?

10.2 進(jìn)程和僵尸進(jìn)程

????????文件操作中,關(guān)閉文件和打開文件同等重要。同樣,進(jìn)程銷毀和進(jìn)程創(chuàng)建也同等重要。如果未認(rèn)真對待進(jìn)程銷毀,他們將變成僵尸進(jìn)程。????????

僵尸(Zombie)進(jìn)程:

????????進(jìn)程的工作完成后(執(zhí)行完 main 函數(shù)中的程序后)應(yīng)被銷毀,但有時(shí)這些進(jìn)程將變成僵尸進(jìn)程,占用系統(tǒng)中的重要資源。這種狀態(tài)下的進(jìn)程稱作「僵尸進(jìn)程」,這也是給系統(tǒng)帶來負(fù)擔(dān)的原因之一。

僵尸進(jìn)程是當(dāng)子進(jìn)程比父進(jìn)程先結(jié)束,而父進(jìn)程又沒有回收子進(jìn)程,釋放子進(jìn)程占用的資源,此時(shí)子進(jìn)程將成為一個(gè)僵尸進(jìn)程。如果父進(jìn)程先退出 ,子進(jìn)程被init接管,子進(jìn)程退出后init會(huì)回收其占用的相關(guān)資源

產(chǎn)生僵尸進(jìn)程的原因:

????????為了防止僵尸進(jìn)程產(chǎn)生,先解釋產(chǎn)生僵尸進(jìn)程的原因。利用如下兩個(gè)示例展示調(diào)用 fork 函數(shù)產(chǎn)生子進(jìn)程的終止方式。

  • 傳遞參數(shù)并調(diào)用 exit() 函數(shù)
  • main 函數(shù)中執(zhí)行 return 語句并返回值

????????向 exit 函數(shù)傳遞的參數(shù)值和 main 函數(shù)的 return 語句返回的值都會(huì)傳遞給操作系統(tǒng)。而操作系統(tǒng)不會(huì)銷毀子進(jìn)程,直到把這些值傳遞給產(chǎn)生該子進(jìn)程的父進(jìn)程。處在這種狀態(tài)下的進(jìn)程就是僵尸進(jìn)程。也就是說將子進(jìn)程變成僵尸進(jìn)程的正是操作系統(tǒng)。既然如此,僵尸進(jìn)程何時(shí)被銷毀呢?

應(yīng)該向創(chuàng)建子進(jìn)程的父進(jìn)程傳遞子進(jìn)程的 exit 參數(shù)值或 return 語句的返回值。

如何向父進(jìn)程傳遞這些值呢?操作系統(tǒng)不會(huì)主動(dòng)把這些值傳遞給父進(jìn)程。只有父進(jìn)程主動(dòng)發(fā)起請求(函數(shù)調(diào)用)的時(shí)候,操作系統(tǒng)才會(huì)傳遞該值。換言之,如果父進(jìn)程未主動(dòng)要求獲得子進(jìn)程結(jié)束狀態(tài)值,操作系統(tǒng)將一直保存,并讓子進(jìn)程長時(shí)間處于僵尸進(jìn)程狀態(tài)。接下來的示例是創(chuàng)建僵尸進(jìn)程:????????

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    pid_t pid = fork(); // 創(chuàng)建一個(gè)新的子進(jìn)程

    if (pid == 0) // 子進(jìn)程執(zhí)行代碼
    {
        puts("Hi, I am a child Process"); // 輸出一條消息:"Hi, I am a child Process"
    }
    else // 父進(jìn)程執(zhí)行代碼
    {
        printf("Child Process ID: %d \n", pid); // 輸出子進(jìn)程的進(jìn)程ID(PID)
        sleep(30); // 父進(jìn)程睡眠30秒
    }

    // 以下代碼父子進(jìn)程共同執(zhí)行

    if (pid == 0) // 子進(jìn)程將執(zhí)行這部分代碼
        puts("End child process"); // 輸出一條消息:"End child process"
    else // 父進(jìn)程將執(zhí)行這部分代碼
        puts("End parent process"); // 輸出一條消息:"End parent process"

    return 0;
}

運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

?????????通過?ps au?命令可以看出,子進(jìn)程仍然存在,并沒有被銷毀,僵尸進(jìn)程在這里顯示為?Z+。30秒后,紅框里面的兩個(gè)進(jìn)程會(huì)同時(shí)被銷毀。

銷毀僵尸進(jìn)程 1:利用 wait 函數(shù)

為了銷毀子進(jìn)程,父進(jìn)程應(yīng)該主動(dòng)請求獲取子進(jìn)程的返回值。下面是發(fā)起請求的具體方法。一共有兩種:

#include <sys/wait.h>
pid_t wait(int *statloc);
/*
成功時(shí)返回終止的子進(jìn)程 ID ,失敗時(shí)返回 -1
*/

????????調(diào)用此函數(shù)時(shí)如果已有子進(jìn)程終止,那么子進(jìn)程終止時(shí)傳遞的返回值(exit 函數(shù)的參數(shù)返回值,main 函數(shù)的 return 返回值)將保存到該函數(shù)的參數(shù)所指的內(nèi)存空間。但函數(shù)參數(shù)指向的單元中還包含其他信息,因此需要用下列宏進(jìn)行分離:

  • WIFEXITED 子進(jìn)程正常終止時(shí)返回「真」
  • WEXITSTATUS 返回子進(jìn)程時(shí)的返回值

也就是說,向 wait 函數(shù)傳遞變量 status 的地址時(shí),調(diào)用 wait 函數(shù)后應(yīng)編寫如下代碼:

if (WIFEXITED(status))
{
    puts("Normal termination");
    printf("Child pass num: %d", WEXITSTATUS(status));
}

????????如下示例不會(huì)讓子進(jìn)程變成僵尸進(jìn)程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int status;
    pid_t pid = fork(); // 創(chuàng)建第一個(gè)子進(jìn)程

    if (pid == 0) // 子進(jìn)程執(zhí)行代碼
    {
        return 3; // 子進(jìn)程返回值為3,表示子進(jìn)程正常終止
    }
    else // 父進(jìn)程執(zhí)行代碼
    {
        printf("Child PID: %d \n", pid); // 輸出第一個(gè)子進(jìn)程的進(jìn)程ID(PID)
        pid = fork(); // 在父進(jìn)程中創(chuàng)建第二個(gè)子進(jìn)程

        if (pid == 0) // 第二個(gè)子進(jìn)程執(zhí)行代碼
        {
            exit(7); // 第二個(gè)子進(jìn)程以退出碼7正常終止
        }
        else // 父進(jìn)程執(zhí)行代碼
        {
            printf("Child PID: %d \n", pid); // 輸出第二個(gè)子進(jìn)程的進(jìn)程ID(PID)

            wait(&status); // 等待第一個(gè)子進(jìn)程終止并處理其退出狀態(tài)
            if (WIFEXITED(status)) // 驗(yàn)證第一個(gè)子進(jìn)程是否正常終止
                printf("Child send one: %d \n", WEXITSTATUS(status)); // 輸出第一個(gè)子進(jìn)程的返回值

            wait(&status); // 等待第二個(gè)子進(jìn)程終止并處理其退出狀態(tài)
            if (WIFEXITED(status)) // 驗(yàn)證第二個(gè)子進(jìn)程是否正常終止
                printf("Child send two: %d \n", WEXITSTATUS(status)); // 輸出第二個(gè)子進(jìn)程的返回值

            sleep(30); // 父進(jìn)程睡眠30秒
        }
    }

    return 0;
}

運(yùn)行結(jié)果:《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????此時(shí),系統(tǒng)中并沒有上述 PID 對應(yīng)的進(jìn)程,這是因?yàn)檎{(diào)用了 wait 函數(shù),完全銷毀了該子進(jìn)程。另外兩個(gè)子進(jìn)程返回時(shí)返回的 3 和 7 傳遞到了父進(jìn)程。

????????這就是通過 wait 函數(shù)消滅僵尸進(jìn)程的方法,值得注意的是:調(diào)用 wait 函數(shù)時(shí),如果沒有已經(jīng)終止的子進(jìn)程,那么程序?qū)⒆枞˙locking)直到有子進(jìn)程終止,因此要謹(jǐn)慎調(diào)用該函數(shù)。

銷毀僵尸進(jìn)程 2:使用 waitpid 函數(shù)

????????wait 函數(shù)會(huì)引起程序阻塞,還可以考慮調(diào)用 waitpid 函數(shù)。這是防止僵尸進(jìn)程的第二種方法,也是防止阻塞的方法:

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
/*
成功時(shí)返回終止的子進(jìn)程ID 或 0 ,失敗時(shí)返回 -1
pid: 等待終止的目標(biāo)子進(jìn)程的ID,若傳 -1,則與 wait 函數(shù)相同,可以等待任意子進(jìn)程終止
statloc: 與 wait 函數(shù)的 statloc 參數(shù)具有相同含義
options: 傳遞頭文件 sys/wait.h 聲明的常量 WNOHANG ,即使沒有終止的子進(jìn)程也不會(huì)進(jìn)入阻塞狀態(tài),而是返回 0 退出函數(shù)。
*/

?waitpid 的使用示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int status;
    pid_t pid = fork(); // 創(chuàng)建子進(jìn)程

    if (pid == 0) // 子進(jìn)程執(zhí)行代碼
    {
        sleep(15); // 用 sleep 推遲子進(jìn)程的執(zhí)行,讓其睡眠15秒
        return 24; // 子進(jìn)程返回值為24,表示子進(jìn)程正常終止
    }
    else // 父進(jìn)程執(zhí)行代碼
    {
        // 使用 waitpid 傳遞參數(shù) WNOHANG,這樣在沒有終止的子進(jìn)程時(shí),waitpid 立即返回,不會(huì)阻塞
        // 循環(huán)等待子進(jìn)程終止
        while (!waitpid(-1, &status, WNOHANG))
        {
            sleep(3); // 父進(jìn)程睡眠3秒
            puts("sleep 3 sec.");
        }

        if (WIFEXITED(status)) // 驗(yàn)證子進(jìn)程是否正常終止
            printf("Child send %d \n", WEXITSTATUS(status)); // 輸出子進(jìn)程的返回值
    }

    return 0;
}

運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????可以看出來,在 while 循環(huán)中正好執(zhí)行了 5 次。這也證明了 waitpid 函數(shù)并沒有阻塞 。

10.3 信號(hào)處理

????????我們已經(jīng)知道了進(jìn)程的創(chuàng)建及銷毀的辦法,但是還有一個(gè)問題沒有解決:子進(jìn)程究竟何時(shí)終止?調(diào)用 waitpid 函數(shù)后要無休止的等待嗎?

向操作系統(tǒng)求助:

????????子進(jìn)程終止的識(shí)別主題是操作系統(tǒng),因此,若操作系統(tǒng)能把子進(jìn)程結(jié)束的信息告訴正忙于工作的父進(jìn)程,將有助于構(gòu)建更高效的程序。

????????為了實(shí)現(xiàn)上述的功能,引入信號(hào)處理機(jī)制(Signal Handing)。此處「信號(hào)」是在特定事件發(fā)生時(shí)由操作系統(tǒng)向進(jìn)程發(fā)送的消息。另外,為了響應(yīng)該消息,執(zhí)行與消息相關(guān)的自定義操作的過程被稱為「處理」或「信號(hào)處理」。

信號(hào)與 signal 函數(shù):

????????下面進(jìn)程和操作系統(tǒng)的對話可以幫助理解信號(hào)處理。

進(jìn)程:操作系統(tǒng),如果我之前創(chuàng)建的子進(jìn)程終止,就幫我調(diào)用 zombie_handler 函數(shù)。

操作系統(tǒng):好的,如果你的子進(jìn)程終止,我就幫你調(diào)用 zombie_handler 函數(shù),你先把函數(shù)要執(zhí)行的語句寫好。????????

????????上述的對話,相當(dāng)于「注冊信號(hào)」的過程。即進(jìn)程發(fā)現(xiàn)自己的子進(jìn)程結(jié)束時(shí),請求操作系統(tǒng)調(diào)用的特定函數(shù)。該請求可以通過如下函數(shù)調(diào)用完成:?

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
/*
為了在產(chǎn)生信號(hào)時(shí)調(diào)用,返回之前注冊的函數(shù)指針
函數(shù)名: signal
參數(shù):int signo,void(*func)(int)
返回類型:參數(shù)類型為int型,返回 void 型函數(shù)指針
*/

????????調(diào)用上述函數(shù)時(shí),第一個(gè)參數(shù)為特殊情況信息,第二個(gè)參數(shù)為特殊情況下將要調(diào)用的函數(shù)的地址值(指針)。發(fā)生第一個(gè)參數(shù)代表的情況時(shí),調(diào)用第二個(gè)參數(shù)所指的函數(shù)。下面給出可以在 signal 函數(shù)中注冊的部分特殊情況和對應(yīng)的函數(shù)。

  • SIGALRM:已到通過調(diào)用 alarm 函數(shù)注冊時(shí)間
  • SIGINT:輸入 ctrl+c
  • SIGCHLD:子進(jìn)程終止

????????接下來編寫調(diào)用 signal 函數(shù)的語句完成如下請求:子進(jìn)程終止則調(diào)用 mychild 函數(shù)。

????????此時(shí) mychild 函數(shù)的參數(shù)應(yīng)為 int ,返回值類型應(yīng)為 void 。只有這樣才能成為 signal 函數(shù)的第二個(gè)參數(shù)。另外,常數(shù) SIGCHLD 定義了子進(jìn)程終止的情況,應(yīng)成為 signal 函數(shù)的第一個(gè)參數(shù)。也就是說,signal 函數(shù)調(diào)用語句如下:

signal(SIGCHLD , mychild);

接下來編寫 signal 函數(shù)的調(diào)用語句,分別完成如下兩個(gè)請求:

  1. 已到通過 alarm 函數(shù)注冊時(shí)間,請調(diào)用 timeout 函數(shù)
  2. 輸入 ctrl+c 時(shí)調(diào)用 keycontrol 函數(shù)

代表這 2 種情況的常數(shù)分別為 SIGALRM 和 SIGINT ,因此按如下方式調(diào)用 signal 函數(shù):

signal(SIGALRM , timeout);
signal(SIGINT , keycontrol);

????????以上就是信號(hào)注冊過程。注冊好信號(hào)之后,發(fā)生注冊信號(hào)時(shí)(注冊的情況發(fā)生時(shí)),操作系統(tǒng)將調(diào)用該信號(hào)對應(yīng)的函數(shù)。先介紹 alarm 函數(shù):

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
// 返回0或以秒為單位的距 SIGALRM 信號(hào)發(fā)生所剩時(shí)間

????????如果調(diào)用該函數(shù)的同時(shí)向它傳遞一個(gè)正整型參數(shù),相應(yīng)時(shí)間后(以秒為單位)將產(chǎn)生 SIGALRM 信號(hào)。若向該函數(shù)傳遞為 0 ,則之前對 SIGALRM 信號(hào)的預(yù)約將取消。如果通過改函數(shù)預(yù)約信號(hào)后未指定該信號(hào)對應(yīng)的處理函數(shù),則(通過調(diào)用 signal 函數(shù))終止進(jìn)程,不做任何處理。

? ? ? ? 示例代碼:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void timeout(int sig) //信號(hào)處理器
{
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2); //為了每隔 2 秒重復(fù)產(chǎn)生 SIGALRM 信號(hào),在信號(hào)處理器中調(diào)用 alarm 函數(shù)
}
void keycontrol(int sig) //信號(hào)處理器
{
    if (sig == SIGINT)
        puts("CTRL+C pressed");
}
int main(int argc, char *argv[])
{
    int i;
    signal(SIGALRM, timeout); //注冊信號(hào)及相應(yīng)處理器
    signal(SIGINT, keycontrol);
    alarm(2); //預(yù)約 2 秒后發(fā)生 SIGALRM 信號(hào)

    for (i = 0; i < 3; i++)
    {
        puts("wait...");
        sleep(100);
    }
    return 0;
}

? ? ? ? 運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

? ? ? ? 第一次結(jié)果是沒有任何輸入的運(yùn)行結(jié)果 。 第二次是連續(xù)鍵入3次?ctrl+c 的結(jié)果。發(fā)生信號(hào)時(shí)將喚醒由于調(diào)用 sleep 函數(shù)而進(jìn)入阻塞狀態(tài)的進(jìn)程。

????????調(diào)用函數(shù)的主體的確是操作系統(tǒng),但是進(jìn)程處于睡眠狀態(tài)時(shí)無法調(diào)用函數(shù),因此,產(chǎn)生信號(hào)時(shí),為了調(diào)用信號(hào)處理器,將喚醒由于調(diào)用 sleep 函數(shù)而進(jìn)入阻塞狀態(tài)的進(jìn)程。而且,進(jìn)程一旦被喚醒,就不會(huì)再進(jìn)入睡眠狀態(tài)。即使還未到 sleep 中規(guī)定的時(shí)間也是如此。所以上述示例運(yùn)行不到 10 秒后就會(huì)結(jié)束,連續(xù)輸入 CTRL+C 可能連一秒都不到。

利用 sigaction 函數(shù)進(jìn)行信號(hào)處理:

????????前面所學(xué)的內(nèi)容可以防止僵尸進(jìn)程,還有一個(gè)函數(shù),叫做 sigaction 函數(shù),他類似于 signal 函數(shù),而且可以完全代替后者,也更穩(wěn)定。之所以穩(wěn)定,是因?yàn)椋?strong>signal 函數(shù)在 Unix 系列的不同操作系統(tǒng)可能存在區(qū)別,但 sigaction 函數(shù)完全相同。

????????實(shí)際上現(xiàn)在很少用 signal 函數(shù)編寫程序,他只是為了保持對舊程序的兼容,下面介紹 sigaction 函數(shù),只講解可以替換 signal 函數(shù)的功能:

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
/*
成功時(shí)返回 0 ,失敗時(shí)返回 -1
act: 對于第一個(gè)參數(shù)的信號(hào)處理函數(shù)(信號(hào)處理器)信息。
oldact: 通過此參數(shù)獲取之前注冊的信號(hào)處理函數(shù)指針,若不需要?jiǎng)t傳遞 0
*/

????????聲明并初始化 sigaction 結(jié)構(gòu)體變量以調(diào)用上述函數(shù),該結(jié)構(gòu)體定義如下:?

struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
};

????????此結(jié)構(gòu)體的成員 sa_handler 保存信號(hào)處理的函數(shù)指針值(地址值)。sa_mask 和 sa_flags 的所有位初始化 0 即可。這 2 個(gè)成員用于指定信號(hào)相關(guān)的選項(xiàng)和特性,而我們的目的主要是防止產(chǎn)生僵尸進(jìn)程,故省略。

????????下面的示例是關(guān)于 sigaction 函數(shù)的使用方法:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void timeout(int sig)
{
    if (sig == SIGALRM)
        puts("Time out!");
    alarm(2);
}

int main(int argc, char *argv[])
{
    int i;
    struct sigaction act;
    act.sa_handler = timeout;    //保存函數(shù)指針
    sigemptyset(&act.sa_mask);   //將 sa_mask 成員的所有位初始化成0
    act.sa_flags = 0;            //sa_flags 同樣初始化成 0
    sigaction(SIGALRM, &act, 0); //注冊 SIGALRM 信號(hào)的處理器。

    alarm(2); //2 秒后發(fā)生 SIGALRM 信號(hào)

    for (int i = 0; i < 3; i++)
    {
        puts("wait...");
        sleep(100);
    }
    return 0;
}

? ? ? ? 運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

?????????可以發(fā)現(xiàn),結(jié)果和之前用 signal 函數(shù)的結(jié)果沒有什么區(qū)別。

利用信號(hào)處理技術(shù)消滅僵尸進(jìn)程:

????????下面利用子進(jìn)程終止時(shí)產(chǎn)生 SIGCHLD 信號(hào)這一點(diǎn),來用信號(hào)處理來消滅僵尸進(jìn)程??匆韵麓a:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void read_childproc(int sig)
{
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG);
    if (WIFEXITED(status))
    {
        printf("Removed proc id: %d \n", id);             //子進(jìn)程的 pid
        printf("Child send: %d \n", WEXITSTATUS(status)); //子進(jìn)程的返回值
    }
}

int main(int argc, char *argv[])
{
    pid_t pid;
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);

    pid = fork();
    if (pid == 0) //子進(jìn)程執(zhí)行階段
    {
        puts("Hi I'm child process");
        sleep(10);
        return 12;
    }
    else //父進(jìn)程執(zhí)行階段
    {
        printf("Child proc id: %d\n", pid);
        pid = fork();
        if (pid == 0)
        {
            puts("Hi! I'm child process");
            sleep(10);
            exit(24);
        }
        else
        {
            int i;
            printf("Child proc id: %d \n", pid);
            for (i = 0; i < 5; i++)
            {
                puts("wait");
                sleep(5);
            }
        }
    }
    return 0;
}

????????運(yùn)行結(jié)果:《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????代碼中創(chuàng)建了兩個(gè)子進(jìn)程,父進(jìn)程在創(chuàng)建子進(jìn)程后會(huì)輸出子進(jìn)程的信息,然后進(jìn)入等待狀態(tài),睡眠5秒,循環(huán)輸出"wait"。而子進(jìn)程在輸出自身信息后,睡眠10秒后返回不同的返回值。父進(jìn)程在循環(huán)等待期間,若有子進(jìn)程終止,則信號(hào)處理函數(shù) read_childproc 會(huì)被調(diào)用,輸出子進(jìn)程的PID和返回值。?

10.4 基于多任務(wù)的并發(fā)服務(wù)器

基于進(jìn)程的并發(fā)服務(wù)器模型:

????????之前的回聲服務(wù)器每次只能同事向 1 個(gè)客戶端提供服務(wù)。因此,需要擴(kuò)展回聲服務(wù)器,使其可以同時(shí)向多個(gè)客戶端提供服務(wù)。下圖是基于多進(jìn)程的回聲服務(wù)器的模型:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????從圖中可以看出,每當(dāng)有客戶端請求時(shí)(連接請求),回聲服務(wù)器都創(chuàng)建子進(jìn)程以提供服務(wù)。如果請求的客戶端有 5 個(gè),則將創(chuàng)建 5 個(gè)子進(jìn)程來提供服務(wù),為了完成這些任務(wù),需要經(jīng)過如下過程:

  • 第一階段:回聲服務(wù)器端(父進(jìn)程)通過調(diào)用 accept 函數(shù)受理連接請求
  • 第二階段:此時(shí)獲取的套接字文件描述符創(chuàng)建并傳遞給子進(jìn)程
  • 第三階段:進(jìn)程利用傳遞來的文件描述符提供服務(wù)

實(shí)現(xiàn)并發(fā)服務(wù)器:

????????

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

#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;

    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];
    if (argc != 2)
    {
        printf("Usgae : %s <port>\n", argv[0]);
        exit(1);
    }
    act.sa_handler = read_childproc; //防止僵尸進(jìn)程
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);         //注冊信號(hào)處理器,把成功的返回值給 state
    serv_sock = socket(PF_INET, SOCK_STREAM, 0); //創(chuàng)建服務(wù)端套接字
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) //分配IP地址和端口號(hào)
        error_handling("bind() error");
    if (listen(serv_sock, 5) == -1) //進(jìn)入等待連接請求狀態(tài)
        error_handling("listen() error");

    while (1)
    {
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
        if (clnt_sock == -1)
            continue;
        else
            puts("new client connected...");
        pid = fork(); //此時(shí),父子進(jìn)程分別帶有一個(gè)套接字
        if (pid == -1)
        {
            close(clnt_sock);
            continue;
        }
        if (pid == 0) //子進(jìn)程運(yùn)行區(qū)域,此部分向客戶端提供回聲服務(wù)
        {
            close(serv_sock); //關(guān)閉服務(wù)器套接字,因?yàn)閺母高M(jìn)程傳遞到了子進(jìn)程
            while ((str_len = read(clnt_sock, buf, BUFSIZ)) != 0)
                write(clnt_sock, buf, str_len);

            close(clnt_sock);
            puts("client disconnected...");
            return 0;
        }
        else
            close(clnt_sock); //通過 accept 函數(shù)創(chuàng)建的套接字文件描述符已經(jīng)復(fù)制給子進(jìn)程,因?yàn)榉?wù)器端要銷毀自己擁有的
    }
    close(serv_sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid(-1, &status, WNOHANG);
    printf("removed proc id: %d \n", pid);
}

?運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????此時(shí)的服務(wù)端支持同時(shí)給多個(gè)客戶端進(jìn)行服務(wù),每有一個(gè)客戶端連接服務(wù)端,就會(huì)多開一個(gè)子進(jìn)程,所以可以同時(shí)提供服務(wù)。?

通過 fork 函數(shù)復(fù)制文件描述符:

????????示例中給出了通過 fork 函數(shù)復(fù)制文件描述符的過程。父進(jìn)程將 2 個(gè)套接字(一個(gè)是服務(wù)端套接字另一個(gè)是客戶端套接字)文件描述符復(fù)制給了子進(jìn)程。

????????調(diào)用 fork 函數(shù)時(shí)復(fù)制父進(jìn)程的所有資源,但是套接字不是歸進(jìn)程所有的,而是歸操作系統(tǒng)所有,只是進(jìn)程擁有代表相應(yīng)套接字的文件描述符。

????????復(fù)制套接字后,同一端口將對應(yīng)多個(gè)套接字。

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

?????????如上圖所示,1 個(gè)套接字存在 2 個(gè)文件描述符時(shí),只有 2 個(gè)文件描述符都終止(銷毀)后,才能銷毀套接字。如果維持圖中的狀態(tài),即使子進(jìn)程銷毀了與客戶端連接的套接字文件描述符,也無法銷毀套接字(服務(wù)器套接字同樣如此)。因此調(diào)用 fork 函數(shù)后,要將無關(guān)緊要的套接字文件描述符關(guān)掉,如下圖所示:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

10.5 分割 TCP 的 I/O 程序

分割 I/O 的優(yōu)點(diǎn):

????????我們已經(jīng)實(shí)現(xiàn)的回聲客戶端的數(shù)據(jù)回聲方式如下:向服務(wù)器傳輸數(shù)據(jù),并等待服務(wù)器端回復(fù)。無條件等待,直到接收完服務(wù)器端的回聲數(shù)據(jù)后,才能傳輸下一批數(shù)據(jù)。? ? ?

????????傳輸數(shù)據(jù)后要等待服務(wù)器端返回的數(shù)據(jù),因?yàn)槌绦虼a中重復(fù)調(diào)用了 read 和 write 函數(shù)。只能這么寫的原因之一是,程序在 1 個(gè)進(jìn)程中運(yùn)行,現(xiàn)在可以創(chuàng)建多個(gè)進(jìn)程,因此可以分割數(shù)據(jù)收發(fā)過程。默認(rèn)分割過程如下圖所示:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????從圖中可以看出,客戶端的父進(jìn)程負(fù)責(zé)接收數(shù)據(jù),額外創(chuàng)建的子進(jìn)程負(fù)責(zé)發(fā)送數(shù)據(jù),分割后,不同進(jìn)程分別負(fù)責(zé)輸入輸出,這樣,無論客戶端是否從服務(wù)器端接收完數(shù)據(jù)都可以進(jìn)程傳輸。

分割 I/O 程序的另外一個(gè)好處是,可以提高頻繁交換數(shù)據(jù)的程序性能,如下圖所示:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程

????????下面是回聲客戶端的 I/O 分割的代碼實(shí)現(xiàn):?

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

#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);

int main(int argc, char *argv[])
{
    int sock;
    pid_t pid;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_adr;
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("connect() error!");

    pid = fork();
    if (pid == 0)
        write_routine(sock, buf);
    else
        read_routine(sock, buf);

    close(sock);
    return 0;
}

void read_routine(int sock, char *buf)
{
    while (1)
    {
        int str_len = read(sock, buf, BUF_SIZE);
        if (str_len == 0)
            return;

        buf[str_len] = 0;
        printf("Message from server: %s", buf);
    }
}
void write_routine(int sock, char *buf)
{
    while (1)
    {
        fgets(buf, BUF_SIZE, stdin);
        if (!strcmp(buf, "q\n") || !strcmp(buf, "Q\n"))
        {
            shutdown(sock, SHUT_WR); //向服務(wù)器端傳遞 EOF,因?yàn)閒ork函數(shù)復(fù)制了文件描述度,所以通過1次close調(diào)用不夠
            return;
        }
        write(sock, buf, strlen(buf));
    }
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

運(yùn)行結(jié)果:

《TCP IP網(wǎng)絡(luò)編程》第十章,《TCPIP網(wǎng)絡(luò)編程》,網(wǎng)絡(luò),tcp/ip,服務(wù)器,網(wǎng)絡(luò)編程


習(xí)題:?

1、請說明進(jìn)程變?yōu)榻┦M(jìn)程的過程以及預(yù)防措施。

????????進(jìn)程變?yōu)榻┦M(jìn)程的過程:

  1. 創(chuàng)建子進(jìn)程:當(dāng)一個(gè)進(jìn)程創(chuàng)建子進(jìn)程后,子進(jìn)程會(huì)拷貝父進(jìn)程的資源和狀態(tài),包括代碼、數(shù)據(jù)、打開的文件等。

  2. 子進(jìn)程終止:子進(jìn)程執(zhí)行完任務(wù)后,會(huì)調(diào)用 exit() 或者從 main 函數(shù)中返回一個(gè)值來終止。此時(shí),子進(jìn)程的終止?fàn)顟B(tài)和退出碼會(huì)被保存在內(nèi)核的進(jìn)程表項(xiàng)中,等待父進(jìn)程回收。

  3. 父進(jìn)程未及時(shí)回收子進(jìn)程:父進(jìn)程可能因?yàn)楦鞣N原因,無法及時(shí)處理子進(jìn)程的終止?fàn)顟B(tài),比如父進(jìn)程忙于其他任務(wù)、死鎖、阻塞在某個(gè)操作上等。在這種情況下,子進(jìn)程的進(jìn)程表項(xiàng)仍然保留在系統(tǒng)進(jìn)程表中,成為僵尸進(jìn)程。盡管子進(jìn)程已經(jīng)終止,但內(nèi)核仍然保存它的狀態(tài)信息,以便父進(jìn)程在合適的時(shí)候獲取。

????????預(yù)防措施:通過 wait 和 waitpid 函數(shù)加上信號(hào)函數(shù)寫代碼來預(yù)防。文章來源地址http://www.zghlxwxcb.cn/news/detail-605428.html

到了這里,關(guān)于《TCP IP網(wǎng)絡(luò)編程》第十章的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于UDP的服務(wù)器端/客戶端

    《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于UDP的服務(wù)器端/客戶端

    目錄 1--TCP和UDP的主要區(qū)別 2--基于 UDP 的數(shù)據(jù) I/O 函數(shù) 3--基于 UDP 的回聲服務(wù)器端/客戶端 4--UDP客戶端Socket的地址分配 5--UDP存在數(shù)據(jù)邊界 6--UDP已連接與未連接的設(shè)置 ① TCP 提供的是可靠數(shù)據(jù)傳輸服務(wù),而 UDP 提供的是不可靠數(shù)據(jù)傳輸服務(wù); ② UDP 在結(jié)構(gòu)上比 TCP 更簡潔,其不會(huì)

    2024年02月09日
    瀏覽(53)
  • Linux網(wǎng)絡(luò)編程之TCP/IP實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)服務(wù)器設(shè)計(jì)指南

    目錄 引言: 多進(jìn)程服務(wù)器 例程分享: 多線程服務(wù)器 ?例程分享: I/O多路復(fù)用服務(wù)器 select 例程分享: poll 例程分享: epoll 例程分享: 總結(jié)建議 ????????隨著互聯(lián)網(wǎng)的迅猛發(fā)展,服務(wù)器面臨著越來越多的并發(fā)請求。如何設(shè)計(jì)一個(gè)能夠高效處理大量并發(fā)請求的服務(wù)器成為

    2024年02月20日
    瀏覽(35)
  • 《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于Windows實(shí)現(xiàn)Hello Word服務(wù)器端和客戶端

    《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于Windows實(shí)現(xiàn)Hello Word服務(wù)器端和客戶端

    目錄 1--Hello Word服務(wù)器端 2--客戶端 3--編譯運(yùn)行 3-1--編譯服務(wù)器端 3-2--編譯客戶端 3-3--運(yùn)行 運(yùn)行結(jié)果:

    2024年02月10日
    瀏覽(63)
  • 《TCP IP網(wǎng)絡(luò)編程》

    《TCP IP網(wǎng)絡(luò)編程》

    ? ? ? ? 2023.6.28 正式開始學(xué)習(xí)網(wǎng)絡(luò)編程。 每一章每一節(jié)的筆記都會(huì)記錄在博客中以便復(fù)習(xí)。 ? ? ? ? 網(wǎng)絡(luò)編程又叫套接字編程。所謂網(wǎng)絡(luò)編程,就是編寫程序使兩臺(tái)連網(wǎng)的計(jì)算機(jī)相互交換數(shù)據(jù)。 為什么叫套接字編程? 我們平常將插頭插入插座上就能從電網(wǎng)中獲取電力,同

    2024年02月11日
    瀏覽(25)
  • TCP/IP網(wǎng)絡(luò)編程(三)

    TCP/IP網(wǎng)絡(luò)編程(三)

    多播(Multicast)方式的數(shù)據(jù)傳輸是 基于 UDP 完成的 。因此 ,與 UDP 服務(wù)器端/客戶端的實(shí)現(xiàn)方式非常接近。區(qū)別在于,UDP 數(shù)據(jù)傳輸以單一目標(biāo)進(jìn)行,而多播數(shù)據(jù) 同時(shí)傳遞到加入(注冊)特定組的大量主機(jī) 。換言之, 采用多播方式時(shí),可以同時(shí)向多個(gè)主機(jī)傳遞數(shù)據(jù) 。 14.1.1 多

    2024年02月03日
    瀏覽(36)
  • TCP/IP網(wǎng)絡(luò)編程(一)

    TCP/IP網(wǎng)絡(luò)編程(一)

    1.1.1 構(gòu)建打電話套接字 以電話機(jī)打電話的方式來理解套接字。 **調(diào)用 socket 函數(shù)(安裝電話機(jī))時(shí)進(jìn)行的對話:**有了電話機(jī)才能安裝電話,于是就要準(zhǔn)備一個(gè)電話機(jī),下面函數(shù)相當(dāng)于電話機(jī)的套接字。 **調(diào)用 bind 函數(shù)(分配電話號(hào)碼)時(shí)進(jìn)行的對話:**套接字同樣如此。就想

    2024年02月03日
    瀏覽(26)
  • TCP/IP網(wǎng)絡(luò)編程(二)

    TCP/IP網(wǎng)絡(luò)編程(二)

    本章將討論如何優(yōu)雅地?cái)嚅_相互連接的套接字。之前用的方法不夠優(yōu)雅是因?yàn)椋覀兪钦{(diào)用 close 或 closesocket 函數(shù)單方面斷開連接的。 TCP中的斷開連接過程比建立連接過程更重要,因?yàn)檫B接過程中一般不會(huì)出現(xiàn)大的變數(shù),但斷開過程有可能發(fā)生預(yù)想不到的情況,因此應(yīng)準(zhǔn)確掌

    2024年02月03日
    瀏覽(33)
  • 網(wǎng)絡(luò)編程——TCP/IP協(xié)議族(IP協(xié)議、TCP協(xié)議和UDP協(xié)議……)

    1、IP協(xié)議簡介 IP協(xié)議又稱 網(wǎng)際協(xié)議 特指為實(shí)現(xiàn)在一個(gè)相互連接的網(wǎng)絡(luò)系統(tǒng)上從源地址到目的地傳輸數(shù)據(jù)包(互聯(lián)網(wǎng)數(shù)據(jù)包)所提供必要功能的協(xié)議,是網(wǎng)絡(luò)層中的協(xié)議。 2、特點(diǎn) 不可靠 :它不能保證IP數(shù)據(jù)包能成功地到達(dá)它的目的地,僅提供盡力而為的傳輸服務(wù) 無連接 :IP 并不

    2024年02月13日
    瀏覽(113)
  • 《TCP IP網(wǎng)絡(luò)編程》第一章

    《TCP IP網(wǎng)絡(luò)編程》第一章

    ? ? ? ? 2023.6.28 正式開始學(xué)習(xí)網(wǎng)絡(luò)編程。 每一章每一節(jié)的筆記都會(huì)記錄在博客中以便復(fù)習(xí)。 ? ? ? ? 網(wǎng)絡(luò)編程又叫套接字編程。所謂網(wǎng)絡(luò)編程,就是編寫程序使兩臺(tái)連網(wǎng)的計(jì)算機(jī)相互交換數(shù)據(jù)。 為什么叫套接字編程? 我們平常將插頭插入插座上就能從電網(wǎng)中獲取電力,同

    2024年02月11日
    瀏覽(22)
  • 《TCP IP網(wǎng)絡(luò)編程》第六章

    《TCP IP網(wǎng)絡(luò)編程》第六章

    UDP 套接字的特點(diǎn): ????????通過寄信來說明 UDP 的工作原理,這是講解 UDP 時(shí)使用的傳統(tǒng)示例,它與 UDP 的特點(diǎn)完全相同。寄信前應(yīng)先在信封上填好寄信人和收信人的地址,之后貼上郵票放進(jìn)郵筒即可。當(dāng) 然,信件的特點(diǎn)使我們無法確認(rèn)信件是否被收到。郵寄過程中也可能

    2024年02月16日
    瀏覽(30)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包