版权声明:转载请声明 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点:
- 读和写用不同的流
- 读和写用不同的文件描述符
- 可以实现半关闭