第 16 章 關(guān)于 I/O 流分離的其他內(nèi)容
?
16.1 分離 I/O 流
????????「分離 I/O 流」是一種常用表達。有 I/O 工具可區(qū)分二者,無論采用哪種方法,都可以認為是分離了 I/O 流。
2次 I/O 流分離:
- 第一種是第 10 章的「TCP I/O 過程」分離。通
shutdown(sock,SHUT_WR);
- 第二種分離是在第 15 章。通過 2 次 fdopen 函數(shù)的調(diào)用,創(chuàng)建讀模式 FILE 指針(FILE 結(jié)構(gòu)體指針)和寫模式 FILE 指針。換言之,我們分離了輸入工具和輸出工具,因此也可視為「流」的分離。下面是分離的理由。
分離「流」的好處:
????????首先是第 10 章「流」的分離目的:
- 通過分開輸入過程(代碼)和輸出過程降低實現(xiàn)難度
- 與輸入無關(guān)的輸出操作可以提高速度
????????下面是第 15 章「流」分離的目的:
- 為了將 FILE 指針按讀模式和寫模式加以區(qū)分
- 可以通過區(qū)分讀寫模式降低實現(xiàn)難度
- 通過區(qū)分 I/O 緩沖提高緩沖性能
「流」分離帶來的 EOF 問題:
????????第 7 章介紹過 EOF 的傳遞方法和半關(guān)閉的必要性。有一個語句:
shutdown(sock,SHUT_WR);
????????當(dāng)時說過調(diào)用 shutdown 函數(shù)的基于半關(guān)閉的 EOF 傳遞方法。第十章添加了半關(guān)閉的相關(guān)代碼。但是還沒有講采用 fdopen 函數(shù)怎么半關(guān)閉。那么是否是通過 fclose 函數(shù)關(guān)閉流呢?我們先試試:
????????服務(wù)端代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
int 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 1024
int 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;
}
運行結(jié)果:
????????
????????從運行結(jié)果可以看出,服務(wù)端最終沒有收到客戶端發(fā)送的信息。
????????原因是:服務(wù)端代碼的?fclose(writefp);
?這一句,完全關(guān)閉了套接字而不是半關(guān)閉。這才是這一章需要解決的問題。
16.2 文件描述符的的復(fù)制和半關(guān)閉
終止「流」時無法半關(guān)閉原因:
????????下面的圖描述的是服務(wù)端代碼中的兩個FILE 指針、文件描述符和套接字中的關(guān)系:
????????從圖中可以看到,兩個指針都是基于同一文件描述符創(chuàng)建的。因此,針對于任何一個 FILE 指針調(diào)用 fclose 函數(shù)都會關(guān)閉文件描述符,如圖所示:
????????那如何進入可以進入但是無法輸出的半關(guān)閉狀態(tài)呢?如下圖所示:
????????只需要創(chuàng)建 FILE 指針前先復(fù)制文件描述符即可。復(fù)制后另外創(chuàng)建一個文件描述符,然后利用各自的文件描述符生成讀模式的 FILE 指針和寫模式的 FILE 指針。這就為半關(guān)閉創(chuàng)造好了環(huán)境,因為套接字和文件描述符具有如下關(guān)系:?
????????銷毀所有文件描述符候才能銷毀套接字。
????????也就是說,針對寫模式 FILE 指針調(diào)用 fclose 函數(shù)時,只能銷毀與該 FILE 指針相關(guān)的文件描述符,無法銷毀套接字,如下圖:
????????那么調(diào)用 fclose 函數(shù)候還剩下 1 個文件描述符,因此沒有銷毀套接字。那此時的狀態(tài)是否為半關(guān)閉狀態(tài)?不是!只是準(zhǔn)備好了進入半關(guān)閉狀態(tài),而不是已經(jīng)進入了半關(guān)閉狀態(tài)。仔細觀察,還剩下一個文件描述符。而該文件描述符可以同時進行 I/O 。因此,不但沒有發(fā)送 EOF ,而且仍然可以利用文件描述符進行輸出。?
復(fù)制文件描述符:
????????與調(diào)用 fork 函數(shù)不同,調(diào)用 fork 函數(shù)將復(fù)制整個進程,此處討論的是同一進程內(nèi)完成對描述符的復(fù)制。如圖:
????????復(fù)制完成后,兩個文件描述符都可以訪問文件,但是編號不同。?
dup 和 dup2:
????????下面給出文件描述符的復(fù)制方法:
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
/*
成功時返回復(fù)制的文件描述符,失敗時返回 -1
fildes : 需要復(fù)制的文件描述符
fildes2 : 明確指定的文件描述符的整數(shù)值。
*/
????????dup2 函數(shù)明確指定復(fù)制的文件描述符的整數(shù)值。向其傳遞大于 0 且小于進程能生成的最大文件描述符值時,該值將成為復(fù)制出的文件描述符值。下面是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); //復(fù)制文件描述符 1
cfd2 = dup2(cfd1, 7); //再次復(fù)制文件描述符,定為數(shù)值 7
printf("fd1=%d , fd2=%d \n", cfd1, cfd2);
write(cfd1, str1, sizeof(str1));
write(cfd2, str2, sizeof(str2));
close(cfd1);
close(cfd2); //終止復(fù)制的文件描述符,但是仍有一個文件描述符
write(1, str1, sizeof(str1));
close(1);
write(1, str2, sizeof(str2)); //無法完成輸出
return 0;
}
? ? ? ? ?運行結(jié)果:
????????復(fù)制文件描述符后「流」的分離?:
????????下面更改sep_clnt.c和sep_serv.c???可以使得讓它正常工作,正常工作是指通過服務(wù)器的半關(guān)閉狀態(tài)接收客戶端最后發(fā)送的字符串。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
int 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"); //復(fù)制文件描述符
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 產(chǎn)生的文件描述符使用 shutdown 進入半關(guān)閉狀態(tài)
fclose(writefp);
fgets(buf, sizeof(buf), readfp);
fputs(buf, stdout);
fclose(readfp);
return 0;
}
? ? ? ? 運行結(jié)果:
?文章來源:http://www.zghlxwxcb.cn/news/detail-647247.html
? ? ? ? ?運行結(jié)果證明了 服務(wù)器端在半關(guān)閉狀態(tài)下向客戶端發(fā)送了EOF。文章來源地址http://www.zghlxwxcb.cn/news/detail-647247.html
到了這里,關(guān)于《TCP IP網(wǎng)絡(luò)編程》第十六章的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!