《TCP/IP网络编程》学习笔记 | Chapter 15:套接字与标准 I/O

《TCP/IP网络编程》学习笔记 | Chapter 15:套接字与标准 I/O

  • 《TCP/IP网络编程》学习笔记 | Chapter 15:套接字与标准 I/O
    • 标准 I/O 函数
      • 标准 I/O 函数的两个优点
      • 标准 I/O 函数和系统函数之间的性能对比
      • 标准 I/O 函数的几个缺点
    • 使用标准 I/O 函数
      • 利用 fdopen 函数转换为 FILE 结构体指针
      • 利用 fileno 函数转换为文件描述符
    • 基于套接字的标准 I/O 函数使用
    • 习题
      • (1)请说明标准I/O函数的2个优点。它为何拥有这2个优点?
      • (2)利用标准I/O函数传输数据时,下面的想法是错误的:“调用fputs函数传输数据时,调用后应立即开始发送!”为何上述想法是错误的?为了达到这种效果应添加哪些处理过程?

《TCP/IP网络编程》学习笔记 | Chapter 15:套接字与标准 I/O

标准 I/O 函数

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

除了使用 read 和 write 函数收发数据外,还能使用标准 I/O 函数收发数据。下面是标准 I/O 函数的两个优点:

  • 标准 I/O 函数具有良好的移植性
  • 标准 I/O 函数可以利用缓冲提高性能

不仅是I/O函数,所有的标准函数都具有很好的移植性,为了支持所有的操作系统和编译器,这些函数都是按照ANSI C标准定义的。

创建套接字时,操作系统会准备 I/O 缓冲。此缓冲在执行 TCP 协议时发挥着非常重要的作用。此时若使用标准 I/O 函数,将得到额外的缓冲支持。如下图:

在这里插入图片描述

套接字中的缓冲区主要是为了实现TCP协议而设立的,TCP在传输数据的过程中,如果丢失了数据,将会再次进行传输,而再次发送数据,意味着数据保存在了某个地方,并没有丢失,保存的地方就是套接字的输出缓冲区。与之相反,使用标准IO函数缓冲的主要目的是为了提高性能。

实际上,缓冲区并非在所有情况下都能带来卓越的性能,但是如果传输的数据量越大,有无缓冲带来的性能差异就越大。

标准 I/O 函数和系统函数之间的性能对比

下面是利用系统函数的示例:

#include <stdio.h>
#include <fcntl.h>
#include <cstdlib>
#include <ctime>
#include <unistd.h>

#define BUF_SIZE 3

// 采用未提供缓冲区技术的read,write方法进行拷贝文件

int main(int argc, char *argv[])
{
    int fd1, fd2; // 文件描述符
    int len;
    char buffer[BUF_SIZE];

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

    clock_t start = clock();

    while ((len = read(fd1, buffer, BUF_SIZE)) > 0)
        write(fd2, buffer, len);

    close(fd1);
    close(fd2);

    clock_t end = clock();

    double timespan = ((double)(end - start)) / CLOCKS_PER_SEC;

    printf("Total time consume is %f ms.\n", timespan * 1000);

    return 0;
}

输出:

在这里插入图片描述

如果是采用上述代码,数据传输的时间需要很长。

下面采用标准I/O函数复制文件:

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

#define BUF_SIZE 3

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

    char buffer[BUF_SIZE];

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

    if (fp1 == NULL || fp2 == NULL)
    {
        printf("Failed to open file.\n");
        return -1;
    }

    // 程序计数
    clock_t start = clock();
    while (fgets(buffer, BUF_SIZE, fp1) != NULL)
        fputs(buffer, fp2);

    fclose(fp1);
    fclose(fp2);

    clock_t end = clock();

    // 计算消耗时间
    double timespan = ((double)(end - start)) / CLOCKS_PER_SEC;
    printf("The total time consume is %f ms.\n", timespan * 1000);

    return 0;
}

输出:

在这里插入图片描述

该示例用标准IO函数fputs和fgets函数复制文件,是一种基于缓冲的复制,就很快。

标准 I/O 函数的几个缺点

标准 I/O 函数存在以下几个缺点:

  • 不容易进行双向通信。
  • 有时可能频繁调用 fflush 函数。
  • 需要以 FILE 结构体指针的形式返回文件描述符。

打开文件时,如果希望同时进行读操作,则应以 r+、w+、a+ 模式打开。但因为缓冲的缘故,每次切换读写工作状态时应调用fIush为数。这也会影响基于缓冲的性能提高。而且,为了使用标准IO函数,需要FILE结构体指针。而创建套接字时默认返回文件描述符,需要将文件描述符转化为FILE指针。

使用标准 I/O 函数

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

函数原型如下:

#include <stdio.h>

FILE *fdopen(int fildes, const char *mode);

成功时返回转换的 FILE 结构体指针,失败时返回 NULL。

参数:

  • fildes:需要转换的文件描述符
  • mode:将要创建的 FILE 结构体指针的模式信息

示例程序:

#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 error", stdout);
        return -1;
    }
    fp = fdopen(fd, "w"); // 返回写模式的 FILE 指针
    fputs("NetWork C programming \n", fp);
    fclose(fp);
    return 0;
}

编译,运行结果:

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>gcc desto.c -o desto

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>desto

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>cat data.dat
'cat' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>type data.dat
NetWork C programming

可以看出,fdopen 函数将文件描述符转换为 FILE 指针,并可以通过该指针调用标准 I/O 函数。

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

函数原型如下:

#include <stdio.h>

int fileno(FILE *stream);

成功时返回文件描述符,失败时返回 -1。

示例程序:

#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 error", stdout);
        return -1;
    }

    printf("First file descriptor : %d \n", fd);
    fp = fdopen(fd, "w"); // 转成 file 指针
    fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
    printf("Second file descriptor: %d \n", fileno(fp)); // 转回文件描述符
    fclose(fp);
    return 0;
}

编译,运行结果:

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>gcc todes.c -o todes

C:\Users\81228\Documents\Program\TCP IP Project\Chapter 15>todes
First file descriptor : 3
Second file descriptor: 3

输出的文件描述符值相同,证明 fileno 函数争取转换了文件描述符。

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

把第四章的回声客户端和回声服务端的内容改为基于标准 I/O 函数的数据交换形式。

echo_stdserv.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

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    FILE *readfp;
    FILE *writefp;

    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        error_handling("socket() error");

    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)
        error_handling("bind() error");

    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");

    clnt_adr_sz = sizeof(clnt_adr);
    // 调用 5 次 accept 函数,共为 5 个客户端提供服务
    for (i = 0; i < 5; i++)
    {
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1)
            error_handling("accept() error");
        else
            printf("Connect client %d \n", i + 1);

        readfp = fdopen(clnt_sock, "r");
        writefp = fdopen(clnt_sock, "w");
        while (!feof(readfp))
        {
            fgets(message, BUF_SIZE, readfp);
            fputs(message, writefp);
            // 刷新文件流中的缓冲区,将缓冲区中的数据强制写入文件
            fflush(writefp);
        }

        fclose(readfp);
        fclose(writefp);
    }
    close(serv_sock);
    return 0;
}

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

echo_client.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

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
    FILE *readfp;
    FILE *writefp;
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    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)
        error_handling("connect() error!");
    else
        puts("Connected...........");
    readfp = fdopen(sock, "r");
    writefp = fdopen(sock, "w");
    while (1)
    {
        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;
}

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

此处接收数据的缓冲区没有在结尾增加0,与第四章的不同,原因:使用标准I/O函数后可以按字符串单位进行数据交换。

printf 函数以参数" % s"输出字符串时过程为:

  1. 从首地址开始逐字节寻址,把存储单元(一个字节)内的数据转换为ASCII字符格式输出。
  2. 直到某一个字节内存的元素为字符’\0’时,输出此字符并且寻址结束。

习题

(1)请说明标准I/O函数的2个优点。它为何拥有这2个优点?

  • 标准 I/O 函数具有良好的移植性。因为这些函数都是按照ANSI C标准定义的,支持所有的操作系统和编译器。
  • 标准 I/O 函数可以利用缓冲提高性能。因为使用标准 I/O 函数,将得到额外的缓冲支持。可以降低传输的数据量,减少数据向输出缓冲移动的次数,这两种都能提升性能。

(2)利用标准I/O函数传输数据时,下面的想法是错误的:“调用fputs函数传输数据时,调用后应立即开始发送!”为何上述想法是错误的?为了达到这种效果应添加哪些处理过程?

通过标准输出函数的传输的数据不直接通过套接字的输出缓冲区发送,而是保存在标准输出函数的缓冲中,然后再用fflush函数进行输出。因此,即使调用fputs函数,也不能立即发送数据。如果想保障数据传输的时效性,必须经过fflush函数的调用过程。

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

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

相关文章

<OS 有关> ubuntu 24 不同版本介绍 安装 Vmware tools

原因 想用 apt-get download 存到本地 / NAS上&#xff0c;减少网络流浪。 看到 VMware 上的确实有 ubuntu&#xff0c;只是版本是16。 ubuntu 版本比较&#xff1a;LTS vs RR LTS: Long-Term Support 长周期支持&#xff0c; 一般每 2 年更新&#xff0c;会更可靠与更稳定…

支持多种快充协议和支持多种功能的诱骗取电协议芯片

汇铭达XSP15是一款应用于手持电动工具、智能家居、显示器、音箱等充电方案的大功率快充协议芯片&#xff0c;支持最大功率100W给设备快速充电&#xff0c;大大缩短了充电时间。芯片支持通过UART串口发送电压/电流消息供其它芯片读取。支持自动识别连接的是电脑或是充电器。支持…

【一篇搞定配置】网络分析工具WireShark的安装与入门使用

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;各种软件安装与配置_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1.…

JavaWeb之综合案例

前言 这一节讲一个案例 1. 环境搭建 然后就是把这些数据全部用到sql语句中执行 2.查询所有-后台&前台 我们先写后台代码 2.1 后台 2.2 Dao BrandMapper&#xff1a; 注意因为数据库里面的名称是下划线分割的&#xff0c;我们类里面是驼峰的&#xff0c;所以要映射 …

【STM32】MPU6050初始化常用寄存器说明及示例代码

一、MPU6050常用配置寄存器 1、电源管理寄存器1&#xff08; PWR_MGMT_1 &#xff09; 此寄存器允许用户配置电源模式和时钟源。 DEVICE_RESET &#xff1a;用于控制复位的比特位。设置为1时复位 MPU6050&#xff0c;内部寄存器恢复为默认值&#xff0c;复位结束…

隐私友好型分析平台Plausible Analytics

什么是 Plausible Analytics &#xff1f; Plausible Analytics 是一个简单、轻量级&#xff08;小于1KB&#xff09;、开源且隐私友好的网站分析工具&#xff0c;旨在作为 Google Analytics 的替代品。它不使用 cookies 并且完全符合 GDPR、CCPA 和 PECR 法规&#xff0c;因此…

Flutter:RotationTransition旋转动画

配置vsync&#xff0c;需要实现一下with SingleTickerProviderStateMixinclass _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{// 定义 AnimationController late AnimationController _controller;overridevoid initState() {super…

【大数据学习 | Spark-Core】Spark提交及运行流程

spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…

青训营刷题笔记16

问题描述 小R从班级中抽取了一些同学&#xff0c;每位同学都会给出一个数字。已知在这些数字中&#xff0c;某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。 测试样例 样例1&#xff1a; 输入&#xff1a;array [1, 3, 8, 2, 3, 1, 3, 3, 3] 输出…

C4D技巧总结

鼠标右键单击这两个小箭头可以把参数恢复到默认值&#xff01; 对象坐标 全局坐标 按住Alt键&#xff0c;点击挤压&#xff08;或者其他绿色的图标&#xff09;&#xff0c;可以快速形成父子级效果&#xff01;

(动画)Qt控件 QLCDNumer

文章目录 LCD Number1. 介绍2. 核心属性3 . 代码实现:倒计时1. 在界⾯上创建⼀个 QLCDNumber , 初始值设为 10.2. 修改 widget.h 代码, 创建⼀个 QTimer 成员, 和⼀个 updateTime 函数3. 修改 widget.cpp, 在构造函数中初始化 QTimer4. 修改 widget.cpp, 实现 updateTime 4. 动…

draggable的el-dialog实现对话框标题可以选择

请看图 这个对话框使用了el-dialog并且draggable属性设置成了true&#xff0c;所以标题栏这里就可以拖动&#xff0c;现在用户想选中标题栏的文本进而复制。我看到这个需求头都大了。 我能想到的方案有三个&#xff1a;1. 取消draggable为true 2. 标题文案后面加一个复制按钮 …

DeepSpeed-chat RLHF实战

轩辕-6bRLHF落地实战 模型介绍&#xff1a;轩辕-6B 模型库 (modelscope.cn) 1.1偏好数据集构建 ​ 1.1.1Prompt构建 1.1.2 Response生成 保证RM训练数据和测试数据分布一致 使用模型来生成response&#xff0c;为了评价response的质量&#xff0c;可以提高采样参数中的…

Java-05 深入浅出 MyBatis - 配置深入 动态 SQL 参数、循环、片段

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

Prompting LLMs to Solve Complex Tasks: A Review

文章目录 题目简介任务分解未来方向结论 题目 促使 LLM 解决复杂任务&#xff1a; 综述 论文地址&#xff1a;https://www.intjit.org/cms/journal/volume/29/1/291_3.pdf 简介 大型语言模型 (LLM) 的最新趋势显而易见&#xff0c;这体现在大型科技公司的投资以及媒体和在线社…

恋爱通信史之完整性

在前面的章节中&#xff0c;介绍了对通信消息的加密&#xff0c;可以保证保密性(机密性)。虽说中间人无法解密通信消息的内容&#xff0c;但是可以篡改通信的消息。在接受者视角来看&#xff0c;是无法识别通信消息是否被篡改。因此&#xff0c;必须引入一种机制&#xff0c;保…

Easyexcel(5-自定义列宽)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09; 注解 ColumnWidth Data…

C#调用C++ DLL方法之C++/CLI(托管C++)

托管C与C/CLI前世今生 C/CLI (C/Common Language Infrastructure) 是一种用于编写托管代码的语言扩展&#xff0c;它是为了与 .NET Framework 进行互操作而设计的。C/CLI 是 C 的一种方言&#xff0c;它引入了一些新的语法和关键字&#xff0c;以便更好地支持 .NET 类型和垃圾…

家庭智慧工程师:如何通过科技提升家居生活质量

在今天的数字化时代&#xff0c;家居生活已经不再只是简单的“住”的地方。随着物联网&#xff08;IoT&#xff09;、人工智能&#xff08;AI&#xff09;以及自动化技术的快速发展&#xff0c;越来越多的家庭开始拥抱智慧家居技术&#xff0c;将他们的家变得更加智能化、便捷和…

【Unity踩坑】出现d3d11问题导致编辑器崩溃

升级到Unity 6&#xff0c;有时出现下面这种D3D11的问题&#xff0c;会导致编辑器崩溃。 有人总结了这个问题的解决方法&#xff0c;可以做为参考&#xff1a; Failed to present D3D11 swapchain due to device reset/removed. List of Solutions - Unity Engine - Unity Dis…