《TCP IP网络编程》第十六章

news2024/11/24 18:33:23

第 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。

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

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

相关文章

英语——时态

动词随着所表现出的时间而改变的形态&#xff0c;称为时态&#xff0c;包含以下12种 现在过去将来一般一般现在时一般过去时一般将来时进行现在进行时过去进行式将来进行时完成现在完成时过去完成时将来完成时完成进行现在完成进行时过去完成进行时将来完成进行时 1 现在时 …

考公-判断推理-类比推理

第七节课 例题 例题 例题 例题 目无全牛&#xff1a;很熟练 不以为然&#xff1a;不认为正确 不孚众望&#xff1a;不能让人信服 例题 步人后尘&#xff1a;走别人的老路 例题 见义勇为是助人为乐的一种 例题 例题-全同 例题 并列关系 例题 例题 例题 例题 进士…

C++QT教程3——手册4.11.1自带教程(笔记)——创建一个基于Qt Widget的应用程序

文章目录 创建一个基于Qt Widget的应用程序创建Text Finder项目素材文件 填补缺失的部分设计用户界面完成头文件完成源文件创建资源文件 编译和运行程序 参考文章 创建一个基于Qt Widget的应用程序 本教程介绍如何使用Qt Creator创建一个小型Qt应用程序&#xff0c;名为Text F…

docker — 容器网络

一、概述 Docker容器每次重启后容器ip是会发生变化的。 这也意味着如果容器间使用ip地址来进行通信的话&#xff0c;一旦有容器重启&#xff0c;重启的容器将不再能被访问到。 而Docker 网络就能够解决这个问题。 Docker 网络主要有以下两个作用&#xff1a; 容器间的互联…

阿里云轻量应用服务器_2核4G4M_2核2G3M_性能测评

阿里云轻量应用服务器2核2G3M带宽108元一年&#xff0c;系统盘为50GB高效云盘&#xff1b;轻量服务器2核4G4M带宽&#xff0c;60GB高效云盘297.98元12个月。目前轻量应用服务器只有2核2G和2核4G有活动&#xff0c;阿里云百科分享阿里云轻量应用服务器入口&#xff1a; 目录 阿…

动态优先权算法

1.设计目的与要求 1.1设计目的 通过动态优先权算法的模拟加深对进程概念和进程调度过程的理解。 1.2设计要求 本实验要求学生独立地用C或C语言编写一个简单的进程管理程序&#xff0c;其主要部分是进程调度。调度算法可由学生自行选择&#xff0c;如基于动态优先级的调度算法…

FL Studio 21 for macOS-21.1.0.3267中文直装版功能介绍及系统配置要求

FL Studio 21简称FL水果软件,全称是&#xff1a;Fruity Loops Studio编曲&#xff0c;由于其Logo长的比较像一款水果因此&#xff0c;在大家更多的是喜欢称他为水果萝卜&#xff0c;FL studio21是目前最新的版本&#xff0c;这是一款可以让你的计算机就像是一个全功能的录音室&…

ArcGIS Maps SDK for JavaScript系列之三:在Vue3中使用ArcGIS API加载三维地球

目录 SceneView类的常用属性SceneView类的常用方法vue3中使用SceneView类创建三维地球项目准备引入ArcGIS API创建Vue组件在OnMounted中调用初始化函数initArcGisMap创建Camera对象Camera的常用属性Camera的常用方法 要在Vue 3中使用ArcGIS API for JavaScript加载和展示三维地…

8.利用matlab完成 符号微积分和极限 (matlab程序)

1.简述 一、符号微积分 微积分的数值计算方法只能求出以数值表示的近似解&#xff0c;而无法得到以函数形式表示的解析解。在 MATLAB 中&#xff0c;可以通过符号运算获得微积分的解析解。 1. 符号极限 MATLAB 中求函数极限的函数是 limit&#xff0c;可用来求函数在指定点的…

Java 并发编程与CAS基本原理

一、Java并发基础知识 Java里的程序天生就是多线程的&#xff0c;那么有几种新启线程的方式&#xff1f; 两种,启动线程的方式只有&#xff1a; 1、X extends Thread;&#xff0c;然后X.start&#xff1b; 2、X implements Runnable&#xff1b;然后交给Thread运行。 Java…

IDEA的常用设置,让你更快速的编程

一、前言 在使用JetBrains的IntelliJ IDEA进行软件开发时&#xff0c;了解和正确配置一些常用设置是非常重要的。IDEA的强大功能和定制性使得开发过程更加高效和舒适。 在本文中&#xff0c;我们将介绍一些常用的IDEA设置&#xff0c;帮助您更好地利用IDEA进行开发。这些设置包…

Nonebot实战之编写插件1

前言 应粉丝群内粉丝要求&#xff0c;我也决定写一个Nonebot插件编写教程&#xff0c;从0开始教学。有些不对的地方也欢迎大家指正&#xff0c;修改。 开始 准备 合适的代码编辑器一定的python基础懂得提问的方式 代码编辑器 代码编辑器有很多种选择&#xff0c;比如 vsc…

基本逻辑门的工作原理、电路图、逻辑图、逻辑表达式等

与、或、与非、或非、异或门、三态门、OD门&#xff08;OC门&#xff09;和传输门等的结构与使用 逻辑门是基于半导体器件形成的&#xff0c;开始学习逻辑门之前应具备半导体器件的相关知识 可阅读如下文章进行先导学习 半导体器件&#xff1a; https://blog.csdn.net/weixi…

彻底卸载Android Studio

永恒的爱是永远恪守最初的诺言。 在安装Android Studio会有很多问题导致无法正常运行&#xff0c;多次下载AS多次错误后了解到&#xff0c;删除以下四个文件才能彻底卸载Android Studio。 第一个文件&#xff1a;.gradle 路径&#xff1a;C:\Users\yao&#xff08;这里yao是本…

下载程序到西门子PLC

更多关于西门子S7-200PLC内容请查看&#xff1a;西门子200系列PLC学习课程大纲 下载西门子200PLC程序分以下两步&#xff1a; 一.编译程序 1. 如下图1-1所示&#xff0c;使用PPI电缆将PLC和电脑连接上&#xff0c;注意笔记本使用USB转PPI电缆&#xff0c;连接保证给PLC单独供…

什么是gRPC?

1. GRPC是google开源的rpc框架 2. 核心是一个.proto的服务描述文件 3. 添加依赖的grpc相关的包&#xff0c;配置IDEA的grpc插件&#xff0c;就可以很方便的生成调用代码 4. 通过在IDEA的protobuf插件上分别执行以下两个服务&#xff0c;就可以生成需要的调用代码 1&#xff…

阿里云轻量应用服务器使用教程_创建配置_远程连接_网站上线

阿里云轻量应用服务器怎么使用&#xff1f;阿里云百科分享轻量应用服务器从选择创建、配置建站环境、轻量服务器应用服务器远程连接、开端口到网站上线全流程&#xff1a; 目录 阿里云轻量应用服务器使用教程 步骤一&#xff1a;购买一台轻量应用服务器 步骤二&#xff1a;…

Android核心开发之——OpenGL

OpenGL是一种用于编程计算机图形的应用程序编程接口&#xff08;API&#xff09;。它提供了一系列函数和方法&#xff0c;用于绘制2D和3D图形&#xff0c;以及进行渲染和图形处理。OpenGL可以跨平台使用&#xff0c;支持各种操作系统和硬件设备。它被广泛应用于游戏开发、虚拟现…

Java并发编程(五)线程同步 下 [CAS/原子类/同步容器类/同步工具类]

CAS 概述 CAS全称为Compare-And-Swap。它是一条CPU的原子指令,是硬件对于并发操作共享数据的支持。其作用是CPU在某个时刻比较两个值是否相等 核心原理&#xff1a;在操作期间CAS先比较下主存中的值和线程中工作内存中的值是否相等,如果相等才会将主存中的值更新为新值&…

Golang服务的请求调度

文章目录 1. 写在前面2. SheddingHandler的实现原理3. 相关方案的对比4. 小结 1. 写在前面 最近在看相关的Go服务的请求调度的时候&#xff0c;发现在gin中默认提供的中间件中&#xff0c;不含有请求调度相关的逻辑中间件&#xff0c;去github查看了一些服务框架&#xff0c;发…