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

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;
}

客服端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char* argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

    if(argc!=3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
    error_handling("socket() error");

    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]));

    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling("connect() error I");

    while (1) {
        str_len = read(sock, message, sizeof(message)-1);
        if(str_len==0)
            break;
        message[str_len] = 0;
        printf("%s\n", message);

    }

    write(sock, "Thank you!", sizeof("Thank you!"));
    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

运行结果:

#服务器
$ ./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. 可以实现半关闭

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值