TCP/IP网络编程(11) 套接字和标准IO

news2024/11/18 17:39:48

标准IO函数的优点

标准IO函数具备两大优点:

  • 标准IO函数具备良好的移植性
  • 标准IO函数可以利用缓冲提高性能

不仅是IO函数,所有的标准函数都具有很好的移植性,为了支持所有的操作系统和编译器,这些函数都是按照ANSI C标准定义的; 此外,使用标准IO函数会得到额外的缓冲支持。这里的缓冲区应该与套接字的缓冲区进行区分,避免混淆。再创建套接字的时候,系统会生成用于IO的缓冲,此缓冲在执行TCP协议的时候发挥着非常重要的作用。如果此时使用标准IO函数,将得到除此之外的另一缓冲区支持。

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

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

采用不带缓冲的read/write函数进行文件读写:

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

#define BUFF_SIZE 3

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

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

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

    clock_t start = clock();

    len = read(fd1, buffer, BUFF_SIZE);

    while (len > 0)
    {
        /* code */
        write(fd2, buffer, len);
        len = read(fd1, buffer, BUFF_SIZE);
    }

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

文件读取以及写入时间如下所示:

标准IO进行文件内容读取:

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

#define BUFF_SIZE 3


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

    char buffer[BUFF_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();

    char* res = fgets(buffer, BUFF_SIZE, fp1);

    while (res != NULL)
    {
        /* code */
        fputs(buffer, fp2);

        res = fgets(buffer, BUFF_SIZE, fp1);
    }

    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函数具备更快的读写速度

标准IO函数的缺点:

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

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

利用标准IO函数实现套接字通讯

在创建套接字的时候会返回文件描述符,而为了使用标准IO函数,需要将其转换为FILE结构体指针,同时在某些应用场合,需要将FILE结构体指针转换为文件描述符,相互转换方法如下:

利用fdopen函数将文件描述符转换为FILE结构体指针

#include <stdio.h>

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

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

mode参数的形式与fopen函数的打开模式参数相同,常用的模式有读模式(r), 写模式(w)

示例代码

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

int main(int argc, char* argv[])
{
    FILE* fp;
    
    int fd = open("data.txt", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd < 0)
    {
        fputs("Faled to open file.\n", stdout);
        return -1;
    }

    fp = fdopen(fd, "w");   // 返回写模式的FILE指针

    if (fp == NULL)
    {
        fputs("Faled to convert file descriptor.\n", stderr);
        return -1;
    }

    fputs("This is a test content.\n", fp);    // 调用标准输出函数写入文件

    fclose(fp);

    return 0;
}

注: 利用FILE指针关闭文件,会使文件完全关闭,无需再通过文件描述符进行关闭,因为调用fclose函数之后,文件描述符已经变成毫无意义的整数。

利用fileno函数将FILE结构体指针转换为文件描述符:

在某些应用场合,需要将FILE结构体指针转换为文件描述符,可通过fileno函数进行抓换

#include <stdio.h>

int fileno(FILE* stream)

示例代码:

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

int main(int argc, char* argv[])
{
    FILE* fp;
    
    int fd = open("data.txt", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd < 0)
    {
        fputs("Faled to open file.\n", stdout);
        return -1;
    }

    printf("The first file descriptor is %d.\n", fd);

    fp = fdopen(fd, "w");

    if (fp == NULL)
    {
        fputs("Faled to convert file descriptor.\n", stderr);
        return -1;
    }

    fputs("This is a test content.\n", fp);

    printf("The second file descriptor is %d.\n", fileno(fp));

    fclose(fp);

    return 0;
}

输出结果:

 基于套接字的标准IO函数使用

下面以回声服务器为例,介绍将标准IO函数应用于套接字的实例:
服务端示例代码:

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

#define BUF_SIZE 10

#define SERVER_ADDR  "127.0.0.1"
#define SERVER_PORT  12000

void error_handler(const char* msg);

int main(int argc, char* argv[])
{
    int serverSock;
    int clientSock;

    char buffer[BUF_SIZE];

    struct sockaddr_in servAddr;
    struct sockaddr_in clientAddr;

    socklen_t addrSize = sizeof(servAddr);

    FILE* readfp;   
    FILE* writefp;

    serverSock = socket(PF_INET, SOCK_STREAM, 0);

    if (serverSock == -1)
    {
        error_handler("Failed to create socket.");
    }

    // 服务端地址初始化
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(SERVER_PORT);

    if (bind(serverSock, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1)
    {
        // failed to bind the socket
        error_handler("Failed to bind the server address.");
    }

    if (listen(serverSock, 5) == -1)
    {
        error_handler("Failed to listen.");
    }

    while (true)
    {
        /* code */
        fputs("Waiting for client connection...\n", stdout);
        clientSock = accept(serverSock, (struct sockaddr*)&clientAddr, &addrSize);

        if (clientSock == -1)
        {
            continue;
        }

        // fputs("Connected client %s.\n", inet_ntoa(clientAddr.sin_addr), stdout);
        printf("Connected client %s.\n", inet_ntoa(clientAddr.sin_addr));

        readfp  = fdopen(clientSock, "r");
        writefp = fdopen(clientSock, "w");

        while (!feof(readfp))
        {
            fgets(buffer, BUF_SIZE, readfp);    // 标准IO读取
            fputs(buffer, writefp);
            fflush(writefp);      // 保证数据立即传输到客户端
        }

        fclose(readfp);
        fclose(writefp);
    }

    close(serverSock);
    
    return 0;
}

void error_handler(const char* msg)
{
    printf("%s\n", msg);
    exit(-1);
}

客户端示例代码:

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

#define BUF_SIZE 10
#define SERVER_ADDR  "127.0.0.1"
#define SERVER_PORT  12000


void error_handler(const char* msg);

int main(int argc, char* argv[])
{
    int sock;
    char buffer[BUF_SIZE];

    struct sockaddr_in servAddr;

    FILE* readfp;
    FILE* writefp;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if (sock == -1)
    {
        error_handler("Failed to create sock");
    }

    memset(buffer, 0 , BUF_SIZE);

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
    servAddr.sin_port = htons(SERVER_PORT);

    if (connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1)
    {
        error_handler("Failed to connect to server.\n");
    }

    printf("Connecting to server: %s : %d", inet_ntoa(servAddr.sin_addr), ntohs(servAddr.sin_port));

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

    while(true)
    {
        fputs("Input message(Q or q to quit):", stdout);
        fgets(buffer, BUF_SIZE, stdin);

        if (!strcmp(buffer, "q\n") || !strcmp(buffer, "Q\n"))
        {
            break;
        }

        // 向服务端写数据
        fputs(buffer, writefp);
        fflush(writefp);
        
        // 清空缓冲区
        memset(buffer, 0, BUF_SIZE);

        // 读取父服务端数据
        fgets(buffer, BUF_SIZE, readfp);
        printf("Message from server is: %s\n", buffer);
    }

    fclose(readfp);
    fclose(writefp);

    return 0;
}

void error_handler(const char* msg)
{
    printf("%s\n", msg);
    exit(-1);
}

标准IO函数为了提高性能,内部提供额外的缓冲区,因此,若不调用fflush函数,则无法保证立即将数据传输到对端。使用标准IO函数还有一个好处,就是可以按字符串单位进行交换,而在前面的回声服务器的示例代码中,接收到的数据需要先转换为字符串(在数据的尾部插入0)。

...

write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE-1);
message[strlen] = 0;
printf(""Message from server is: %s\n, message);
...

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

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

相关文章

大数据和数据可视化为什么这么受欢迎?

“大数据”已经从一个概念性质的词语转变为了对经济社会各个领域都具有渗透影响的事物&#xff1b;并且随着互联网技术的持续发展&#xff0c;大数据所产生的影响呈现出了范围不断扩大&#xff0c;深度持续扩展的新特征。 大数据时代就是指人们的生产、生活和工作中出现更多的…

windows VS2015 Steup 打包发布软件并附带动态链接库dll

打包&#xff1a; 方法一&#xff1a;VS 2015 Steup 打包发布软件_柠檬野生菌的博客-CSDN博客_vs2015 exe发布 1、插件下载Microsoft Visual Studio 2015 Installer Projects 工具->扩展与更新->联机->搜索 Installer Projects 2、安装 关掉VS 双击下载好的 .exe …

ArcGIS基础实验操作100例--实验21按区域修改栅格值

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验21 按区域修改栅格值 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

8、多线程

文章目录8、多线程8.1 线程中的进程8.2 进程中的线程8.3 自己的第一个线程8.4 线程的生命周期8.5 线程执行方式&#xff08;串行和并发&#xff09;8.6 线程休眠8.7 工作8.8 线程池8.9 同步8.10 wait和sleep8.11 线程安全问题Java零基础极速入门-讲师&#xff1a;海波 失败&…

一文快速上手Swagger

目录 1.什么是Swagger&#xff1f; 2.Swagger的作用&#xff1f; 3.Swagger的使用方法 &#xff08;1&#xff09;一般是在Spring Boot项目中进行集成&#xff0c;只需在项目中引入springfox-swagger2和springfox-swagger-ui依赖即可 &#xff08;2&#xff09;配置Swaage…

[虚幻引擎][UE][UE5]在UE中画一个线框球,网格连接画球,高阶画球并操控

[虚幻引擎][UE][UE5]在UE中画一个线框球&#xff0c;网格连接画球&#xff0c;高阶画球并操控1.材质法2.绘制调试球体3.网格连接4.高阶画球并操控1.材质法 缺点&#xff1a;是实体的&#xff0c;只能欺骗视觉&#xff0c;实际还是一个实体体积球&#xff0c;往里放东西显示不出…

电磁兼容测试整改

1.1 什么时候需要电磁兼容整改及对策 在设计阶段就应考虑电磁兼容性&#xff0c;将产品生产阶段出现电磁兼容问题可能性减小。最终要通过电磁兼容测试检验其电磁兼容标准的符合性。 由于电磁兼容的复杂性&#xff0c;即使电磁兼容设计问题考虑比较周全&#xff0c;在设计制造…

SpringBoot+AOP+自定义注解,实现日志记录/权限验证

目录自定义注解简介AOP实现日志记录1.导入依赖2.创建自定义注解3.编写切面类4.编写测试接口5.测试AOP实现权限验证1.创建自定义注解2.编写切面类3.编写测试接口&#xff1a;4.测试自定义注解简介 为什么要通过AOP来实现日志记录 在业务处理中&#xff0c;我们经常需要将一些用…

基于springboot家政管理系统

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

Python数据容器--字符串(str)

1、字符串的定义 字符串是字符的容器&#xff0c;一个字符串可以存放任意数量的字符。 如&#xff1a;字符串&#xff1a;“itheima”。 2、字符串的相关操作 2.1 字符串的下标索引取值 从前向后&#xff0c;下标从0开始。从后向前&#xff0c;下标从-1开始。 my_str &quo…

【LeetCode每日一题】——611.有效三角形的个数

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 二分查找 二【题目难度】 中等 三【题目编号】 611.有效三角形的个数 四【题目描述】 给定一…

数据库管理-第五十期 半百(20221230)

数据库管理 2022-12-30第五十期 半百1 半百2 展望3 计划总结第五十期 半百 1 半百 不知不觉&#xff0c;来到了第五十期&#xff0c;自从立了flag以后也几乎保持了一周一篇文章的频率&#xff0c;当然不得不承认&#xff0c;文章质量还是参差不齐的&#xff0c;有好几篇还是挺…

海尔智家:科技引领的目的是让用户生活幸福

随着时代发展&#xff0c;科技从未像今天这样影响着每个人的“幸福感”。因为科技&#xff0c;我们的通讯可以“随时随地”&#xff0c;交通出行可以“咫尺之间”&#xff0c;购物消费可以“跨越国界”......每一项引领科技的诞生&#xff0c;都能让用户的幸福生活更进一步。 …

git远程仓库下载以及上传

一、远程仓库 通常我们并不常亲自建立远程仓库&#xff0c;而是委托给『第三方』。 常见的操作是&#xff0c;先在第三方上创建『远程 Git 文档库』&#xff0c;然后复制&#xff08;Clone&#xff09;出一个『本地 Git 文档库』 1、在托管网站上创建远程仓库 常见的代码托管…

CDGA|自然资源数据治理五部曲,实现数据融合关联、共享可用

在自然资源行业&#xff0c;数据治理是自然资源信息化的工作基础&#xff0c;是推进自然资源治理体系和治理能力现代化进程的必经之路。 为积极应对数据治理新要求&#xff0c;着力解决资源分散、质量不齐、关联不高等问题&#xff0c;围绕自然资源治理对象&#xff0c;构建集…

有哪些好用的设备巡检类的软件?

现今许多企业&#xff0c;尤其是制造业&#xff0c;规模日趋机械化、自动化、大型化、高速化和复杂化&#xff0c;对设备巡检工作的要求越加精细。 因此&#xff0c;选择好的设备巡检软件&#xff0c;是设备管理的关键。为企业提供设备巡检的解决方案&#xff0c;确保设备巡检…

2022国内网络安全事件大盘点

转眼又到了年底&#xff0c;回望过去的一年&#xff0c;网络攻击事件依旧层出不穷&#xff0c;尤其以俄乌战争为代表&#xff0c;网络空间俨然已经成了第二战场。再回看国内&#xff0c;网络攻击导致的大规模数据泄露事件不但给企业带来巨额财务和品牌损失&#xff0c;同时随着…

项目实战-----产品经理要做什么?

文章目录写在前面&#xff1a;兴奋地开始干我在AR的第一个彻头彻尾的需求&#xff0c;发现原来产品经理脑袋竟然要转这么多圈&#xff0c;果然是术业有专攻啊~&#xff0c;但是既然要成为一个全栈工程师&#xff0c;我也来挑战一下吧~第一版显示我学的课还有我教的课&#xff0…

数据报告 | 美国民众健康状况和医疗需求研究报告

在美国&#xff0c;自2020年COVID-19流行开始&#xff0c;以数字健康为关键词的医疗领域正在发⽣⾰命性的变化。全球范围发生的疫情&#xff0c;促进了医疗行业的变革与创新。 本研究将从美国医疗费用和民众疾病等方面&#xff0c;对美国民众健康状况和医疗需求进行分析。 |美…

Android Kotlin之Coroutine(协程)详解

协程是一种并发设计模式&#xff0c;您可以在 Android 平台上使用它来简化异步执行的代码。 在 Android 上&#xff0c;协程有助于管理长时间运行的任务&#xff0c;如果管理不当&#xff0c;这些任务可能会阻塞主线程并导致应用无响应。 协程的优点&#xff1a; 轻量 您可以…