参考
- 《TCP/IP网络编程》 尹圣雨
分离I/O流
两种I/O流分离
第一种是TCP I/O过程分离。这种方法通过调用fork函数复制出1个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但分开了2个文件描述符的用途,因此也属于流的分离。
第二种是使用标准I/O函数时,通过2次fdopen函数的调用,创建读模式FILE指针和写模式FILE指针,分离了输入工具和输出工具,也可以视为流的分离。
分离流的好处
第一种分离流的方式的好处:
- 通过分开输入过程(代码)和输出过程降低实现难度
- 与输入无关的输出操作可以提高速度
第二种分离流的方式的好处:
- 为了将FILE指针按读模式和写模式区分
- 可以通过区分读写模式降低实现难度
- 通过区分I/O缓冲提高缓冲性能
分离流带来的EOF问题
第一种分离流的方式没有问题,但是第二种分离流的方式有问题。
虽然表面上看可以针对输出模式的FILE指针调用fclose函数,向对方传递EOF,编程可以接收数据但无法发送数据的半关闭状态,但实际上,fclose(writefp)完全终止了套接字,而不是半关闭。
终止流时无法半关闭的原因:读模式FILE指针和写模式FILE指针都是基于同一文件描述符创建的。因此,针对任意一个FILE指针调用fclose函数时都会关闭文件描述符,也就终止套接字。
文件描述符的复制和半关闭
为了实现可以输入但无法输出的半关闭状态,创建FILE指针前先复制文件描述符即可。然后,利用各自的文件描述符生成读模式FILE指针和写模式FILE指针。因为,销毁所有文件描述符后才能销毁套接字。即针对写模式FILE指针调用fclose函数,只能销毁与该FILE指针相关的文件描述符,无法销毁套接字。
但实际上,剩余的与读模式FILE指针相关的文件描述符还是可以同时进行I/O,因此,仅仅如此还不能实现半关闭。还需要使用shutdown函数。
复制文件描述符
与fork函数不同,调用fork函数将复制整个进程,因此同一进程内不能同时有原件和副本。如果在同一进程内完成文件描述符的复制,需要dup或dup2函数。
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
成功时返回复制的文件描述符,失败时返回-1。其中,fildes为需要复制的文件描述符,fildes为明确指定的文件描述符整数值。dup2函数明确指定复制的文件描述符整数值,向其传递大于0且小于进程能生成的最大文件描述符值时,该值将成为复制出的文件描述符值。
复制文件描述符后进行流的分类
我们的目标是,通过服务器端的半关闭状态接收客户端最后发送的字符串。为了完成这样任务,服务器端需要同时发送EOF。
需要注意的是,无论复制出了多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态,因此代码中shutdown(fileno(writefp), SHUT_WR); 实现了服务器的半关闭状态,并向客户端发送EOF
#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* 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(atol(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(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);
shutdown(fileno(writefp), SHUT_WR);
fclose(writefp);
fgets(buf, sizeof(buf), readfp);
fputs(buf, stdout);
fclose(readfp);
return 0;
}
|