第十章用fork函数复制文件描述符的目的:
之前的章节介绍过EOF的传递方法和半关闭的必要性,shutdown函数可以实现半关闭还能发送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");//创建读文件指针
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(writefp);//关闭写文件指针(会发送EOF,并不会半关闭而是套接字输入输出全关闭)
fgets(buf, sizeof(buf), 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后fget会返回NULL指针,跳出循环
break;
fputs(buf, stdout);//输出信息到屏幕
fflush(stdout);
}
fputs("FROM CLIENT: Thank you! \n", writefp);//发送最后的消息
fflush(writefp);
fclose(writefp); fclose(readfp);
return 0;
}
通过运行上述程序发现并没有输出最后客户端发来的消息
读模式的FILE指针和写模式的FILE指针都是基于同一个文件描述符创建的,对任意一个FILE指针调用fclose函数时都会关闭文件描述符,所以不能实现半关闭。
那怎么实现半关闭?
只要创建FILE指针之前先复制文件描述符即可 利用两个文件描述符分别创建读模式和写模式FILE指针,对不同FILE指针的关闭,只能关闭对应的文件描述符,只有销毁所有文件描述符之后才能关闭套接字,所以可以实现I/O分离
这时候用fclose关闭掉一个文件描述符并不会实现半关闭,因为另一个套接字文件描述符可以完成独立的输入输出,而且因为套接字并没有销毁也不会发送EOF,所以要用shutdown强制实现半关闭(不能输入或者输出)并发送EOF。
调用fork函数会复制整个进程,因此同一进程不能同时有原件和副本,但是后面的函数不是针对整个进程而是文件描述符。 文件描述符的值不能重复,因此所用的整数值不同,实际上是为了访问同一文件和套接字而创建的另一个文件描述符,这里的“复制”不会复制包括文件描述符整数值在内的所有内容。
#include<unistd.h>
int dup(int fildes);
int dup2(int fildes,int fildes2);
成功返回复制的文件描述符,失败返回-1
filds 需要复制的文件描述符
fildes2 明确指定的文件描述符整数值
第二个dup2是可以明确指定文件描述符整数值,向其传递大于0且小于进程能够生成的最大文件描述符值,这个值将成为复制出的文件描述符值。
#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);//指定复制的文件描述符为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));//虽然前面关掉了两个文件描述符,但是还有1个能够输出
close(1);
write(1, str2, sizeof(str2));//无法输出
return 0;
}
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");//读模式指针
writefp=fdopen(dup(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);//强制刷新输出缓冲,确保信息尽快发送
shutdown(fileno(writefp), SHUT_WR);//关闭输出流,发送EOF,半关闭时要用shutdown(参数是文件描述符不是FILE指针),
//用shutdown的时候无论复制了多少文件描述符, 都会进入半关闭状态(无法发送或者接收),都会发送EOF
fclose(writefp);
fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); //仍然能够收到客户端最后发来的消息
fclose(readfp);//关闭输入流,此时用于服务客户端的套接字(clnt_sock)彻底关闭
return 0;
}
无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态,因为单纯的fclose只会关闭一个文件描述符,既不会实现半关闭,也不会发送EOF。
本文章使用limfx的vscode插件快速发布