参考
- 《TCP/IP网络编程》 尹圣雨
分离I/O流
两种I/O流分离
第一种是TCP I/O通过调用过程分离。fork函数复制一个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出来区分,但两个文件描述符的用途是分开的,所以也属于流的分离。
二是使用标准I/O函数时,通过两次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符合整数值的指定文件描述。dup当函数明确指定复制的文件描述符整数值时,该值将成为复制的文件描述符。
复制文件描述符后流动的分类
我们的目标是通过服务器端的半关闭状态接收客户端发送的最终字符串。为了完成此任务,服务器端需要同时发送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"); // 利用dup函数的返回值生成FILE指针
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); // 针对fileno函数返回的文件描述符调用shutdown函数
fclose(writefp);
fgets(buf, sizeof(buf), readfp);
fputs(buf, stdout);
fclose(readfp);
return 0;
}