分离I/O流
"流"分离带来的EOF问题
服务器端代码: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 *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"); //创建读模式FILE指针 writefp = fdopen(clnt_sock,"w"); //创建写模式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); fclose(writefp); //关闭写模式FILE指针,发送EOF fgets(buf,sizeof(buf),readfp); //使用readfp接收数据 fputs(buf,stdout); fclose(readfp); return 0; }
38行调用fclose函数后会发送EOF,下面的客户端收到EOF后也会发送最后的字符串。需要验证39行的函数调用能否接收。
客户端:sep_clnt.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 *argv[]) { int sock; char buf[BUF_SIZE]; struct sockaddr_in serv_addr; FILE * readfp; FILE * writefp; sock = socket(PF_INET,SOCK_STREAM,0); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); readfp = fdopen(sock,"r"); writefp = fdopen(sock,"w"); while(1) { if(fgets(buf,sizeof(buf),readfp) == NULL) //收到EOF时,fgets函数返回NULL指针,退出循环. break; fputs(buf,stdout); fflush(stdout); } fputs("FROM CLIENT: Thank you! \n",writefp); //发送最后的字符串 fflush(writefp); fclose(writefp); fclose(readfp); return 0; }
运行结果:
服务器端未能接收到最后发送的字符串。
因为服务器端38的fclose函数调用完全终止了套接字,而不是半关闭。
这就是本节需要解决的问题。
如何对fdopen函数调用时生成的FILE指针进行半关闭操作?
文件描述符的复制和半关闭
终止“流”时无法半关闭的原因
2个FILE指针-文件描述符-套接字之间的关系:
读模式和写模式的FILE指针都是基于同一个文件描述符创建的。因此对任意一个FILE指针调用fclose函数都会关闭文件描述符,也就是终止套接字。如图:
如何进入可以输入但无法输出的半关闭状态呢?如下图:创建FILE指针前先复制文件描述符即可
复制后另外创建一个文件描述符,然后利用各自的文件描述符生成读模式FILE指针和写模式FILE指针。
这时候,若针对写模式FILE指针调用fclose函数时,只能销毁与该FILE指针相关的文件描述符,无法销毁套接字:
但此时不是半关闭状态,只是准备好了半关闭环境。要进入真正的半关闭状态需要特殊处理。
剩下的1个文件描述符可以同时进行I/O,因此,不但没有发送EOF,而且仍然可以利用文件描述符进行输出。
复制文件描述符
文件描述符的复制与fork函数中进行的复制有所区别。文件描述符的复制是在同一进程内完成。
文件描述符的值不能重复,因此各使用5和7整数值。同一进程内2个文件描述符可以同时访问同一个文件或套接字。
dup & dup2
文件描述符的复制函数:
dup2函数明确指定复制的文件描述符整数值。向其传递大于0且小于进程能生成的最大文件描述符时,该值将成为复制出的文件描述符值。
示例:复制自动打开的标准输出的文件描述符1,并利用复制出的描述符进行输出。
#include<stdio.h> #include<unistd.h> int main(int argc,char *argv[]) { int cfd1, cfd2; char str1[] = "Hi~ \n"; char str2[] = "It's nice day~ \n"; cfd1 = dup(1); cfd2 = dup2(cfd1,7); printf("fd1 = %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函数无法输出 write(1,str2,sizeof(str2)); return 0; }
运行结果:dup.c
复制文件描述符后“流”的分离
更改sep_serv.c,使其能通过服务器端的半关闭状态接收客户端最后发送的字符串。为了完成这1任务。服务器端必须同时发送EOF。
服务器端: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 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(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"); //创建读模式FILE指针 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函数。服务器端进入半关闭状态,并向客户端发送EOF。 fclose(writefp); fgets(buf,sizeof(buf),readfp); //使用readfp接收数据 fputs(buf,stdout); fclose(readfp); return 0; }
客户端:sep_clnt.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 *argv[]) { int sock; char buf[BUF_SIZE]; struct sockaddr_in serv_addr; FILE * readfp; FILE * writefp; sock = socket(PF_INET,SOCK_STREAM,0); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); readfp = fdopen(sock,"r"); writefp = fdopen(sock,"w"); while(1) { if(fgets(buf,sizeof(buf),readfp) == NULL) //收到EOF时,fgets函数返回NULL指针,退出循环. break; fputs(buf,stdout); fflush(stdout); } fputs("FROM CLIENT: Thank you! \n",writefp); //发送最后的字符串 fflush(writefp); fclose(writefp); fclose(readfp); return 0; }
运行结果:
运行结果证明服务器端在半关闭状态下向客户端发送了EOF。
通过该示例希望掌握一点:
无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态。