文章目录

  • 1. 什么是I/O流分离?
  • 2. 为什么需要I/O分流
  • 3. 当我们用fclose()无法实现半关闭的原因
  • 4. 实现真正的半关闭

1. 什么是I/O流分离?

回忆我们之前写的程序,最开始写的程序只要获得一个套接字并成功连接服务器,就能实现与服务器之间的数据交换,而这个数据交换从客户端角度看就是发送数据与接收数据; 而这个收发数据的过程称为流。而I/O流分离是指接收数据的流与发送数据的流实现分离,这里的分离指的是操作彼此隔离,一个流关闭不影响另外一个流的使用。这才是I/O流分离的真正理解。其实在这之前我们已经实现了几次I/O流分离,一次是使用fork()实现多线程服务端回声服务器那一章,只不过输入流与输出流分布在不同的进程当中;另外一次是在第十五章、通过将套接字文件描述符转化成两个FILE结构体指针(一个负责写、一个负责读),实现了读写流之间的分流。但值得注意的是这两次流的分离存在一定的差异,主要是流分离的目的不同:

使用多进程实现流分离是为了通过分开输入过程与输出过程简化代码降低难度;使用FILE结构体指针实现I/O分流通过区分I/O缓冲提高缓冲的性能。

2. 为什么需要I/O分流

这一块在上面一小节略讲了以下,主要有以下优点:

  • 分开输入过程与输出过程降低实现难度。
  • 与输入无关的输出操作可以 提高速度。
  • 可以通过区分读写模式降低实现难度。
  • 通过区分I/O缓冲提高缓冲性能。

当然,流分离带来的问题也不小,主要是如何实现半关闭? 在之前我们通过shutdown函数实现半关闭,但是这是在没有使用标准I/O流的情况下进行半关闭,如果在第十五章的情况下(即将套接字转换成FILE结构体指针)如何实现半关闭?我们可以试着去猜测:半关闭?在标准I/O函数中有一个fclose()函数,这个函数不就是可以让FILE结构体指针调用并实现半关闭吗?

这个猜测我们接着去用实验看看是不是跟我们想的那样能实现半关闭。

//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 const *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()函数实现关闭流,看看能不能实现关闭。fclose(writefp);fgets(buf,sizeof(buf),readfp);fputs(buf,stdout);fclose(readfp);return 0;
}//sep_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 1024int main(int argc, char const *argv[])
{int sock;struct sockaddr_in serv_adr;char buf[BUF_SIZE];FILE* readfp;FILE* writefp;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=inet_addr(argv[1]);serv_adr.sin_port=htons(atoi(argv[2]));connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr));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;
}

测试结果:

//客户端输出:
FROM SERVER: Hi~ client?
I love all of the world
You are awesome!
//服务端没有收到任何来自客户端的内容

结果分析:

可知,fclose()函数不能实现半关闭。原因在于当服务端调用fclose()试图去关闭写端(writefp)时就已经将整套接字给终止了,因此无法收到来自客户端的消息。

3. 当我们用fclose()无法实现半关闭的原因

在这之前我们需要理清楚一个容易忽略的事实,我们所说“创建套接字”是指在计算机内部创建一种结构体,这个结构体(数据格式)能够实现网络数据的交换,类似创建一个文件。套接字只有一个,但是套接字文件描述符可以有多个,就像一个文件可以有多个链接(软链接)类似。没有文件描述符,套接字就无法使用 ,即使在计算机中依然存在。好,理解上面这段话我们接下来看一组图来说明为啥之前猜测是错误的。

我们上面程序中设置的读写FILE结构体指针与文件描述符之间是如下图的关系。

当我们使用fclose()关闭写模式时,同时也会关闭相关的文件描述符,如下所示。

之所以连套接字也注销了,是因为系统会自动注销没有文件描述符指向的套接字。现在知道原因了吧?原因就是读写FILE结构体指针都是指向同一个文件描述符,当我们希望关闭其中一种模式,不影响另外一种模式使用的情况下完成半关闭的目的。因此弄清楚原因,解决方案也显而易见了,就是多复制一个文件描述符指向同一个套接字,不同的套接字指向各自的文件描述符。

当我们关闭其中一种模式,也就删除了一个文件描述符,但是由于并没有完全删除套接字的其他的描述符,套接字不会因此消失。

文件描述符复制函数

#include<unistd.h>
int dup(int fildes);//成功返回复制的文件描述符(系统自动分配)
int dup2(int fildes,int fildes2)//fildes:复制文件描述母本//fildes2:明确指定希望得到的文件描述符(即由开发者自己选择)

也许不太明白,我们通过小程序实现看看

//dup.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int cdf1,cfd2;char str1[]="Hi~ \n";char str2[]="It's nice day~ \n";cdf1=dup(1);cfd2=dup2(cdf1,8);printf("cfd1=%d   cfd2=%d  \n",cdf1,cfd2);write(cdf1,str1,sizeof(str1));write(cfd2,str2,sizeof(str2));close(cdf1);close(cfd2);write(1, str1,sizeof(str1));close(1);write(1,str2,sizeof(str2)); return 0;
}

运行结果:

cfd1=3   cfd2=8
Hi~
It's nice day~
Hi~

4. 实现真正的半关闭

既然了解了解决方案,那我们直接看代码。

//sep_serv2.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 const *argv[])
{int serv_sock,clnt_sock;FILE* readfp,*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");/*** @brief  利用dup复制文件描述符* @note   * @retval */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);//关闭写模式,fileno函数是将FILE文件指针转化成对应的文件描述符,这就是给客户端发送EOF(终止连接)的方法,值得注意的是,不论复制了多少文件描述符,一旦调用了shutdown 函数,都会进入半关闭状态。shutdown(fileno(writefp),SHUT_WR);fclose(writefp);fgets(buf,sizeof(buf),readfp);fputs(buf,stdout);fclose(readfp);return 0;
}
//客户端依然是上面的sep_client.c

运行结果

//客户端返回结果
FROM SERVER: Hi~ client?
I love all of the world
You are awesome!
//服务端返回结果
FROM CLIENT: Thank you~

当服务端发送完字符串之后关闭了写端,但是读端依然接收到了来自客户端发来的信息,并显示在终端。

记住:“无论复制出多少文件描述符,均应该调用shutdown函数发送EOF并进入半关闭状态”。

十六、关于IO流分离的内容相关推荐

  1. 第十五章 IO流(转换流 字符流 字符缓冲流 打印流)

    Java基础15 第十五章 IO流(转换流 字符流 字符缓冲流 打印流) 15.1 字符编码和字符集 15.1.1 字符编码 15.1.2 字符集 15.1.3 String类getBytes()方法 ...

  2. IO流---复制文件内容

    IO流:用来进行设备间的数据传输问题. IO流根据流向可以分为输入流和输出流,其中输出流,从一个设备向一个设备传数据,自然是先读取数据,所以输出流既是读取,同理输入流既是写入. IO流又根据数据类型分 ...

  3. JAVA(十八)IO流(十六)补充版

    目录 常用流对象 1.文件字节流 1.1文件字节输入流 1.2文件字节输出流 1.3通过缓冲区提高读写效率 1.3.1方法一 1.3.2方法二 1.4字节缓冲流 1.5定义文件拷贝工具类 2.文件字符 ...

  4. Java Review(三十六、IO)

    文章目录 File 类 访问文件和目录 1.访问文件名相关的方法 2. 文件检测相关的方法 3. 获取常规文件信息 4. 文件操作相关的方法 5. 目录操作相关的方法 文件过滤器 IO流概览 流的分类 ...

  5. 十五、IO流【黑马JavaSE笔记】(本文文中记录了个人学习感受)

    文章目录 IO流 (一)File 1.File类的概述和构造方法 2.File类的创建功能 3.File类判断和获取功能 4.File类的删除功能 5.递归 6.案例(递归求阶乘) 7.案例(遍历目录 ...

  6. python123 io平台-Java Review(三十六、IO)

    Java 的 IO 通过 java.io 包下的类和接口来支持, 在 java.io 包下主要包括输入. 输出两种 10 流, 每种输入. 输出流又可分为字节流和字符流两大类. 其中字节流以字节为单位 ...

  7. 零基础学Python(第十八章 文件IO流操作)

    本套学习内容共计[22]个章节,每个章节都会有对应的从0-1的学习过程详细讲解,希望可以给更多的人提供帮助. 开发环境:[Win10] 开发工具:[Visual Studio 2019] 本章内容为: ...

  8. 我的Java学习笔记(六)----IO流

    IO流 1. File 1.1 File类概述和构造方法 File:它是文件和目录路径名的抽象表示 文件和目录是可以通过File封装成对象的 对于File而言,其封装的并不是一个真正存在的文件,仅仅是 ...

  9. Java基础(十五)IO流---字符流(Reader、Writer)、字节流(InputStream、OutputStream)

    IO流(应用广泛) 1.概念与三要素 本质是一套用于数据传输的机制 分类: 根据传输的方向(参照物–内存) 输入流:往内存传输数据 输出流:从内存往外传输数据 根据数据传输方式: 字符流:底层以字符形 ...

最新文章

  1. 机器学习的前期入门汇总
  2. 使用wdcp面板安装感恩教师节wordpress网站
  3. 推荐:一个VS插件——CopySourceAsHtml
  4. 最详细的vue-cli工具构建vue项目教程
  5. 如何修改myeclipse中web项目的工作路径或默认路径
  6. html留言回复评论页面模板,HTML5实现留言和回复的页面样式
  7. Android逆向笔记-4种方式破解下例中的smali代码
  8. 【LeetCode】【HOT】104. 二叉树的最大深度(BFS+队列/递归)
  9. Bringing up interface eth0: Error: Connection activation failed: Device not managed by NetworkMan
  10. [选择性翻译][HDP Ambari 2.2.2安装使用说明][1]
  11. Solr -- 实时搜索
  12. 拓端tecdat|Python使用矩阵分解法找到类似的音乐
  13. f(x)的泰勒(Taylor)展开式
  14. 深度学习-8.实践方法论
  15. 人民币对美元汇率中间价报6.7802元 上调167个基点
  16. matlab 矩阵维度必须一致,错误使用 /
  17. Java基础IO系列之ByteArrayInputStream和ByteArrayOutputStream解析
  18. 实验室5位直博生每人一篇 Science!她再获颁“世界杰出女科学家奖”
  19. 汇编实验1 两个多位十进制数相加的实验
  20. 国外.net开源程序

热门文章

  1. Android 设备adb连接后unauthorized解决方法
  2. 北京新高考加分规则公布 两类考生最多可加20分
  3. Controler和RestController注解的区别
  4. 千万级PV规模高性能高并发网站架构详解
  5. 常用配置文件-toml文件
  6. 删除elemnt UI ——el-popper文字提示的小三角
  7. Keil MDK终于免费了,没有代码大小限制
  8. Qt Button控件的属性说明
  9. kali下扫描CMS web漏洞小工具web-sorrow
  10. 建站之旅——Nginx代理服务器配置域名