TCP/IP网络编程——关于 I/O 流分离的其他内容

news2024/10/7 18:27:57

完整版文章请参考:
TCP/IP网络编程完整版文章

文章目录

    • 第 16 章 关于 I/O 流分离的其他内容
      • 16.1 分离 I/O 流
        • 16.1.1 2次 I/O 流分离
        • 16.1.2 分离「流」的好处
        • 16.1.3 「流」分离带来的 EOF 问题
      • 16.2 文件描述符的的复制和半关闭
        • 16.2.1 终止「流」时无法半关闭原因
        • 16.2.2 复制文件描述符
        • 16.2.3 dup 和 dup2
        • 16.2.4 复制文件描述符后「流」的分离

第 16 章 关于 I/O 流分离的其他内容

16.1 分离 I/O 流

「分离 I/O 流」是一种常用表达。有 I/O 工具可区分二者,无论采用哪种方法,都可以认为是分离了 I/O 流。

16.1.1 2次 I/O 流分离

之前有两种分离方法:

  • 第一种是第 10 章的「TCP I/O 过程」分离。通过调用 fork 函数复制出一个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了 2 个文件描述符的用途,因此,这也属于「流」的分离。
  • 第二种分离是在第 15 章。通过 2 次 fdopen 函数的调用,创建读模式 FILE 指针(FILE 结构体指针)和写模式 FILE 指针。换言之,我们分离了输入工具和输出工具,因此也可视为「流」的分离。下面是分离的理由。

16.1.2 分离「流」的好处

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

  • 通过分开输入过程(代码)和输出过程降低实现难度
  • 与输入无关的输出操作可以提高速度

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

  • 为了将 FILE 指针按读模式和写模式加以区分
  • 可以通过区分读写模式降低实现难度
  • 通过区分 I/O 缓冲提高缓冲性能

16.1.3 「流」分离带来的 EOF 问题

第 7 章介绍过 EOF 的传递方法和半关闭的必要性。有一个语句:

shutdown(sock,SHUT_WR);

当时说过调用 shutdown 函数的基于半关闭的 EOF 传递方法。第十章的 echo_mpclient.c 添加了半关闭的相关代码。但是还没有讲采用 fdopen 函数怎么半关闭。那么是否是通过 fclose 函数关闭流呢?我们先试试

下面是服务端和客户端码:

sep_clnt.c 程序:

#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;
}

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 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;
}

运行结果:

在这里插入图片描述

从运行结果可以看出,服务端最终没有收到客户端发送的信息。那么这是什么原因呢?

原因是:服务端代码的 fclose(writefp); 这一句,完全关闭了套接字而不是半关闭。这才是这一章需要解决的问题

16.2 文件描述符的的复制和半关闭

16.2.1 终止「流」时无法半关闭原因

下面的图描述的是服务端代码中的两个FILE 指针、文件描述符和套接字中的关系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUdRH2ss-1677034513269)(null)]

从图中可以看到,两个指针都是基于同一文件描述符创建的。因此,针对于任何一个 FILE 指针调用 fclose 函数都会关闭文件描述符,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jXtHjXJZ-1677034513649)(null)]

从图中看到,销毁套接字时再也无法进行数据交换。那如何进入可以进入但是无法输出的半关闭状态呢?如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trKsbqTS-1677034513366)(null)]

只需要创建 FILE 指针前先复制文件描述符即可。复制后另外创建一个文件描述符,然后利用各自的文件描述符生成读模式的 FILE 指针和写模式的 FILE 指针。这就为半关闭创造好了环境,因为套接字和文件描述符具有如下关系:

销毁所有文件描述符候才能销毁套接字

也就是说,针对写模式 FILE 指针调用 fclose 函数时,只能销毁与该 FILE 指针相关的文件描述符,无法销毁套接字,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KwnJWYXm-1677034513449)(null)]

那么调用 fclose 函数候还剩下 1 个文件描述符,因此没有销毁套接字。那此时的状态是否为半关闭状态?不是!只是准备好了进入半关闭状态,而不是已经进入了半关闭状态。仔细观察,还剩下一个文件描述符。而该文件描述符可以同时进行 I/O 。因此,不但没有发送 EOF ,而且仍然可以利用文件描述符进行输出。

16.2.2 复制文件描述符

与调用 fork 函数不同,调用 fork 函数将复制整个进程,此处讨论的是同一进程内完成对完成描述符的复制。如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4qDcg9y-1677034513546)(null)]

复制完成后,两个文件描述符都可以访问文件,但是编号不同。

16.2.3 dup 和 dup2

下面给出两个函数原型:

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

dup2 函数明确指定复制的文件描述符的整数值。向其传递大于 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);        //复制文件描述符 1
    cfd2 = dup2(cfd1, 7); //再次复制文件描述符,定为数值 7

    printf("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;
}

编译运行:

在这里插入图片描述

16.2.4 复制文件描述符后「流」的分离

下面更改 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 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"); //复制文件描述符

    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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362776.html

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

相关文章

Python 之 Pandas 时间函数 time 、datetime 模块和时间处理基础

文章目录一、time 模块1、时间格式转换图2. struct_time 元组元素结构3. format time 结构化表示二、datetime 模块1. date类2. 方法和属性3. datetime 类三、timedelta 类的时间加减四、时间处理基础Python 中提供了对时间日期的多种多样的处理方式&#xff0c;主要是在 time …

Spring架构篇--2.4 远程通信基础--Socket通信

前言&#xff1a;通信中我们常常建立socket 通过其tcp完成通信&#xff1b; 1 Socket 介绍&#xff1a; 所谓socket 通常也称作”套接字“&#xff0c;用于描述IP地址和端口&#xff0c;是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求&#…

四色菊皇家大学-BCG U2T产品宣传活动

BCG U2T产品的宣传和介绍。 2023年2月16日&#xff0c;四色菊皇家大学艺术文化中心&#xff0c;高等教育、科学、研究和创新部长Dnuch Tantodtit博士主持了BCG U2T产品的市场推广项目。四色菊皇家大学校长Saksit Anganaphanayakorn博士&#xff0c;以及U2T项目的管理人员、教职…

Ubuntu16.04使用apache创建个人用户主页并添加口令认证

文章目录一.安装apache二、apache文件和目录简述2.1 网站数据目录2.2 Apache配置文件三、创建个人用户主页3.1 开启个人用户主页功能3.2 建立目录和首页面3.3 开启模块3.4 测试四、添加口令认证4.1 生成密码数据库4.2 修改配置文件一.安装apache 创建虚拟机&#xff0c;保持默…

代码随想录【Day21】| 530. 二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236. 二叉树的最近公共祖先

530. 二叉搜索树的最小绝对差 题目链接 题目描述&#xff1a; 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#xff1a; 提示&#xff1a;树中至少有 2 个节点。 难点&#xff1a; 解答错误&#xff01;仅考虑了…

2023年软件测试工程师怎样跳槽,才能越跳越值钱?

2023年就业难&#xff1f;可那个转行干软件测试的小哥哥才刚拿到2W薪资的offer&#xff0c;紧接着又跳槽去了大厂 作为软件测试工程师怎样跳槽才能越跳越值钱呢&#xff1f; 把控好跳槽频次 我们在编写简历的时候&#xff0c;总想尽可能展示出自己的技能&#xff0c;但是简历上…

【基础算法】数的范围

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

尚医通 (二十一)预约下单

目录一、预约下单功能(一)1、需求2、搭建订单模块3、封装Feign调用获取就诊人接口4、封装Feign调用获取排班下单信息接口二、预约下单功能(二)1、实现生成订单接口三、预约下单功能(三)四、预约下单功能(四)1、生成订单后处理逻辑-封装短信接口2、生成订单后处理逻辑-更新排班数…

Allegro误删器件位号如何快速刷新回来操作指导

Allegro误删器件位号如何快速刷新回来操作指导 在用Allegro做PCB设计的时候,有时会因为误操作,把需要的丝印位号删除了,如果想把位号复原回来,可以把当前器件删除,再重新放置即可。 下面介绍在不删除器件的情况下也能快速刷新回来的方法 如下图 误操作前 误操作后,位号…

Vue3+Ts+Vite开发插件并发布到npm

依赖版本信息如下&#xff1a; "vue": "^3.2.45""typescript": "~4.7.4""vite": "^4.0.0""less": "^4.1.3""terser": "^5.16.4"npm: 8.1.0node: 16.13.0 目标&#xf…

Spring AOP(AOP概念、组成、Spring AOP实现及实现原理)

文章目录1. Spring AOP 是什么2. 为什么要用 AOP3. 怎么学 Spring AOP4. AOP 组成5. Spring AOP 实现5.1 添加 Spring AOP 框架支持5.2 定义切面和切点5.3 实现通知方法5.4 使⽤ AOP 统计 UserController 每个⽅法的执⾏时间 StopWatch5.4 切点表达式说明 AspectJ6. Spring AOP…

【SPSS】基础图形的绘制(条形图、折线图、饼图、箱图)详细操作过程

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Qt-FFmpeg开发-实现录屏功能(10)

#音视频/FFmpeg #Qt Qt-FFmpeg开发-实现录屏功能&#x1f4ac; 文章目录Qt-FFmpeg开发-实现录屏功能&#x1f4ac;1、概述&#x1f4a5;2、实现效果&#x1f4a8;3、FFmpeg录屏代码流程&#x1f441;️‍&#x1f5e8;️4、主要代码&#x1f919;5、完整源代码&#x1f90f;更…

Doris单机部署

文章目录1. 前言2. 安装3. 启动4. 使用1. 前言 Apache Doris 是一款现代 MPP (Massively Parallel Processing大规模并行处理) 的分布式 SQL 分析数据库&#xff0c;所谓分析数据库就是将其数据集分布在许多机器或节点上&#xff0c;以处理大量数据&#xff0c;采用 Apache 2.0…

几十亿工单表,查询优化案例

前言: 之前在某大型保险公司担任技术经理&#xff0c;负责优化话务系统模块&#xff0c;由于系统已经运行10年之久&#xff0c;尤其在话务系统中&#xff0c;沉积了几十亿的话务信息表&#xff0c;业务人员反馈&#xff0c;话务系统历史数据查询部分已经完全查询不动&#xff0…

Spring Cloud Nacos源码讲解(二)- Nacos客户端服务注册源码分析

Nacos客户端服务注册源码分析 服务注册信息 ​ 我们从Nacos-Client开始说起&#xff0c;那么说到客户端就涉及到服务注册&#xff0c;我们先了解一下Nacos客户端都会将什么信息传递给服务器&#xff0c;我们直接从Nacos Client项目的NamingTest说起 public class NamingTest…

less、sass、webpack(前端工程化)

目录 一、Less 1.配置less环境 1.先要安装node&#xff1a;在cmd中&#xff1a;node -v检查是否安装node 2.安装less :cnpm install -g less 3.检查less是否安装成功&#xff1a;lessc -v 4.安装成功后&#xff0c;在工作区创建xx.less文件 5.在控制台编译less,命令&…

Spring Cloud——流监控Dashboard

一、编写三个module 1. springcloud-consumer-hystrix-dashboard 1.导入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-hystrix</artifactId><version>1.4.6.RELEASE</version>…

【服务器数据恢复】raid5磁盘阵列硬盘离线的数据恢复案例

服务器数据恢复环境&#xff1a; 某公司一台服务器组建了一组raid5磁盘阵列&#xff0c;作为共享存储池使用。该服务器存储数据库文件和普通文件。 服务器故障&检测&#xff1a; RAID5磁盘阵列的硬盘掉线导致服务器操作系统识别不到D分区。管理员重启了服务器&#xff0c;服…

如何发布新闻稿,包含编写新闻稿、发布渠道

发布新闻稿是一种重要的传播方式&#xff0c;它可以让公众了解你的新闻&#xff0c;并提高新闻的知名度和可信度。在这篇文章中&#xff0c;我将详细介绍如何发布新闻稿&#xff0c;包括选择发布渠道、编写新闻稿、发布新闻稿和推广新闻稿等方面的内容。1、选择发布渠道在发布新…