TCP/IP网络编程 (十六):关于I/O流分离的其他内容 (dup & dup2函数)

分离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函数调用完全终止了套接字,而不是半关闭。

这就是本节需要解决的问题。

扫描二维码关注公众号,回复: 915155 查看本文章


如何对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并进入半关闭状态。


猜你喜欢

转载自blog.csdn.net/amoscykl/article/details/80303829