第一次:TCP的I/O过程分离:
- 通过fork函数复制出一个文件描述符,以区分输入和输出中使用的文件描述符;
- 文件描述符本身不会区分输入输出,但是分开了2个文件描述符的作用,也属于流的分离;
第二次:通过两次fdopen函数调用:
- 创建读模式是FILE指针和写模式FILE指针;
对于TCP的I/O流分离的目的:
- 通过分开输入过程代码和输出过程降低实现难度;
- 与输入无关的输出操作可以提高速度;
对于两次调用fdopen函数分离流的目的:
- 为了将FILE指针按照读模式和写模式加以区分;
- 可以通过区分读写模式降低实现难度;
- 通过区分I/O缓冲提高缓冲性能;
对于TCP的I/O流分离带来的流终止问题:采用EOF的传递方法和版关闭的必要性:
shutdown(sock, SHUT_WR);
对于基于fdopen函数的流则不同,我们不清楚如何情况下进行半关闭,因此可能犯如下错误:
- 半关闭?针对输出模式的FILE指针调用fclose函数?这样可以相对想传递EOF,编程可以接收数据但是无法发送数据的版关闭状态!
基于fdopen函数进行流分离,但对版关闭理解不清楚可能的错误:实例见Code/sep_serv.cpp, Code/sep_clnt.cpp;
具体表现为:服务器端不能接受到客户端发来的数据;
- sep_serv.cpp中调用的fclose函数完全终止了套接字,并不是半关闭;
- 就是说只要调用了fclose函数,就会关闭它相关的套接字!你可以测试下将fclose(readfp)替换fclose(writefp)效果相同!都会传递EOF,并完全关闭套接字!
- 上图描述了FILE指针同文件描述符/套接字之间的关系;
- 可以发现:只要调用fclose函数关闭任意一个FILE指针都将关闭套接字!
因为上面的情况销毁了套接字,导致无法进行数据交换!
那么如何进入可以输入但无法输出的半关闭状态呢?
- 只要创建FILE指针前复制文件描述符即可;
- 复制后,另外创建一个文件描述符,然后分别利用各自的文件描述符生成读模式FILE指针和写模式FILE指针;
上面的内容都是基于套接字和文件描述符之间的关系:销毁所有文件描述符后才能销毁套接字;
- 关闭写模式的FILE指针只能关闭对应的文件描述符;
此时是否已经进入了半关闭模式呢?
- 并没有!因此还有”原件“这个文件描述符,它可以独立同时的进行I/O;
- 此时不但没有发送EOF,并可以利用文件描述符进行输入输出;
那么如何进入半关闭状态呢?
- 对需要半关闭的那中模式(读或写)的FILE指针通过fileno函数转换为文件描述符,然后调用shutdown函数即可;
- shutdown的文件描述符:必须由进行写操作的FILE指针调用fileno函数转换而来!否则无效!
- shutdown后,该文件描述符所指向的对象(socket、file)的该操作都将被关闭!即不能通过readfp指针来获取其文件描述符后,在获得写操作!但是转换是允许的,而且能够转换出写的FILE指针,但是不能写成功!因为对象的该操作被关闭了!
- fork函数进行的复制,会复制整个进程,因此同一进程内无法同时具有原件和副本。
我们需要在同一进程内完成描述符的复制:
- 复制文件描述符,并不是说复制了它的值;
- 复制的含义:为了访问同一文件或套接字,创建另一个文件描述符;
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
- fildes:需要复制的文件描述符;
- 明确指定的文件描述符整数([0, max));
- 成功返回复制的文件描述符,失败返回-1;
使用实例:Code/dup.cpp;
- 注意里面的标准输出文件描述符1时默认自动打开;
- 自动打开的文件描述符0,1,2与其他套接字文件描述符没有区别;
使用实例:Code/sep_serv2.cpp;
无论复制出多少文件描述符,调用shutdown函数发送EOF后都将进入半关闭状态!;(原文我觉得描述的不对!应该是翻译的锅!这个就理解了!)