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

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 30int 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 1024void 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"); //这里不要写错了,排错排了好久,操作的都是clntsockwhile(!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 1024void 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 1024int 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 1024int 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 1024int 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);//发送EOFfclose(writefp);char buf[BUF_SIZE];fgets(buf, sizeof(buf), readfp);fputs(buf, stdout);fclose(readfp);return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安卓游戏开发之图形渲染技术优劣分析

一、引言 随着移动设备的普及和性能的提升&#xff0c;安卓游戏开发已经成为一个热门领域。在安卓游戏开发中&#xff0c;图形渲染技术是关键的一环。本文将对安卓游戏开发中常用的图形渲染技术进行分析&#xff0c;比较它们的优劣&#xff0c;并探讨它们在不同应用场景下的适用…

关于 selinux 规则

1. 查看selinux状态 SELinux的状态&#xff1a; enforcing&#xff1a;强制&#xff0c;每个受限的进程都必然受限 permissive&#xff1a;允许&#xff0c;每个受限的进程违规操作不会被禁止&#xff0c;但会被记录于审计日志 disabled&#xff1a;禁用 相关命令&#xf…

manjaro 安装 wps 教程

内核: Linux 6.6.16.2 wps-office版本&#xff1a; 11.10.11719-1 本文仅作为参考使用, 如果以上版本差别较大不建议参考 安装wps主体 yay -S wps-office 安装wps字体 &#xff08;如果下载未成功看下面的方法&#xff09; yay -S ttf-waps-fonts 安装wps中文语言 yay …

upload-Labs靶场“11-15”关通关教程

君衍. 一、第十一关 %00截断GET上传1、源码分析2、%00截断GET上传 二、第十二关 %00截断POST上传1、源码分析2、%00截断POST上传 三、第十三关 文件头检测绕过1、源码分析2、文件头检测绕过 四、第十四关 图片检测绕过上传1、源码分析2、图片马绕过上传 五、第十五关 图片检测绕…

mysql学习笔记8——常用5个内置方法

1count 对查询内容进行计数&#xff0c;并返回结果 2as as可以将查询出来结果赋予新名字 3sum sum可以查询某字段特定条件下的和值 4concat concat可以将多列数据合并成一列&#xff0c;只要concat&#xff08;&#xff09;即可 5group_concat group_concat可以把多列…

Docker的镜像操作

目录 镜像的操作(**开头为常用请留意) 镜像查找 **拉取镜像 **推送镜像 **查看镜像 **修改镜像名称 **查看镜像的详细信息 ​编辑 删除镜像 查看所有镜像ID 删除全部镜像 **查看镜像的操作历史 镜像迁移 镜像打包 远程发送镜像(需要先打包) 本地镜像tar包恢复 镜像过…

【YOLO v5 v7 v8 v9小目标改进】辅助超推理SAHI:分而治之,解决高分辨率图像中小物体检测的问题

辅助超推理SAHI&#xff1a;分而治之&#xff0c;解决高分辨率图像中小物体检测的问题 设计思路结构小目标涨点YOLO v5 魔改YOLO v7 魔改YOLO v8 魔改YOLO v9 魔改 论文&#xff1a;https://arxiv.org/pdf/2202.06934.pdf 代码&#xff1a;https://github.com/obss/sahi 设计思…

文件MD5校验码的安全性及重要性

title: 文件MD5校验码的安全性及重要性 date: 2024/3/6 18:13:20 updated: 2024/3/6 18:13:20 tags: MD5原理文件校验下载验证数据库一致性安全性保障计算方法MD5安全防护 文件MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常用的哈希算法&#xff0c;用于验证…

基于OpenCV的图形分析辨认04

目录 一、前言 二、实验目的 三、实验内容 四、实验过程 一、前言 编程语言&#xff1a;Python&#xff0c;编程软件&#xff1a;vscode或pycharm&#xff0c;必备的第三方库&#xff1a;OpenCV&#xff0c;numpy&#xff0c;matplotlib&#xff0c;os等等。 关于OpenCV&…

SqlServer中连续号及断号查询—附源码

效果如下图所示&#xff1a; SqlServer中连续号及断号查询SQL如下&#xff1a; --1.定义临时表 DECLARE TestTemp TABLE(TestCode NVARCHAR(50),TestNum INT )DECLARE DataTemp TABLE(TestCode NVARCHAR(50),TestNumStr NVARCHAR(100) )--2.插入测试数据 INSERT INTO TestT…

C及C++每日练习(1)

一.选择&#xff1a; 1.以下for循环的执行次数是&#xff08;&#xff09; for(int x 0, y 0; (y 123) && (x < 4); x); A.是无限循环 B.循环次数不定 C.4次 D.3次 对于循环&#xff0c;其组成部分可以四个部分&#xff1a; for(初始化;循环进行条件;调整) …

基于Java的校园失物招领管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系统公告模块2.4 感谢留言模块 三、界面展示3.1 登录注册3.2 招领模块3.3 寻物模块3.4 公告模块3.5 感谢留言模块3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 校园失物招领…

Cyber RT 开发工具

在Cyber RT中还提供了一些工具&#xff0c;这些工具可以拓展Cyber RT功能、提高开发调试效率&#xff0c;本章主要介绍这些工具的使用。 本章内容: 1.cyber record工具的应用&#xff1b; 2.常用命令工具的使用&#xff1b; 学习收获: 1.可以通过cyber record将发布的话题消息…

因果学习篇(2)-Causal Attention for Vision-Language Tasks(文献阅读)

Causal Attention for Vision-Language Tasks 引言 这篇论文是南洋理工大学和澳大利亚莫纳什大学联合发表自2021年的CVPR顶会上的一篇文献&#xff0c;在当前流行的注意力机制中增加了因果推理算法&#xff0c;提出了一种新的注意力机制&#xff1a;因果注意力(CATT)&#xff…

AI智商排名:Claude-3首次突破100

用挪威门萨&#xff08;智商测试题&#xff09;中 35 个问题对chatGPT等人工智能进行了测试&#xff1a; ChatGPT 对ChatGPT进行了两次挪威门萨测试&#xff0c;在 35 个问题中&#xff0c;它平均答对了 13 个&#xff0c;智商估计为 85。 测试方法 每个人工智能都接受了两次…

2023第十届GIAC全球互联网架构大会:洞察未来互联网架构的革新与突破(附大会核心PPT下载)

随着互联网的迅猛发展&#xff0c;其底层架构的演进与革新成为了推动全球数字化进程的关键力量。2023年第十届GIAC全球互联网架构大会如期而至&#xff0c;汇聚了全球互联网架构领域的顶尖专家、学者、企业领袖和创新者&#xff0c;共同探讨和展望互联网架构的未来发展趋势。本…

RabbitMQ 基本介绍

RabbitMQ 基本介绍 消息模型 所有 MQ 产品从模型抽象上来说都是一样的过程&#xff1a; 消费者&#xff08;consumer&#xff09;订阅某个队列。生产者&#xff08;producer&#xff09;创建消息&#xff0c;然后发布到队列&#xff08;queue&#xff09;中&#xff0c;最后…

centos7安装maven离线安装

1、从官方网站下载maven文件包 官方下载网站&#xff1a;https://maven.apache.org/download.cgi 2、创建文件夹解压文件 将下载好的安装包&#xff0c;放到创建的目录下&#xff0c;并解压 a、创建/app/maven文件 mkdir /app/mavenb、解压文件 tar -zxvf apache-maven-…

C#使用whisper.net实现语音识别(语音转文本)

目录 介绍 效果 输出信息 项目 代码 下载 介绍 github地址&#xff1a;https://github.com/sandrohanea/whisper.net Whisper.net. Speech to text made simple using Whisper Models 模型下载地址&#xff1a;https://huggingface.co/sandrohanea/whisper.net/tree…

32、Redis 7系列:Spring Boot集成Redis

32、Redis 7系列&#xff1a;Spring Boot集成Redis 一、前言二、集成 RedisTemplate1、单机&#xff08;1&#xff09;新建项目&#xff08;2&#xff09;修改pom文件&#xff08;3&#xff09;修改yml文件&#xff08;4&#xff09;启动类&#xff08;5&#xff09;配置类&…