《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.mzph.cn/news/42405.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++STL——deque容器详解

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 &#x1f4ac;文章目录 一.deque容器的基本概念二.deque容器常用操作①deque构造函数②deque元素操作③deque赋值操作④deque交换操作⑤deque大小操作⑥deque插入和删除…

直方图均衡化和自适应直方图均衡化

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 均衡化是数字图像处理中常用的一种技术&#xff0c;用于增强图像的视觉效果和对比度。&#xff0c;今天我们将实现对同一张图像的直方图均衡化和自适应直方图均衡化处理&#xff0c;学习一下两者的的基本原理和实现过程&a…

React 高阶组件(HOC)

React 高阶组件(HOC) 高阶组件不是 React API 的一部分&#xff0c;而是一种用来复用组件逻辑而衍生出来的一种技术。 什么是高阶组件 高阶组件就是一个函数&#xff0c;且该函数接受一个组件作为参数&#xff0c;并返回一个新的组件。基本上&#xff0c;这是从 React 的组成…

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…

Java IO流(一)IO基础

概述 IO流本质 I/O表示Input/Output,即数据传输过程中的输入/输出,并且输入和输出都是相对于内存来讲Java IO(输入/输出)流是Java用于处理数据读取和写入的关键组件常见的I|O介质包括 文件(输入|输出)网络(输入|输出)键盘(输出)显示器(输出)使用场景 文件拷贝&#xff08;File&…

算法竞赛入门【码蹄集新手村600题】(MT1160-1180)C语言

算法竞赛入门【码蹄集新手村600题】(MT1160-1180&#xff09;C语言 目录MT1161 N的零MT1162 数组最大公约数MT1163 孪生质数MT1164 最大数字MT1165 卡罗尔数MT1166 自守数MT1167自守数IIMT1168 阶乘数MT1169 平衡数MT1170 四叶玫瑰数MT1171 幻数MT1172 完美数字MT1173 魔数MT11…

WebGL游戏站优化实录【myshmup.com】

myshmup.com 允许在浏览器中创建 shmup&#xff08;射击&#xff09;游戏。 你可以使用具有创意通用许可证的资源或上传自己的艺术作品和声音。 创建的游戏可以在网站上发布。 该平台不需要编码&#xff0c;游戏对象的配置是在用户界面的帮助下执行的。 后端是使用Django框架开…

星际争霸之小霸王之小蜜蜂(三)--重构模块

目录 前言 一、为什么要重构模块 二、创建game_functions 三、创建update_screen() 四、修改alien_invasion模块 五、课后思考 总结 前言 前两天我们已经成功创建了窗口&#xff0c;并将小蜜蜂放在窗口的最下方中间位置&#xff0c;本来以为今天将学习控制小蜜蜂&#xff0c;结…

GPT-4一纸重洗:从97.6%降至2.4%的巨大挑战

斯坦福大学和加州大学伯克利分校合作进行的一项 “How Is ChatGPTs Behavior Changing Over Time?” 研究表明&#xff0c;随着时间的推移&#xff0c;GPT-4 的响应能力非但没有提高&#xff0c;反而随着语言模型的进一步更新而变得更糟糕。 研究小组评估了 2023 年 3 月和 20…

win10安装mysql和c++读取调用举例

一、下载mysql8.rar解压到C盘(也可以解压到其他位置) 在系统环境变量添加JAVA_HOMEC:\myslq8&#xff0c;并在path中添加%JAVA_HOME%\bin; 二、以管理员身份进入命令窗口 三、修改配置文件指定安装路径和数据库的存放路径 四、键入如下命令初始化并启动mysql服务,然后修改登录…

TDD(测试驱动开发)?

01、前言 很早之前&#xff0c;曾在网络上见到过 TDD 这 3 个大写的英文字母&#xff0c;它是 Test Driven Development 这三个单词的缩写&#xff0c;也就是“测试驱动开发”的意思——听起来很不错的一种理念。 其理念主要是确保两件事&#xff1a; 确保所有的需求都能被照…

macOS Ventura 13.5.1(22G90)发布(附黑/白苹果系统镜像地址)

系统镜像下载&#xff1a;百度&#xff1a;黑果魏叔 系统介绍 黑果魏叔 8 月 18 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.5.1 更新&#xff08;内部版本号&#xff1a;22G90&#xff09;&#xff0c;本次更新距离上次发布隔了 24 天。 本次更新重点修复了…

Redis 缓存过期及删除

一、Redis缓存过期策略 物理内存达到上限后&#xff0c;像磁盘空间申请虚拟内存(硬盘与内存的swap),甚至崩溃。 内存与硬盘交换 (swap) 虚拟内存&#xff0c;频繁I0 性能急剧下降&#xff0c;会造成redis内存急剧下降&#xff1b; 一般设置物理内存的3/4&#xff0c;在redis…

超分辨率地震速度模型

文献分享 1. Multitask Learning for Super-Resolution 原题目&#xff1a;Multitask Learning for Super-Resolution of Seismic Velocity Model 全波形反演&#xff08;FWI&#xff09;是估算地下速度模型的强大工具。与传统反演策略相比&#xff0c;FWI充分利用了地震波的…

typedef

t y p e d e f typedef typedef 声明&#xff0c;简称typedef&#xff0c;是创建现有类型的新名字。 比如&#xff1a; #include <bits/stdc.h> using namespace std; typedef long long ll; int main() {ll n;scanf("%lld",&n);printf("%lld"…