《TCP IP網絡編程》第十六章

第 16 章 關于 I/O 流分離的其他內容

?

16.1 分離 I/O 流

????????「分離 I/O 流」是一種常用表達。有 I/O 工具可區分二者,無論采用哪種方法,都可以認為是分離了 I/O 流。

2次 I/O 流分離:

  • 第一種是第 10 章的「TCP I/O 過程」分離。通
    shutdown(sock,SHUT_WR);
    過調用 fork 函數復制出一個文件描述符,以區分輸入和輸出中使用的文件描述符。雖然文件描述符本身不會根據輸入和輸出進行區分,但我們分開了 2 個文件描述符的用途,因此,這也屬于「流」的分離。
  • 第二種分離是在第 15 章。通過 2 次 fdopen 函數的調用,創建讀模式 FILE 指針(FILE 結構體指針)和寫模式 FILE 指針。換言之,我們分離了輸入工具和輸出工具,因此也可視為「流」的分離。下面是分離的理由。

分離「流」的好處:

????????首先是第 10 章「流」的分離目的:

  • 通過分開輸入過程(代碼)和輸出過程降低實現難度
  • 與輸入無關的輸出操作可以提高速度

????????下面是第 15 章「流」分離的目的:

  • 為了將 FILE 指針按讀模式和寫模式加以區分
  • 可以通過區分讀寫模式降低實現難度
  • 通過區分 I/O 緩沖提高緩沖性能

「流」分離帶來的 EOF 問題:

????????第 7 章介紹過 EOF 的傳遞方法和半關閉的必要性。有一個語句:

shutdown(sock,SHUT_WR);

????????當時說過調用 shutdown 函數的基于半關閉的 EOF 傳遞方法。第十章添加了半關閉的相關代碼。但是還沒有講采用 fdopen 函數怎么半關閉。那么是否是通過 fclose 函數關閉流呢?我們先試試:

????????服務端代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024int main(int argc, char *argv[])
{int serv_sock, clnt_sock;FILE *readfp;FILE *writefp;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;char buf[BUF_SIZE] = {0,};serv_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 = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));listen(serv_sock, 5);clnt_adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);readfp = fdopen(clnt_sock, "r");writefp = fdopen(clnt_sock, "w");fputs("FROM SERVER: Hi~ client? \n", writefp);fputs("I love all of the world \n", writefp);fputs("You are awesome! \n", writefp);fflush(writefp);fclose(writefp);fgets(buf, sizeof(buf), readfp);fputs(buf, stdout);fclose(readfp);return 0;
}

? ? ? ? 客戶端代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024int main(int argc, char *argv[])
{int sock;char buf[BUF_SIZE];struct sockaddr_in serv_addr;FILE *readfp;FILE *writefp;sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));readfp = fdopen(sock, "r");writefp = fdopen(sock, "w");while (1){if (fgets(buf, sizeof(buf), readfp) == NULL)break;fputs(buf, stdout);fflush(stdout);}fputs("FROM CLIENT: Thank you \n", writefp);fflush(writefp);fclose(writefp);fclose(readfp);return 0;
}

運行結果:

????????

????????從運行結果可以看出,服務端最終沒有收到客戶端發送的信息。

????????原因是:服務端代碼的?fclose(writefp);?這一句,完全關閉了套接字而不是半關閉。這才是這一章需要解決的問題。

16.2 文件描述符的的復制和半關閉

終止「流」時無法半關閉原因:

????????下面的圖描述的是服務端代碼中的兩個FILE 指針、文件描述符和套接字中的關系:

????????從圖中可以看到,兩個指針都是基于同一文件描述符創建的。因此,針對于任何一個 FILE 指針調用 fclose 函數都會關閉文件描述符,如圖所示:

????????那如何進入可以進入但是無法輸出的半關閉狀態呢?如下圖所示:

????????只需要創建 FILE 指針前先復制文件描述符即可。復制后另外創建一個文件描述符,然后利用各自的文件描述符生成讀模式的 FILE 指針和寫模式的 FILE 指針。這就為半關閉創造好了環境,因為套接字和文件描述符具有如下關系:?

????????銷毀所有文件描述符候才能銷毀套接字。

????????也就是說,針對寫模式 FILE 指針調用 fclose 函數時,只能銷毀與該 FILE 指針相關的文件描述符,無法銷毀套接字,如下圖:

????????那么調用 fclose 函數候還剩下 1 個文件描述符,因此沒有銷毀套接字。那此時的狀態是否為半關閉狀態?不是!只是準備好了進入半關閉狀態,而不是已經進入了半關閉狀態。仔細觀察,還剩下一個文件描述符。而該文件描述符可以同時進行 I/O 。因此,不但沒有發送 EOF ,而且仍然可以利用文件描述符進行輸出。?

復制文件描述符:

????????與調用 fork 函數不同,調用 fork 函數將復制整個進程,此處討論的是同一進程內完成對描述符的復制。如圖:

????????復制完成后,兩個文件描述符都可以訪問文件,但是編號不同。?

dup 和 dup2:

????????下面給出文件描述符的復制方法:

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
/*
成功時返回復制的文件描述符,失敗時返回 -1
fildes : 需要復制的文件描述符
fildes2 : 明確指定的文件描述符的整數值。
*/

????????dup2 函數明確指定復制的文件描述符的整數值。向其傳遞大于 0 且小于進程能生成的最大文件描述符值時,該值將成為復制出的文件描述符值。下面是dup的代碼示例:

#include <stdio.h>
#include <unistd.h>int main(int argc, char *argv[])
{int cfd1, cfd2;char str1[] = "Hi~ \n";char str2[] = "It's nice day~ \n";cfd1 = dup(1);        //復制文件描述符 1cfd2 = dup2(cfd1, 7); //再次復制文件描述符,定為數值 7printf("fd1=%d , fd2=%d \n", cfd1, cfd2);write(cfd1, str1, sizeof(str1));write(cfd2, str2, sizeof(str2));close(cfd1);close(cfd2); //終止復制的文件描述符,但是仍有一個文件描述符write(1, str1, sizeof(str1));close(1);write(1, str2, sizeof(str2)); //無法完成輸出return 0;
}

? ? ? ? ?運行結果:

????????復制文件描述符后「流」的分離?:

????????下面更改sep_clnt.c和sep_serv.c???可以使得讓它正常工作,正常工作是指通過服務器的半關閉狀態接收客戶端最后發送的字符串。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024int main(int argc, char *argv[])
{int serv_sock, clnt_sock;FILE *readfp;FILE *writefp;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;char buf[BUF_SIZE] = {0,};serv_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 = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));listen(serv_sock, 5);clnt_adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);readfp = fdopen(clnt_sock, "r");writefp = fdopen(dup(clnt_sock), "w"); //復制文件描述符fputs("FROM SERVER: Hi~ client? \n", writefp);fputs("I love all of the world \n", writefp);fputs("You are awesome! \n", writefp);fflush(writefp);shutdown(fileno(writefp), SHUT_WR); //對 fileno 產生的文件描述符使用 shutdown 進入半關閉狀態fclose(writefp);fgets(buf, sizeof(buf), readfp);fputs(buf, stdout);fclose(readfp);return 0;
}

? ? ? ? 運行結果:

?

? ? ? ? ?運行結果證明了 服務器端在半關閉狀態下向客戶端發送了EOF。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42405.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42405.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42405.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++STL——deque容器詳解

縱有疾風起&#xff0c;人生不言棄。本文篇幅較長&#xff0c;如有錯誤請不吝賜教&#xff0c;感謝支持。 &#x1f4ac;文章目錄 一.deque容器的基本概念二.deque容器常用操作①deque構造函數②deque元素操作③deque賦值操作④deque交換操作⑤deque大小操作⑥deque插入和刪除…

el-form組件相關的一些基礎使用

el-checkbox 01.description 多選單選框 02.場景舉例 需要對每一條數據展示她的某些狀態是否存在 03.代碼展示 <el-checkbox v-model"query.isAutoAccptncsign" true-label1 false-label0 :disabled"ifReview?true:false">自動發起承兌應答</…

直方圖均衡化和自適應直方圖均衡化

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 均衡化是數字圖像處理中常用的一種技術&#xff0c;用于增強圖像的視覺效果和對比度。&#xff0c;今天我們將實現對同一張圖像的直方圖均衡化和自適應直方圖均衡化處理&#xff0c;學習一下兩者的的基本原理和實現過程&a…

MFC中的窗體繪制事件函數:OnCtlColor、OnPaint、OnNcPaint、OnDrawItem、OnEraseBkgnd、OnDraw

文章目錄 CWnd::OnCtlColorCWnd::OnPaintCWnd::OnNcPaintCWnd::OnDrawItemCWnd::OnEraseBkgndCWnd::InvalidateRectCView::OnDraw 參考&#xff1a;https://learn.microsoft.com/ CWnd::OnCtlColor 即將繪制子控件時&#xff0c;框架會調用此成員函數。 afx_msg HBRUSH OnCt…

React 高階組件(HOC)

React 高階組件(HOC) 高階組件不是 React API 的一部分&#xff0c;而是一種用來復用組件邏輯而衍生出來的一種技術。 什么是高階組件 高階組件就是一個函數&#xff0c;且該函數接受一個組件作為參數&#xff0c;并返回一個新的組件。基本上&#xff0c;這是從 React 的組成…

Mongodb 更新集合的方法到底有幾種 (中) ?

更新方法 Mongodb 使用以下幾種方法來更新文檔 &#xff0c; Mongodb V5.0 使用 mongosh 客戶端&#xff1a; db.collection.updateOne(<filter>, <update>, <options>) db.collection.updateMany(<filter>, <update>, <options>) db.c…

docker 安裝elasticsearch、kibana

下載es鏡像 docker pull elasticsearch 啟動es容器 docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" -e ES_JAVA_OPTS"-Xms512m -Xmx512m" -d elasticsearch 驗證es界面訪問 ?????http://節點ip:9200/ ?…

client-go實戰之十二:選主(leader-election)

歡迎訪問我的GitHub 這里分類和匯總了欣宸的全部原創(含配套源碼)&#xff1a;https://github.com/zq2599/blog_demos 本篇概覽 本文是《client-go實戰》系列的第十二篇&#xff0c;又有一個精彩的知識點在本章呈現&#xff1a;選主(leader-election)在解釋什么是選主之前&…

【自用】云服務器 docker 環境下 HomeAssistant 安裝 HACS 教程

一、進入 docker 中的 HomeAssistant 1.查找 HomeAssistant 的 CONTAINER ID 連接上云服務器&#xff08;宿主機&#xff09;后&#xff0c;終端內進入 root &#xff0c;輸入&#xff1a; docker ps找到了 docker 的 container ID 2.config HomeAssistant 輸入下面的命令&…

修改el-table行懸停狀態的背景顏色

.content:deep().el-table tr:hover>td {background-color: #f5f5f5 !important; /* 設置懸停時的背景顏色 */ }/*這一點很重要&#xff0c;否則可能會導致hover行時操作列還是原來的背景色*/ .content:deep().el-table__body tr.hover-row>td{background-color: #f5f5f5…

使用Nacos配置中心動態管理Spring Boot應用配置

&#x1f337;&#x1f341; 博主貓頭虎 帶您 Go to New World.?&#x1f341; &#x1f984; 博客首頁——貓頭虎的博客&#x1f390; &#x1f433;《面試題大全專欄》 文章圖文并茂&#x1f995;生動形象&#x1f996;簡單易學&#xff01;歡迎大家來踩踩~&#x1f33a; &a…

Linux權限系列--給普通用戶添加某個命令的sudo權限

原文網址&#xff1a;Linux權限系列--給普通用戶添加某個命令的sudo權限_IT利刃出鞘的博客-CSDN博客 簡介 說明 本文介紹Linux系統如何給普通用戶添加某個命令的sudo權限。 使用場景 普通開發者可能需要sudo的命令&#xff1a; apt-get&#xff08;經常要安裝軟件&#x…

【Vue2】---->VueX 3 核心概念

官網&#xff1a; Vuex 是什么&#xff1f; | Vuex (vuejs.org) 目錄 介紹 1、安裝 2、新建 store/index.js 專門存放 vuex 3、 在 main.js 中導入掛載到 Vue 實例上 核心概念 1、核心概念 -state 狀態 1、訪問Vuex中的數據 2、通過$store訪問的語法 3、通過輔助函…

Java IO流(一)IO基礎

概述 IO流本質 I/O表示Input/Output,即數據傳輸過程中的輸入/輸出,并且輸入和輸出都是相對于內存來講Java IO(輸入/輸出)流是Java用于處理數據讀取和寫入的關鍵組件常見的I|O介質包括 文件(輸入|輸出)網絡(輸入|輸出)鍵盤(輸出)顯示器(輸出)使用場景 文件拷貝&#xff08;File&…

Python自帶的IDLE有什么用

在Python的官方解釋器中&#xff0c;自帶了一個名為IDLE(Interactive DeveLopment Environment)的集成開發環境。 一、簡化代碼調試過程 很多初學者在編寫Python代碼時&#xff0c;經常會遇到一些問題需要調試。而在IDLE中&#xff0c;我們可以通過設置斷點、單步調試等方法&…

算法競賽入門【碼蹄集新手村600題】(MT1160-1180)C語言

算法競賽入門【碼蹄集新手村600題】(MT1160-1180&#xff09;C語言 目錄MT1161 N的零MT1162 數組最大公約數MT1163 孿生質數MT1164 最大數字MT1165 卡羅爾數MT1166 自守數MT1167自守數IIMT1168 階乘數MT1169 平衡數MT1170 四葉玫瑰數MT1171 幻數MT1172 完美數字MT1173 魔數MT11…

es線上處理命令記錄

常用命令 搜索 GET _search {"query": {"match_all": {}} }獲取全部模版 GET _index_template GET _index_template/yst_crawler_template獲取全部索引 GET /_cat/indices?v 獲取當前mapping GET /yst_crawler/_mapping創建一個mapping PUT /yst_c…

WebGL游戲站優化實錄【myshmup.com】

myshmup.com 允許在瀏覽器中創建 shmup&#xff08;射擊&#xff09;游戲。 你可以使用具有創意通用許可證的資源或上傳自己的藝術作品和聲音。 創建的游戲可以在網站上發布。 該平臺不需要編碼&#xff0c;游戲對象的配置是在用戶界面的幫助下執行的。 后端是使用Django框架開…

機器學習筆記 - 使用 ResNet-50 和余弦相似度的基于圖像的推薦系統

一、簡述 這里的代碼主要是基于圖像的推薦系統,該系統利用 ResNet-50 深度學習模型作為特征提取器,并采用余弦相似度來查找給定輸入圖像的最相似嵌入。 該系統旨在根據所提供圖像的視覺內容為用戶提供個性化推薦。 二、所需環境 Python 3.x tensorflow ==2.5.0 numpy==1.21.…

星際爭霸之小霸王之小蜜蜂(三)--重構模塊

目錄 前言 一、為什么要重構模塊 二、創建game_functions 三、創建update_screen() 四、修改alien_invasion模塊 五、課后思考 總結 前言 前兩天我們已經成功創建了窗口&#xff0c;并將小蜜蜂放在窗口的最下方中間位置&#xff0c;本來以為今天將學習控制小蜜蜂&#xff0c;結…