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

运行结果:

        

        从运行结果可以看出,服务端最终没有收到客户端发送的信息。

        原因是:服务端代码的 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);        //复制文件描述符 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;
}

         运行结果:

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

        下面更改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;
}

        运行结果:

         运行结果证明了 服务器端在半关闭状态下向客户端发送了EOF。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【electron】electron安装过慢和打包报错:Unable to load file:

文章目录 一、安装过慢问题:二、打包报错&#xff1a;Unable to load file: 一、安装过慢问题: 一直处于安装过程 【解决】 #修改npm的配置文件 npm config edit#添加配置 electron_mirrorhttps://cdn.npm.taobao.org/dist/electron/二、打包报错&#xff1a;Unable to load…

在 Windows 上安装 OpenCV – C++ / Python

在这篇博文中&#xff0c;我们将在 Windows 上安装适用于 C 和 Python 的 OpenCV。 C 安装是在自定义安装 exe 文件的帮助下完成的。而Python的安装是通过Anaconda完成的。 在 Windows 上安装 OpenCV – C / Python&#xff08;opencv官方Wndows上安装openCV- C/ Pthon 的链接…

SpringBoot复习:(43)如何以war包的形式运行SpringBoot程序

一、.pom.xml配置packging为war <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven…

Unity3D高级编程:主程手记学习1

第一章 软件架构 Untiy 分层设计 分层后再分治

zabbix监控mysql数据库、nginx、Tomcat

文章目录 一.zabbix监控mysql数据库1.环境规划2.zabbix-server安装部署&#xff08;192.168.198.17&#xff09;3.zabbix-mysql安装部署&#xff08;192.168.198.15&#xff09;3.1 部署 zabbix 客户端3.2 服务端验证 zabbix-agent2 的连通性&#xff08;192.168.198.17&#x…

人文景区有必要做VR云游吗?如何满足游客出行需求?

VR云游在旅游行业中的应用正在快速增长&#xff0c;为游客带来沉浸式体验的同时&#xff0c;也为文旅景区提供了新的营销方式。很多人说VR全景展示是虚假的&#xff0c;比不上真实的景区触感&#xff0c;人文景区真的有必要做VR云游吗&#xff1f;我的答案是很有必要。 如果你认…

python如何开发一个电商进销存管理系统?

让我们来看一下题主的需求&#xff1a; 管理公司的淘宝天猫平台&#xff0c;后端仓库&#xff0c;采购进行数据同步。其中最主要的还是要对接淘宝API &#xff0c;实现实时订单的通知&#xff0c;同步淘宝订单&#xff0c;管理买家信息&#xff0c;发货&#xff0c;财务统计等…

C语言三子棋小游戏--数组的应用

注&#xff1a;在最后面&#xff0c;完整源码会以两种形式展现。在讲解时&#xff0c;以三个源文件的形式。 前言&#xff1a;三子棋&#xff0c;顾名思义&#xff0c;就是三个子连在一起就可以胜出。在本节我们要介绍的三子棋模式是这样子的&#xff1a;在键盘输入坐标&#x…

清华团队领衔打造,首个AI agent系统性基准测试网站问世AgentBench.com.cn

AI 智能体,或自主智能代理,不仅是诸如贾维斯等科幻电影中的人类超级助手,也一直是现实世界中 AI 领域的研究热点。尤其是以 GPT-4 为代表的 AI 大模型的出现,将 AI 智能体的概念推向了科技的最前沿。 在此前爆火的斯坦福“虚拟小镇”中,25 个 AI 智能体在虚拟小镇自由生长…

数据请求与导入mysql数据库

端口数据获取与文件保存 文件存入数据库 系统&#xff1a;Ubuntu 工具&#xff1a;Postman&#xff0c;MySql Workbench 端口数据获取与文件保存 打开postman接口测试工具 选择请求方式输入请求地址选择请求参数设置请求参数的格式输入请求参数发送请求 请求成功 选择浏览…

2023年许战海咨询《竞争之王CEO年度辅导工程》火热招募中

今天产业迭代速度不断加剧,人类知识更迭周期大大压缩到2年以内,企业遭遇更多挑战:如增长乏力、品牌老化、竞争压力大、竞争方向不明确、产品同质化、利润越来越低、团队执行难等。《竞争之王CEO年度辅导工程》应运而生,旨在手把手辅导企业家及高管团队如何制定战略和落地战略&a…

Python爬虫的应用场景与技术难点:如何提高数据抓取的效率与准确性

作为专业爬虫程序员&#xff0c;我们在数据抓取过程中常常面临效率低下和准确性不高的问题。但不用担心&#xff01;本文将与大家分享Python爬虫的应用场景与技术难点&#xff0c;并提供一些实际操作价值的解决方案。让我们一起来探索如何提高数据抓取的效率与准确性吧&#xf…

最佳实践:如何优雅地提交一个 Amazon EMR Serverless 作业?

《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书由博主历时三年精心创作&#xff0c;现已通过知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详…

阿里云服务器是什么?阿里云服务器有什么优缺点?

阿里云服务器是什么&#xff1f;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;云服务器可以降低IT成本提升运维效率&#xff0c;免去企业或个人前期采购IT硬件的成本&#xff0c;阿里云服务器让用户像使用水、电、天然气等公共资源一样便捷、高效地使用服务器…

【Linux】ICMP协议——网络层

ICMP协议 ICMP&#xff08;Internet Control Message Protoco&#xff09;Internet控制报文协议&#xff0c;用于在IP主机、路由器之间传递控制信息&#xff0c;是一个TCP/IP协议。该协议是用来检测网络传输的问题&#xff0c;相当于维修人员的工具。 ICMP协议的定位 在TCP/IP…

使用Scrapy构建自己的数据集

一、说明 当我第一次开始在工业界工作时&#xff0c;我很快意识到的一件事是&#xff0c;有时你必须收集、组织和清理自己的数据。在本教程中&#xff0c;我们将从一个名为FundRazr的众筹网站收集数据。像许多网站一样&#xff0c;该网站有自己的结构、形式&#xff0c;并有大量…

Oracle将与Kubernetes合作推出DevOps解决方案!

导读Oracle想成为云计算领域的巨头&#xff0c;但它不是推出自己品牌的云DevOps软件&#xff0c;而是将与CoreOS在Kubernetes端展开合作。七年前&#xff0c;Oracle想要成为Linux领域的一家重量级公司。于是&#xff0c;Oracle主席拉里埃利森&#xff08;Larry Ellison&#xf…

【npm run dev报错】无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。

1.winX键&#xff0c;使用管理员身份运行power shell 2.输入命令&#xff1a;set-executionpolicy remotesigned 3.输入”Y“,回车&#xff0c;问题解决。 文章来源&#xff1a;无法加载文件 C:\Program Files\nodejs\npm.ps1&#xff0c;因为在此系统上禁止运行脚本。 - 前…

【Node.js】低代码平台源码

一、低代码简介 低代码管理系统是一种通过可视化界面和简化的开发工具&#xff0c;使非专业开发人员能够快速构建和管理应用程序的系统。它提供了一套预先定义的组件和模块&#xff0c;使用户可以通过拖放操作来设计应用程序的界面和逻辑。低代码管理系统还提供了自动化的工作…

pytest运行时参数说明,pytest详解,pytest.ini详解

一、Pytest简介 1.pytest是一个非常成熟的全功能的Python测试框架&#xff0c;主要有一下几个特点&#xff1a; 简单灵活&#xff0c;容易上手&#xff0c;支持参数化 2.能够支持简单的单元测试和复杂的功能测试&#xff0c;还可以用来做selenium、appium等自动化测试&#xf…