十:套接字和标准I/O,以及分离I/O流

news2025/1/17 0:49:18

1 标准I/O函数的优点

  • C语言标准IO整理

1.1 标准I/O函数的两个优点

  • 标准I/O函数具有良好的移植性。

  • 标准I/O函数可以利用缓冲提高性能

    从图中可以看出,使用标准I/O函数传输数据时,经过两个缓冲。例如,使用fputs函数传输字符串 “Hello” 时,首先将数据传递到标准I/O函数的缓冲。然后数据将移动到套接字输出缓冲,最后将字符串发送到对方主机。
      既然知道了两个缓冲的关系,接下来再说明各自的用途。设置缓冲的主要目的是为了提高性能,但是套接字的缓冲主要是为了实现TCP协议而设立的。例如,TCP传输中丢失数据时将再次传递,而再次发送数据则意味着某地保存了数据。这个数据就存在套接字的输出缓冲。
      实际上,缓冲并非在所有情况下都能带来卓越的性能。但是需要传输的数据越多,有无缓冲带来的性能差异越大,可以通过如下两种角度说明性能的提高。

    • 传输的数据量;
    • 数据项输出缓冲区移动的次数;

  比较一个字节的数据发送10次(10个数据包)的情况和累计10个字节发送一次的情况。发送数据时使用的数据包中含有头信息。头信息和数据大小无关,是按照一定的格式填入的。即使假设该头信息占用40个字节(实际更大),需要传递的数据量也存在较大差别。
- 1个字节 10次 40 ✖10 = 400字节
- 10个字节 1此 40 ✖ 1 = 40字节

  另外,为了发送数据,向套接字输出缓冲区移动数据也会消耗不少时间,但这同样与移动次数有关。1个字节数据共移动10次花费的时间将此10个字节数据移动一次花费时间的10倍。

1.2 标准IO函数和系统函数之间的性能对比

我的news.txt才只有59.4kb大就已经有明显差距了
在这里插入图片描述

首先是利用系统函数复制文件的示例。

#include <stdio.h>
#include <fcntl.h>
#include <time.h>

#define BUFF_SIZE 30

int main(int argc, char* argv[]) {
   int fd1, fd2;
   int len;
   char buf[BUFF_SIZE];

   clock_t start, end;

   fd1 = open("news.txt", O_RDONLY);
   fd2 = open("cpy.txt", O_WRONLY | O_CREAT | O_TRUNC);

   start = clock();

   while (len = read(fd1, buf, sizeof(buf)) > 0) {
       write(fd2, buf, len);
   }

   end = clock();
   double duration = (double)(end - start) / CLOCKS_PER_SEC;

   printf("文件复制执行时间:%f\n", duration);
   close(fd1);
   close(fd2);
   return 0;
}

然后是标准IO

#include <stdio.h>
#include <time.h>

#define BUFF_SIZE 3 //用最短数组长度构成

int main(int argc, char* argv[]) {
   FILE* fp1;
   FILE* fp2;
   char buf[BUFF_SIZE];

   clock_t start, end;

   fp1 = fopen("news.txt", "r");
   fp2 = fopen("cpy.txt", "w");

   start = clock();

   while (fgets(buf, BUFF_SIZE, fp1) != NULL) {
       fputs(buf, fp2);
   }

   end = clock();
   double duration = (double)(end - start) / CLOCKS_PER_SEC;

   printf("文件复制执行时间:%f\n", duration);
   close(fp1);
   close(fp2);
   return 0;
}

1.3 标准IO的缺点

  • 不容易进行双向通信
  • 有时可能频繁调用fflush函数(切换读写工作状态时发生)
  • 需要以FILE结构体指针的形式返回文件描述符

2 使用标准IO函数

  如前所述,创建套接字时返回文件描述符,而为了使用标准IO函数,hi只能将其转化为FILE结构体指针。先介绍转换方法。

2.1 利用fdopen函数转换为FILE结构体指针

  可以通过fdopen函数将创建套接字时返回的文件描述符转换为标准I/O函数中使用的FILE结构体指针。

#include <stdio.h>

/**
* @param fildes 需要转换的文件描述符,mode为结构体指针的模式信息,常用的有"r", "w"
* @return 成功时返回FILE结构体指针,失败时返回NULL
*/
FILE *fdopen(int fildes, const char* mode);
运行结果
#include <stdio.h>
#include <fcntl.h>

int main() {
    FILE *fp;
    int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd == -1) {
        fputs("file open failed \n", stdout);
        return -1;
    }
    fp = fdopen(fd, "w");
    fputs("Network C programming \n", fp);
    fclose(fp);
    return 0;
}

2.2 利用fileno函数转化为文件描述符

该函数功能与fdopen相反。

#include <stdio.h>

int fileno(FILE* stream);

在这里插入图片描述

#include <stdio.h>
#include <fcntl.h>

int main(void) {
    FILE* fp;
    int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd == -1) {
        fputs("file open error \n", stdout);
        return -1;
    }
    printf("first file description: %d \n", fd);
    fp = fdopen(fd, "w");
    fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
    printf("second file description: %d \n", fileno(fp));
    fclose(fp);
    return 0;
}

2.3 基于套接字的标准I/O函数使用

将第四章的回声客户端和服务端改为基于标准I/O函数的数据交换形式。
在这里插入图片描述

echo_stdserv.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 1024

void ErrorHandler(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("Usage : %s <port> \n", argv[0]);
        exit(1);
    }
    
    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1) {
        ErrorHandler("socket error");
    }

    struct sockaddr_in serv_adr;
    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]));

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        ErrorHandler("bind error");
    }
    if (listen(serv_sock, 5) == -1) {
        ErrorHandler("listen error");
    }

    struct sockaddr_in clnt_adr;
    socklen_t clnt_size = sizeof(clnt_adr);

    int clnt_sock;
    char msg[BUF_SIZE];
    int str_len;
    FILE* readfp;
    FILE* writefp;
    for (; ;) {
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_size);
        if (clnt_sock == -1) {
            ErrorHandler("accept error");
        } else {
            printf("Connected client %d \n", clnt_sock);
        }

        readfp = fdopen(clnt_sock, "r");
        writefp = fdopen(clnt_sock, "w"); //这里不要写错了,排错排了好久,操作的都是clntsock
        while(!feof(readfp)) {
            fgets(msg, BUF_SIZE, readfp);
            fputs(msg, writefp);
            fflush(writefp);
        }
        fclose(readfp);
        fclose(writefp);   
    }
    close(serv_sock);
    return 0;
} 

echo_stdclnt.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 1024

void ErrorHandler(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("Usage %s <IP><PORT>\n", argv[0]);
    }

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        ErrorHandler("socket error");
    }

    struct sockaddr_in serv_adr;
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        ErrorHandler("connect error");
    } else {
        printf("connected.....\n");
    }

    FILE* readfp = fdopen(sock, "r");
    FILE* writefp = fdopen(sock, "w");
    char message[BUF_SIZE];
    for (;;) {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
            break;
        }

        fputs(message, writefp);
        fflush(writefp);
        fgets(message, BUF_SIZE, readfp);
        printf("message from server: %s", message);
    }
    fclose(writefp);
    fclose(readfp);
    return 0;
}

3 分离IO流

3.1 2次I/O流分离

  之前我们使用两种方法分离过I/O流。
1. 第一种是通过fork函数复制出一个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身并不根据输入和输出进行区分,但我们分开了2个文件描述符的用途,因此也属于“流”的分离;
2. 第二种是通过2次fdopen函数的调用,创建读模式FILE指针和写模式FILE指针。即,我们分离了输入和输出工具,所以也属于“流”的分离。

3.2 分离“流”的好处

  • 第一种分离方法的目的
    • 通过分开输入过程代码和输出过程降低实现难度
    • 与输入无关的输出操作可以提高速度
  • 第二种分离方式的目的
    • 为了将FILE指针按读模式和写模式进行区分
    • 可以通过区分读写模式降低实现难度
    • 通过区分I/O缓冲提高缓冲性能

3.3 “流”分离带来的EOF问题

  我们在学习多进程服务端时,调用shutdown函数实现基于半关闭的EOF传递方法,此种“流”分离没有问题。但是基于fdopen函数的“流”则不同,我们不知道在这种情况下如何实现半关闭,因此有可能犯以下错误:

“半关闭?不是可以针对输出模式的FILE指针调用fclose函数吗?这样可以向对方传递EOF,变成可以接收数据但无法发送数据的半关闭状态。”

  下面我们使用代码来进行验证
效果:客户端收到服务端的信息,服务端没收到客户端的Thanks。

  • 服务端seq_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 = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serv_adr, clnt_adr;
    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);

    int clnt_adr_size = sizeof(clnt_adr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_size);

    FILE* readfp = fdopen(clnt_sock, "r");
    FILE* writefp = fdopen(clnt_sock, "w");

    fputs("from server: hi client?\n", writefp);
    fputs("i love all the world! \n", writefp);
    fputs("怕了把?\n", writefp);
    fflush(writefp);

    fclose(writefp);  //关闭写指针后,测试读指针是否还能正常工作
    char buf[BUF_SIZE];
    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);
    fclose(readfp);
    return 0;
}
  • 客户端seq_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024

int main(int argc, char* argv[]) {
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serv_adr;
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

    FILE* readfp = fdopen(sock, "r");
    FILE* writefp = fdopen(sock, "w");

    char buf[BUF_SIZE];
    while (1) {
        if (fgets(buf, sizeof(buf), readfp) == NULL) {
            break;
        }
        fputs(buf, stdout);
        fflush(stdout);
    }

    fputs("FROM CLIENT: Thanks! \n", writefp);
    fflush(writefp);
    fclose(writefp);
    fclose(readfp);
    return 0;
}

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

4.1 中止流时无法半关闭的原因

  从图中可以看出读模式和写模式的FILE指针都是基于同一文件描述符创建的。因此针对任意一个FILE指针调用fclose函数都会关闭文件描述符。
  解决放哪也很简单,创建FILE指针之前先复制文件描述符即可。

  但是这样只是准备好了半关闭环境,剩余的文件描述符仍然可以进行I/O,所以并没有发送EOF,因此还需要一些特殊处理。

4.2 复制文件描述符

通过下列两个函数之一完成。

#include <unistd.h>

int dup(int fildes);
//fildes:需要复制的文件描述符;fildes2明确指定的文件描述符整数值
int dup2(int fildes, int fildes2);

在这里插入图片描述

  • dup.c
#include <stdio.h>
#include <unistd.h>

int main() {
    int cfd1, cfd2;
    char str1[] = "hi~ \n";
    char str2[] = "it is a nice day~ \n";

    cfd1 = dup(1);
    cfd2 = dup2(cfd1, 7);

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

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

更改seq的服务端,关闭文件指针后同时发送EOF。
在这里插入图片描述
因为发送了EOF,所以才能退出循环

  • seq_serv2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024

int main(int argc, char* argv[]) {
    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serv_adr;
    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);
    struct sockaddr_in clnt_adr;
    socklen_t clnt_size = sizeof(clnt_adr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_size);

    FILE* readfp = fdopen(clnt_sock, "r");
    FILE* writefp = fdopen(dup(clnt_sock), "w");

    fputs("from server: hi? \n", writefp);
    fputs("from server: love you \n", writefp);
    fflush(writefp);
    shutdown(fileno(writefp), SHUT_WR);//发送EOF
    fclose(writefp);

    char buf[BUF_SIZE];
    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);
    fclose(readfp);
    return 0;
}

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

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

相关文章

数据分析-Pandas数据的画图设置

数据分析-Pandas数据的画图设置 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&#x…

TypeScript(四)枚举类型(Enum Types),类型别名(Type),运算符

一、枚举类型 使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。 枚举类型的特点 可以给一组数值取上一个更好理解的名字&#xff1b;一个枚举中只会存在几个固定的值&#xff0c;并不会…

C语言第三十六弹---文件操作(中)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 文件操作 1、文件的顺序读写 1.1、顺序读写函数介绍 1.1.1、fgetc 与 fputc 1.1.2、fgets 与 fputs 1.1.3、fscanf 与 fprintf 1.1.4、fread 与 fwrite 1.…

【vue3之组合式API及其新特性】

组合式API及其新特性 一、setup1.写法2.如何访问3.语法糖4.同步返回对象 二、reactive()和ref()1.reactive()2.ref() 三、computed四、watch函数1.侦听单个数据2.侦听多个数据3. immediate4. deep5.精确侦听对象的某个属性 五、生命周期函数六、组件通信1.父传子2. 子传父 七、…

云消息队列 Confluent 版正式上线!

作者&#xff1a;阿里云消息队列 前言 在 2023 年杭州云栖大会上&#xff0c;Confluent 成为阿里云技术合作伙伴&#xff0c;在此基础上&#xff0c;双方展开了深度合作&#xff0c;并在今天&#xff08;3月1日&#xff09;正式上线“云消息队列 Confluent 版”。 通过将 Co…

基于51单片机心率脉搏计设计

目 录 摘 要 I Abstract II 引 言 1 1 控制系统设计 3 1.1 系统方案设计 3 1.2 系统总体设计 4 2 硬件设计 5 2.1 主控电路 5 2.2 驱动电路 8 2.3 信号采集电路 10 2.4 显示电路 13 2.5 总体电路图设计 15 3 软件设计 16 3.1 软件开发环境的介绍 16 3.2 系统重要函数介绍 16 4…

解决QMYSQL driver not loaded问题

前言 之前都是在Qt5.51上开发&#xff0c;连接mysql数据库一直没有问题&#xff0c;换到5.15.2后一直报错 一查才发现\5.15.2\msvc2019_64\plugins\sqldrivers目录下没有qsqlmysql了&#xff0c;5.5.1是有的&#xff0c;5.15.2是要自己编译的。。。 下载源码 安装qt的时候没…

大型多模态智能体:综述

论文链接&#xff1a;https://arxiv.org/abs/2402.15116 大型语言模型&#xff08;LLMs&#xff09;在推动文本基础的智能体方面已经取得了超群的性能&#xff0c;赋予它们类似人类的决策和推理能力。与此同时&#xff0c;一个新兴的研究趋势集中于将这些LLM驱动的智能体扩展到…

这套系统架构,同事直呼666

你使用的每一个热门应用程序的背后,都有一个由架构、测试、监控和安全措施组成的软件系统。今天让我们看一下满足生产环境应用程序的高级架构由哪些体系组成。 CI/CD 管道 我们的第一个关键领域是持续集成和持续部署——CI/CD 管道。 这确保了我们的代码从存储库出发,经过…

【TOP】中科院1区TOP,Elsevier出版社,仅2-3个月录用!

【SciencePub学术】 01 期刊基本信息 【期刊简介】IF&#xff1a;5.0-5.5&#xff0c;JCR1区&#xff0c;中科院1区TOP&#xff1b; 【版面情况】正刊&#xff0c;仅10篇版面&#xff1b; 【检索情况】SCIE在检&#xff0c;预计3个月左右录用&#xff1b; 【征稿领域】有关…

图机器学习(2)-图的基本表示

0 复习 1 图的基本表示 1.1 本体图Ontology 疾病和食物之间的关系&#xff1a;能吃、不能吃、推荐吃。 1.2 图的种类 药物和药物之间的副作用就是异质图&#xff1a; 二分图是一种特殊的异质图&#xff1a; 1.3 节点连接数 通过节点数可以说明节点的重要度。 1.4 邻接…

.NetCore6.0实现ActionFilter过滤器记录接口请求日志

文章目录 目的实现案例&#xff1a;一.首先我们新建一个WebApi项目二.配置 appsettings.json 文件&#xff0c;配置日志存放路径三.创建 Model 文件夹&#xff0c;创建AppConfig类和ErrorLog类1.在AppConfig类中编写一个GetConfigInfo方法获取配置文件中的值2.在ErrorLog类中&a…

Apache Flink连载(三十七):Flink基于Kubernetes部署(7)-Kubernetes 集群搭建-3

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录

重庆有哪些媒体资源?活动展会媒体邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 重庆作为中国的一个直辖市&#xff0c;拥有丰富的媒体资源&#xff0c;涵盖电视台、广播电台、报纸、杂志以及网络媒体等各个领域。这些媒体不仅是传播新闻和信息的重要渠道&#xff0c;…

学习JavaEE的日子 Day23 迭代器,LinkedList,Vector,Stack,HashSet,LinkedHashSet

Day23 1.迭代器 含义&#xff1a;遍历集合中的数据 分类&#xff1a;Iterator 和 ListIterator Iterator 和 ListIterator 区别 Iterator &#xff1a;Collection接口下所有的实现类都可以获取的迭代器&#xff0c;可以在遍历时删除元素 ListIterator &#xff1a;List接口下所…

京东数据分析平台(京东店铺数据分析工具)推荐

京东店铺数据分析能够帮助商家了解自己的经营状况&#xff0c;优化商品策略&#xff0c;提高销售效率。以下是京东店铺数据分析的一些基本步骤和方法&#xff1a; 首先&#xff0c;在进行京东店铺数据分析时&#xff0c;我们需要借助一些电商数据分析工具来获取相关数据&#…

一次一对一服务引起的沉思和笑话(微信号Stefan)

前情提要 客户需求&#xff1a; 分析页面代码和接口请求协议和参数需求&#xff0c;将人工下载视频怎么获得最终的视频链接&#xff0c;这一逻辑清晰的展示并讲解清除。我询问了是否需要成品爬虫&#xff0c;他说代码他自己能搞定。 我给的价格选择&#xff1a; 第一种、首…

基于状态机的按键消抖实现

摸鱼记录 Day_14 !(^O^)y review 在day_13中以按键状态判断为例学习了状态分析基于状态机的按键消抖原理-CSDN博客 分析得到了下图&#xff1a; 今日任务&#xff1a;完成此过程 !(^O^)y 小梅哥对应视频&#xff1a; 15B 基于状态机的按键消抖Verilog实现_哔哩哔哩…

[C#]winform基于C2PNet算法实现室内和室外图像去雾

【CP2Net框架】 https://github.com/YuZheng9/C2PNet 【CP2Net介绍】 Abstract 考虑到不适定的性质&#xff0c;发展了单图像去模糊的对比正则化&#xff0c;引入了来自负图像的信息作为下界。然而&#xff0c;对比样本是非一致的&#xff0c;因为阴性通常距离清晰&#xff…

二维码门楼牌管理系统应用场景:赋能市场研究与城市决策的新动力

文章目录 前言一、市场研究的新视角&#xff1a;门牌数据揭示市场趋势二、城市规划的得力助手&#xff1a;门牌数据指导资源分配三、决策制定的科学依据&#xff1a;门牌数据提升决策准确性四、未来展望&#xff1a;二维码门楼牌管理系统的更多可能性 前言 随着信息技术的飞速…