《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容
《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容
- 《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容
- 分离 I/O 流
- 2 次 I/O 流分离
- 分离「流」的好处
- 「流」分离带来的 EOF 问题
- 文件描述符的的复制和半关闭
- 终止「流」时无法半关闭原因
- 复制文件描述符
- 复制文件描述符后「流」的分离
- 习题
- (1)下列关于FILE结构体指针和文件描述符的说法错误的是?
- (2)EOF的发送相关描述中错误的是?
《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容
分离 I/O 流
「分离 I/O 流」是一种常用表达。有 I/O 工具可区分二者,无论采用哪种方法,都可以认为是分离了 I/O 流。
2 次 I/O 流分离
之前有两种分离方法:
第一种是第 10 章的「TCP I/O 过程」分离。通过调用 fork 函数复制出一个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了 2 个文件描述符的用途,因此,这也属于「流」的分离。
第二种分离是在第 15 章。通过 2 次 fdopen 函数的调用,创建读模式 FILE 指针(FILE 结构体指针)和写模式 FILE 指针。换言之,我们分离了输入工具和输出工具,因此也可视为「流」的分离。
分离「流」的好处
首先是第 10 章「流」的分离目的:
- 通过分开输入过程(代码)和输出过程降低实现难度
- 与输入无关的输出操作可以提高速度
下面是第 15 章「流」分离的目的:
- 为了将 FILE 指针按读模式和写模式加以区分
- 可以通过区分读写模式降低实现难度
- 通过区分 I/O 缓冲提高缓冲性能
「流」分离带来的 EOF 问题
第 7 章介绍过 EOF 的传递方法和半关闭的必要性。有一个语句:
shutdown(sock,SHUT_WR);
当时说过调用 shutdown 函数的基于半关闭的 EOF 传递方法。第十章的 echo_mpclient.c 添加了上述代码,实现了半关闭。
但是还没有讲采用 fdopen 函数怎么半关闭。那么是否 可以针对输出模式的 FILE 指针调用 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];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); 这一句,完全关闭了套接字而不是半关闭。这才是这一章需要解决的问题。
文件描述符的的复制和半关闭
终止「流」时无法半关闭原因
上述服务器端程序2个FILE指针、文件描述符及套接字之间的关系:

读模式FILE指针和写模式FILE指针都是基于同一个文件描述符创建的。任意一个FILE指针调用fclose函数时都会关闭文件描述符,也就是终止套接字。

解决办法:创建FILE指针前先复制文件描述符即可。

销毁所有文件描述符后才能销毁套接字。
针对写模式FILE指针调用fclose函数时,只能销毁与该FILE指针相关的文件描述符,无法销毁套接字。

如上图所示,调用 fclose 函数候还剩下 1 个文件描述符,因此没有销毁套接字。那此时的状态是否为半关闭状态?不是!只是准备好了进入半关闭状态,而不是已经进入了半关闭状态。仔细观察,还剩下一个文件描述符。而该文件描述符可以同时进行 I/O 。因此,不但没有发送 EOF ,而且仍然可以利用文件描述符进行输出。
复制文件描述符
与调用 fork 函数不同,调用 fork 函数将复制整个进程,此处讨论的是同一进程内完成对完成描述符的复制。当然,文件描述符的值不能重复,因此各使用一个整数值。此处的复制的含义:“为了访问同一文件或套接字,创建另一个文件描述符”。
下面给出两个文件描述符的复制方法:
#include <unistd.h>int dup(int fildes);
int dup2(int fildes, int fildes2);
成功时返回复制的文件描述符,失败时返回 -1。
参数:
- fildes : 需要复制的文件描述符。
- fildes2 : 明确指定的文件描述符的整数值。向其传递大于 0 且小于进程能生成的最大文件描述符值时,该值将成为复制出的文件描述符值。
示例程序:
#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;
}
运行结果:
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 16>gcc dup.c -o dupC:\Users\81228\Documents\Program\TCP IP Project\Chapter 16>dup
fd1=3 , fd2=7
Hi~
It's nice day~ Hi~
复制文件描述符后「流」的分离
下面更改 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];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);fclose(writefp);fgets(buf, sizeof(buf), readfp);fputs(buf, stdout);fclose(readfp);return 0;
}
注意,无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态。
调用shutdown函数时,无论复制出多少文件描述符都进入半关闭状态,并发送EOF。
习题
(1)下列关于FILE结构体指针和文件描述符的说法错误的是?
a. 与FILE结构体指针相同,文件描述符也分输入描述符和输出描述符。
b. 复制文件描述符时将生成相同值的描述符,可以通过这2个描述符进行I/O。
c. 可以利用创建套接字时返回的文件描述符进行I/O ,也可以不通过文件描述符,直接通过FILE结构体指针完成。
d. 可以从文件描述符生成FILE结构体指针,而且可以利用这种FILE结构体指针进行套接字I/O。
e. 若文件描述符为读模式,则基于该描述符生成的FILE结构体指针同样是读模式;若文件描述符为写模式,则基于该描述符生成的FILE结构体指针同样是写模式。
答:a、b、e。
(2)EOF的发送相关描述中错误的是?
a. 终止文件描述符时发送EOF。
b. 即使未完全终止文件描述符,关闭输出流时也会发送EOF。
c. 如果复制文件描述符,则包括复制的文件描述符在内,所有文件描述符都终止时才会发送EOF。
d. 即使复制文件描述符,也可以通过调用shutdown函数进入半关闭状态并发送EOF。
答:a。
相关文章:
《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容
《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容 《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容分离 I/O 流2 次 I/O 流分离分离「流」的好处「流」分离带来的 EOF 问题 文件描述符的的复制和半关闭终止「流」…...
单片机学习笔记 5. 数码管静态显示
更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~ 目录 0、实现的功能 1、Keil工程 1-1 数码管显示原理 1-2 静态与动态显示 1-3 74HC573锁存器的工作原理 1-…...
ValueError: not enough values to unpack (expected 2, got 1) 解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...
java基础知识(常用类)
一、包装类(Wrapper) (1)包装类与基本数据的转换 装箱:基本类型->包装类型 拆箱:包装类型->基本类型 java5以后是自动装箱和拆箱的方式,自动装箱底层调用的是valueOf方法,比如Integer.…...
Selenium+Java(19):使用IDEA的Selenium插件辅助超快速编写Pages
前言 或是惊叹于Selenium对于IDEA的支持已经达到了这样的地步,又或是由于这个好用的小工具的入口就在那里,它已经陪伴了我这么久,而我这么久的时间却都没有发现它。在突然发现这个功能的一瞬间,真的是喜悦感爆棚,于是赶快写下了这篇文章。希望可以帮助到其他同样在做UI自动…...
决策树分类算法【sklearn/决策树分裂指标/鸢尾花分类实战】
决策树分类算法 1. 什么是决策树?2. DecisionTreeClassifier的使用(sklearn)2.1 算例介绍2.2 构建决策树并实现可视化 3. 决策树分裂指标3.1 信息熵(ID3)3.2 信息增益3.3 基尼指数(CART) 4. 代码…...
深入理解 Spring Boot 的 WebApplicationType
1. 前言 在 Spring Boot 应用程序启动过程中,WebApplicationType 是一个重要的概念,它决定了应用程序是以 Web 应用程序的形式运行还是以非 Web 应用程序的形式运行。本文将详细探讨 WebApplicationType 的工作机制及其在实际项目中的应用。 2. 什么是 WebApplicationType?…...
摄影:相机控色
摄影:相机控色 白平衡(White Balance)白平衡的作用: 白平衡的使用环境色温下相机色温下总结 白平衡偏移与包围白平衡包围 影调 白平衡(White Balance) 人眼看到的白色:会自动适应环境光线。 相…...
Python网络爬虫技术及其应用
Python网络爬虫技术及其应用 在当今数字化时代,互联网已经成为信息传播的主要渠道。海量的数据每天都在互联网上产生,这些数据对于企业决策、市场分析、科学研究等有着极其重要的价值。然而,如何高效地收集并利用这些数据成为了一个挑战。Py…...
鸿蒙学习笔记:ArkUI概述
ArkUI是构建分布式应用界面的声明式UI开发框架。组件是界面搭建最小单位,页面是最小调度分隔单位。其有诸多特征,如内置丰富多态UI组件、多样布局、多种动画及绘制能力、交互事件适配多输入设备等,还有平台API通道与两种开发范式。 JS、TS、…...
Selenium 在自动化测试中的应用
在自动化测试中,Selenium是一种非常流行的工具,它允许开发者通过编程的方式与Web浏览器进行交互,模拟用户操作,如点击按钮、填写表单、导航网页等。 1. Selenium 简介 Selenium是一个支持多种浏览器的Web自动化测试工具ÿ…...
python3 Flask应用 使用 Flask-SQLAlchemy操作MySQL数据库
一、环境搭建 下载命令: pip install flask flask-sqlalchemy pymysql 二、创建项目结构 yourProjectFolder/ |—— app.py |—— config.py |—— models.py |__ mydb.py 三、基本使用 3.1 config.py 进行数据库连接配置 import osbasedir os.path.abspat…...
Python学习——猜拳小游戏
import random player int(input(“请输入:剪刀 0,石头 1,布2”)) computer random.randint(0,2)# print(“玩家输入的是%d,电脑输入的是%d” %(player,computer)) 用于测试 if (player 0) and (computer 0) or (player 1) a…...
递归-迭代
24. 两两交换链表中的节点 Leetcode 24 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 递归解法 // 注意:cpp …...
恋爱通信史之完整性
在前面的章节中,介绍了对通信消息的加密,可以保证保密性(机密性)。虽说中间人无法解密通信消息的内容,但是可以篡改通信的消息。在接受者视角来看,是无法识别通信消息是否被篡改。因此,必须引入一种机制,保…...
Docker 容器的初始化设置
虽然现在Conntainerd 大有取代Docker作为容器运行时的趋势,但是docker还是有自己的优势在。尤其是对于开发者来讲,使用Docker 比使用 containerd 方便很多,尤其是在Docker Desktop等工具的加持下。 本文主要面向Docker的初、中级学者…...
密码编码学与网络安全(第五版)答案
2.4题: 通过如下代码分别统计一个字符的频率和三个字符的频率,"8"——"e",“;48”——“the”,英文字母的相对使用频率,猜测频率比较高的依此为),t,*,5…...
C++初阶(十四)--STL--vector的模拟实现
文章目录 一、vector的基本结构 二、默认成员函数的实现 1.构造函数 2.拷贝构造函数 3.赋值运算符重载 4. 析构函数 三、迭代器相关函数 begin和end 四、容量和大小相关函数 size capacity reserve resize empty 五、修改容器的函数 push_back pop_back insert…...
贴代码框架PasteForm特性介绍之query,linkquery
简介 PasteForm是贴代码推出的 “新一代CRUD” ,基于ABPvNext,目的是通过对Dto的特性的标注,从而实现管理端的统一UI,借助于配套的PasteBuilder代码生成器,你可以快速的为自己的项目构建后台管理端!目前管…...
高防IP如何构建安全高效的数字政务新生态
随着数字化转型浪潮的日渐汹涌,政务行业也在朝着智慧政务的方向高速迈进,提升了为民服务的整体效率。然而,凡事都有双面性,随着政务服务线上化的深入发展,网络安全威胁也日益严峻。黑客攻击、DDoS攻击、CC攻击等安全事…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
