网编(15):I/O流分离

版权声明:转载请声明 https://blog.csdn.net/qq_40732350/article/details/89010457

2种I/O流分离

  • 第一种:是通过调用fork函数复制出1 个文件描述符,以区分输入和输出中使用的文件描述符。虽然文件描述符本身不会根据输入和输出进行区分,但我们分开了2个文件描述符的用途,因此这也属于“流”的分离。
  • 第二种:通过2次fdopen函数的调用,创建读模式FILE指针(FILE结构体指针)和写模式FILE指针。换言之,我们分离了输入工具和输出工具,因此也可视为“流”的分离。

第一种分流的目的:

  • 通过分开输入过程(代码)和输出过程降低实现难度。
  • 与输入无关的输出操作可以提高速度。

第二种分流的目的:

  • 为了将FILE指针按读模式和写模式加以区分。
  • 可以通过区分读写模式降低实现难度。
  • 通过区分l/0缓冲提高缓冲性能。

第二种方法是通过下面的函数实现的,但是不能实现半关闭

#include <stdio.h>
FILE* fdopen(int fildes, const char * mode);
//成功时返回转换的FILE 结构体指针,失败时返回NULL 。

#tildes 需要转换的文件描述符。
#mode   将要创建的FILE结构体指针的模式(mode)信息。

实例代码:

服务器:

#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);//关闭写端
	
	//再从客户端读取一段数据
	fgets(buf, sizeof(buf), readfp);
	fputs(buf, stdout);
	fclose(readfp);
	return 0;
}

客户端:

#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)
			break;
		fputs(buf, stdout);
		fflush(stdout);		
	}
	
	//当服务器关闭
	fputs("FROM CLIENT: Thank you! \n", writefp);
	fflush(writefp);
	fclose(writefp); 
	fclose(readfp);
	return 0;
}

运行结果:

#服务器
$ ./ser 9190
#客户端
$ ./cli 192.168.43.220 9190
FROM SERVER: Hi~ client? 
I love all of the world 
You are awesome! 


终止FILE“ 流” 时无法半关闭的原因

当建立了关系后是这种情况

因此,针对任意一个FILE指针调用fclose函数时都会关闭文件描述符, 也就终止套接字,如图所示。

销毁套接字时再也无法进行数据交换。那如何进入可以输入但无法输出的半关闭状态呢? 其实很简单。如图所示, 创建FILE指针前先复制文件描述符即可。

复制后另外创建l个文件描述符,然后利用各自的文件描述符生成读模式FILE指针和写模式FILE指针。这就为半关闭准备好了环境,因为套接字和文件描述符之间具有如下关系:
"销毁所有文件描述符后才能销毁套接字。”
也就是说,针对写模式FILE指针调用fclose函数时,只能销毁与该FILE指针相关的文件描述符,无法销毁套接字。

复制文件描述符

函数:

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes , int fildes2);
//成功时返回复制的文件描述符,失败时返回-1。

#fildes  需要复制的文件描述符。
#fildes2 可以用fd2指定新描述符的值,如果fd2本身已经打开了,则会先将其关闭。如果fd等于fd2,则返回fd2,并不关闭它。

通过复制文件描述符可以避免上面的情况,但是关闭其中一个描述符,另一个还能继续读写,不能实现“半关闭”状态,如何实现参考下面的实例代码:客服端代码没变

#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");//复制文件描述符
	
	fputs("FROM SERVER: Hi~ client? \n", writefp);
	fputs("I love all of the world \n", writefp);
	fputs("You are awesome! \n", writefp);
	fflush(writefp);
	
	printf("clnt_sock = %d\n", clnt_sock);//打印套接字的文件描述符
	printf("dup(clnt_sock) = %d\n", fileno(writefp));
	
	shutdown(fileno(writefp), SHUT_WR);//半关闭文件描述符
	fclose(writefp);//关闭写端
	
	//再从客户端读取一段数据
	fgets(buf, sizeof(buf), readfp);
	fputs(buf, stdout);
	fclose(readfp);
	return 0;
}

运行结果:

#服务器
$ ./ser 9190
clnt_sock = 4 
dup(clnt_sock) = 5
FROM CLIENT: Thank you!

#客服端
$ ./cli 192.168.43.220 9190
FROM SERVER: Hi~ client? 
I love all of the world 
You are awesome!

这样就可以实现3点:

  1. 读和写用不同的流
  2. 读和写用不同的文件描述符
  3. 可以实现半关闭

猜你喜欢

转载自blog.csdn.net/qq_40732350/article/details/89010457