TCP/IP网络编程 第十六章:关于IO流分离的其他内容

news2024/11/24 4:32:48

分离I/O流


两次I/O流分离


我们之前通过2种方法分离过IO流,第一种是第十章的“TCPI/O过程(Routine)分离”。这种方法通过调用fork函数复制出1个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了2个文件描述符的用途,因此这也属于“流”的分离。
第二种分离在第十五章。通过2次fdopen函数的调用,创建读模式FILE指针(FILE结构体指针)和写模式FILE指针。换言之,我们分离了输入工具和输出工具,因此也可视为“流”的分离。下面说明分离的理由,讨论尚未提及的问题并给出解决方案。


分离“流”的好处


第10章的“流”分离和第15章的“流”分离在目的上有一定差异。首先分析第10章的“流”分离目的。
□通过分开输入过程(代码)和输出过程降低实现难度。
□与输入无关的输出操作可以提高速度

这是第10章讨论过的内容,故不再解释这些优点的原因。接下来给出第15章“流”分离的目的。
□为了将FILE指针按读模式和写模式如以区分。
□可以通过区分读写模式降低实现难度。
□通过区分IO缓冲提高缓冲性能
“流”分离的方法、情况(目的)不同时,带来的好处也有所不同。


“流”分离带来的EOF问题


下面讲解“流”分离带来的问题。第7章介绍过EOF的传递方法和半关闭的必要性(如果记
不清,请复习相应章节)。各位应该还记得如下函数调用语句:

shutdown(sock, SHUT_WR);


当时讲过调用shutdown函数的基于半关闭的EOF传递方法。10章还利用这些技术在示例中添加了半关闭相关代码。也就是说,第10章的“流”分离没有问题。但第15章的基于fdopen函数的“流”则不同,我们还不知道在这种情况下如何进行半关闭,因此有可能犯如下错误:
“半关闭?不是可以针对输出模式的FILE指针调用fclose函数吗?这样可以向对方传递EOF,变成可以接收数据但无法发送数据的半关闭状态呀。”各位是否也这么认为?这是一种很好的猜测,但希望大家先阅读下列代码。另外,接下来的示例中为了简化代码而未添加异常处理,希望各位不要误解。先给出服务器端代码。

#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_sock,clnt_sock;
    socklen_t clnt_addr_sz;
    char buf[BUF_SIZE]={0,};
  
    serv_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=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    listen(serv_sock,5);
    clnt_addr_sz=sizof(clnt_sock);
    clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_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;
}

有些人可能认为可以通过第39行的函数调用接受客户端最后发送的字符串。上述示例调用fclose函数后的确会发送EOF。稍后给出的客户端收到EOF后也会发送最后的字符串,只是需要验证第39行的函数调用能否接受。接下来给出客户端代码。

#include<"头文件声明和服务器端声明一样,故省略">
#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,sizepf(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;
}

从运行结果可以得出以下结论:“服务器端未能接受由客户端传来的最后的字符串!”。

很容易判断其原因::在服务器示例中的第38行调用的fclose函数完全终止了套接字,而不是半关闭。以上就是需要通过本章解决的问题。

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

终止"流"时无法半关闭的原因

 这张图描述了之前示例中服务器端的两个文件指针。那么一切就真相大白了。当读指针调用fclose函数的时候会关闭文件描述符,此时会关闭套接字。那么如何解决呢?不就是创建FILE指针前先复制文件描述符即可。

 如图所示,复制后另外创建1个文件描述符,然后利用各自的文件描述符生成读模式FILE指针和写模式FILE指针。这就为半关闭准备好了环境,因为套接字和文件描述符之间具有如下关系:

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

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

那此时的状态是否为半关闭状态?不是!只是准备好了半关闭环境。要进入真正的半关闭状态需要特殊处理。仔细观察,还剩1个文件描述符。而且该文件描述符可以同时进行IO因此,不但没有发送EOF,而且仍然可以利用文件描述符进行输出。稍后将介绍发送EOF并进入半关闭状态的方法。首先介绍如何复制文件描述符,之前的fork函数不在考虑范围内。

复制文件描述符

之前所提到的文件描述符复制与fork函数中进行的复制有所区别。调用fork函数时将复制整个进程,此时的复制从某种意义上是复制到另一个进程中。但是这里提到的复制是可以在同一个进程内完成文件描述符的复制。当然,文件描述符的值不能重复,因此要使用互不相同的值。为了形成这种结构,需要复制文件描述符。此处所谓的"复制"具有如下含义:"为了访问同一文件或套接字,创建另外一个文件描述符。"

dup&dup2

下面给出文件描述符的复制方法,通过下列两个函数之一完成。

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

dup2函数明确指定复制的文件描述符整数值。向其传递大于0且小于进程能生成的最大文件描述符值时,该值将成为复制出的文件描述符的值。

复制文件描述符后"流"的分离

下面我们的目的就是使之前的服务器客户端模型正常工作。所谓"正常"工作是指,通过服务器端的半关闭状态接收客户端最后发出的字符串。当然,为了完成这一任务,服务端需要同时发送EOF。下面是示例代码:

#include<"头文件和之前的示例相同,故省略">
#define BUF_SIZE 1024

int main(int argc,char*argv[]){
    int serv_sock,clnt_sock;
    FILE *readfp;
    FILE *writefp;
    
    struct sockaddr_in serv_addr,clnt_addr;
    socklen_t clnt_addr_sz;
    char buf[BUF_SIZE]={0,};

    serv_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=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    bind(serv_sock,(struct sockadd*)&serv_addr,sizeof(serv_addr));
    listen(serv_sock,5);
    clnt_addr_sz=sizeof(clnt_addr);
    clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_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);//关闭并发送EOF

    fgets(buf,sizeof(buf),readfp);
    fputs(buf,stdout);
    fclose(readfp);
    return 0;
}

结果证明服务器端在半关闭的情况下向客户端发送了EOF,通过该示例希望大家掌握一点:"

无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态"

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

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

相关文章

基于主从博弈的主动配电网阻塞管理的论文复现——附Matlab代码

目录 文章摘要&#xff1a; 编程思路&#xff1a; 研究背景&#xff1a; 基于主从博弈的电网阻塞管理&#xff1a; 算例介绍&#xff1a; Matlab运行结果展示&#xff1a; Matlab代码数据分享&#xff1a; 文章摘要&#xff1a; 随着需求侧灵活性资源在配电网中的渗透率…

SQLite编程操作

一、打开/创建数据库的C接口 ①sqlite3_open ( const char * filename , sqlite3 ** ppDb ) 打开一个指向 SQLite 数据库文件的连接&#xff0c;返回一个用于其他 SQLite 程序的数据库连接对 象。 ②sqlite3_close(sqlite3*) 关闭之前调用 sqlite3_open() 打开的数据…

⛳ Git安装与配置

Git安装配置目录 ⛳ Git安装与配置&#x1f3ed; 一&#xff0c;git的安装&#x1f3a8; 1&#xff0c;下载git&#x1f463; 2&#xff0c;下载完成之后&#xff0c;双击安装即可。&#x1f4bb; 3&#xff0c;更改安装目录&#xff08;没有中文且没有空格&#xff09;&#x…

3本期刊被剔除,7月SCIE/SSCI目录已更新 (附2023WOS历次更新目录)~

2023年7月17日&#xff0c;科睿唯安更新了Web of Science核心期刊目录。 此次更新后SCIE期刊目录共包含9498本期刊&#xff0c;SSCI期刊目录共包含3557本期刊。此次SCIE & SSCI期刊目录更新&#xff0c;与上次更新&#xff08;2023年6月&#xff09;相比&#xff0c;有4本S…

Shell之循环语句 —— WhileUntil 实验

While While循环语句&#xff1a;满足条件才会执行循环&#xff0c;不满足就结束&#xff0c;用于不知道循环次数&#xff0c;需要主动结束循环或者达到条件循环的场景 While的结构 while&#xff08;条件判断&#xff09;——do —— 命令序列 —— done 如&#xff1a;用whi…

Python实现HBA混合蝙蝠智能算法优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蝙蝠算法是2010年杨教授基于群体智能提出的启发式搜索算法&#xff0c;是一种搜索全局最优解的有效方法…

qiankun框架vue3主应用和子应用生产环境打包部署nginx

首先下载nginx,进行最小化配置 用vscode 打开nginx.conf文件 在http模块的server模块里进行配置 listen 字段监听端口号 http的默认端口号是80(nginx的端口号可以随便写) server_name字段 是ip地址 lochhost就是127.0.0.1 lacation 字段 是在浏览器的地址栏http协议ip地址…

C++类和对象——类的基础

目录 类的引入类的定义类的访问限定符和封装对象的实例化类对象的大小this指针 类的引入 在C语言中&#xff0c;结构体中只能定义变量 但是在C中&#xff0c;结构体不仅可以定义变量&#xff0c;还可以定义函数 下面就是C中的一个结构体&#xff1a; struct Stack {void init(…

【Linux系统 学习笔记】Linux线程互斥 线程安全 可重入 不可重入 死锁

目录 Linux 线程互斥进程线程间互斥相关背景和概念互斥量互斥量的接口互斥量实现原理探究 可重入与线程安全概念常见的线程不安全的情况常见的线程安全的情况常见不可重入的情况常见可重入的情况可重入与线程安全联系可重入与线程安全区别 死锁死锁四个必要条件避免死锁 Linux …

【代码随想录13】前 K 个高频元素

题目 给定一个非空的整数数组&#xff0c;返回其中出现频率前 k 高的元素。 示例 1: 输入: nums [1,1,1,2,2,3], k 2输出: [1,2] 示例 2: 输入: nums [1], k 1输出: [1] 提示&#xff1a; 你可以假设给定的 k 总是合理的&#xff0c;且 1 ≤ k ≤ 数组中不相同的元素…

黑客学习笔记(自学)

一、首先&#xff0c;什么是黑客&#xff1f; 黑客泛指IT技术主攻渗透窃取攻击技术的电脑高手&#xff0c;现阶段黑客所需要掌握的远远不止这些。 二、为什么要学习黑客技术&#xff1f; 其实&#xff0c;网络信息空间安全已经成为海陆空之外的第四大战场&#xff0c;除了国…

C#(六十)之Convert类 和 Parse方法的区别

Convert数据类型转换类&#xff0c;从接触C#开始&#xff0c;就一直在用&#xff0c;这篇日志坐下深入的了解。 Convert类常用的类型转换方法 方法 说明 Convert.ToInt32() 转换为整型(int) Convert.ToChar() 转换为字符型(char) Convert.ToString() 转换为字符串型(st…

优化CSS重置过程:探索CSS层叠技术的应用与优势

目录 下面是正文~~ CSS重置方法 方法的结合 合并方法的问题 通用移除样式 顺序很重要 CSS 优先级 我们的CSS特异性冲突 CSS Layers 来拯救 Sass 预处理器支持 浏览器支持 总结 这篇文章介绍了一种名为CSS层叠的技术&#xff0c;用于优化CSS重置过程。它解释了CSS重…

网络安全(黑客技术)最全面的学习笔记

学网络安全如何成为一名黑客呢&#xff1f; 整合了全知识点及学习框架&#xff0c;本篇零基础依然适用&#xff01; 本篇涵盖内容及其全面&#xff0c;强烈推荐收藏&#xff01; 一、学习网络安全会遇到什么问题呢&#xff1f; 1、学习基础内容多时间长 学习基础语言太多&…

基于MATLAB的无人机遥感数据预处理与农林植被性状估算教程

详情点击链接&#xff1a;基于MATLAB的无人机遥感数据预处理与农林植被性状估算前言 遥感技术作为一种空间大数据手段&#xff0c;能够从多时、多维、多地等角度&#xff0c;获取大量的农情数据。数据具有面状、实时、非接触、无伤检测等显著优势&#xff0c;是智慧农业必须采…

初中级PHP程序员如何进阶学习?

如果你是一个以PHP为主的开发人员&#xff0c;只会依赖现成的框架进行增删改查&#xff0c;想提高自己又不知道从何下手&#xff0c;你可以花点时间研究一下我这个开源项目&#xff1a;酷瓜云课堂&#xff0c;这个项目以PHPJS 为主&#xff0c;负责主要的业务逻辑&#xff0c;部…

基于遗传算法的新能源电动汽车充电桩与路径选择MATLAB程序

主要内容&#xff1a; 根据城市间的距离&#xff0c;规划新能源汽车的行驶路径。要求行驶距离最短。 部分代码&#xff1a; %% 加载数据 %%遗传参数 load zby;%个城市坐标位置 NIND50; %种群大小 MAXGEN200; Pc0.9; %交叉概率 Pm0.2; %变异概率 GGAP0.…

初识Redis——Redis概述、安装、基本操作

目录 一、NoSQL介绍 1.1什么是NoSQL 1.2为什么会出现NoSQL技术 1.3NoSQL的类别 1.4传统的ACID是什么 1.5 CAP 1.5.1 经典CAP图 1.5.4 什么是BASE 二、Redis概述 2.1 什么是Redis 2.2 Redis能干什么 2.3 Redis的特点 2.4 Redis与memcached对比 2.5 Redis的安装 2.6 Docker安装 三…

基于Redisson的Redis结合布隆过滤器使用

一、场景 缓存穿透问题 一般情况下&#xff0c;先查询Redis缓存&#xff0c;如果Redis中没有&#xff0c;再查询MySQL。当某一时刻访问redis的大量key都在redis中不存在时&#xff0c;所有查询都要访问数据库&#xff0c;造成数据库压力顿时上升&#xff0c;这就是缓存穿透。…

【Python基础】- break和continue语句

在Python中&#xff0c;break和continue是用于控制循环语句的特殊关键字。 break语句用于跳出当前的循环&#xff08;for循环或while循环&#xff09;&#xff0c;并继续执行紧接着的循环外的代码。它通常用于满足某个条件时提前结束循环。例如&#xff0c;考虑以下示例&#…