0
点赞
收藏
分享

微信扫一扫

网编(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“ 流” 时无法半关闭的原因

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

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

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

网编(15):I/O流分离_文件描述符_02

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

网编(15):I/O流分离_套接字_03

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

网编(15):I/O流分离_文件描述符_04

复制文件描述符

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

函数:

#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 条评论