1.dup()函数
#include <unistd.h>
int dup(int oldfd);
dup函数的功能:从系统中寻找 最小 可用的文件描述符 作为oldfd的副本。
新文件描述符通过dup的返回值返回。
上述相当于有两个不同的文件描述符,指向同一个对应资源或者操作,这里两者可以独立使用,互不影响。使用
的计数为1.
这不同于fork()函数,其是直接复制,两个相同的文件描述符,使用的引用计数为2。
必须全部关闭才能真正关闭文件描述符
#include <iostream>
#include <unistd.h>
#include <string>
int main(){
int fd=dup(1);//复制标准输出文件描述符
std::cout<<fd<<std::endl;
std::string m="6666";
std::string g{"747474"};
write(fd,m.c_str(),m.size());
std::cout<<std::endl;
write(1,g.c_str(),g.size());
std::cout<<std::endl;
/*fd,1都可以用于标注输出*/
close(fd);
return 0;
}
2.dup2()函数
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2的功能:将newfd作为oldfd的副本。 如果newfd先存在(已经被使用)
dup2会先close(newfd),然后将newfd作为 oldfd的副本。
即dup2不同于dup,dup2可以指定重定向文件描述符,而不是系统分配最小的可用的文件描述符。
#include <iostream>
#include <unistd.h>
#include <string>
int main(){
int fd=dup2(1,5);//复制标准输出文件描述符(或者是重定向为5)
std::cout<<fd<<std::endl;
std::string m="6666";
std::string g{"747474"};
write(fd,m.c_str(),m.size());
std::cout<<std::endl;
write(1,g.c_str(),g.size());
std::cout<<std::endl;
/*fd,5都可以用于标注输出*/
close(fd);
return 0;
}
下面是输出结果:
3.无名管道pipe:
管道(pipe)又称无名管道。 无名管道是一种特殊类型的文件,在应用层体现为 两个打开的文件描 述符(读端和写端)。
管道的特点:
1.半双工,数据在同一时刻只能在一个方向上流动。
2.数据只能从管道的一端写入,从另一端读出。
3.写入管道中的数据遵循先入先出的规则。
4.管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据 的格式, 如 多少字节算一个消息等。
5.管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
6.管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
7.从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。
#include <unistd.h>
int pipe(int pipe[2]);
功能:经由参数filedes返回两个文件描述符
参数: filedes为int型数组的首地址,其存放了管道的文件描述符fd[0]、fd[1]。 filedes[0]为读而打开,filedes[1]为写而打开管道,filedes[0]的输出是filedes[1]的输入。
返回值: 成功:返回 0 失败:返回-1
注意:在使用无名管道的时候 必须事先确定,谁发,谁收的问题。一旦确定不可更改。
注意:管道pipe是阻塞的,
1、默认用read函数从管道中读数据是阻塞的(无数据发生阻塞)。
2、调用write函数向管道里写数据,当缓冲区已满时write也会阻塞。
通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收 到SIGPIPE信号)退出
子进程睡一秒是让CPU调度执行父进程的写入数据操作(只是确定逻辑,可以不睡)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int fd[2];
pipe(fd);
pid_t pid=fork();
if(pid==0)
{
//子进程负责读取父进程的消息
close(fd[1]);
unsigned char buf[128]="";
sleep(1);
read(fd[0],buf,sizeof(buf));
printf("子进程%d读到的消息为:%s\n", getpid(), buf);
close(fd[0]);//读完 关闭管道
}
if(pid>0) //父进程负责写信息
{
close(fd[0]);//关闭读端
printf("父进程%d写入数据hello pipe\n",getpid());
write(fd[1],"hello pipe",strlen("hello pipe"));
printf("父进程:%d完成写入\n", getpid());
//通信完成 应该关闭写端
close(fd[0]);
wait(NULL);
}
return 0;
}
注意:若读端口全部关闭后,写进程向管道内写数据时,写进程会(收 到SIGPIPE信号)退出。
双全工管道:socketpair()函数见网络编程部分下面给出例子(数据流动是双方的)
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main ()
{
int sv[2];
int result = socketpair(PF_UNIX, SOCK_STREAM, 0, sv);//第三个参数只能为0
if (result < 0){
exit(1);
}
printf("sv[0] is : %d \n", sv[0]); //这两个套节字句柄并不相同,但作用是一样的
printf("sv[1] is : %d \n", sv[1]);
if (fork()){ /* 父进程 */
int val = 0;
pid_t pid = getpid();
close(sv[1]); //父进程关闭sv[1]的读写权限
while (1){
++val;
printf("%d send message: %d\n", pid, val);
write(sv[0], &val, sizeof(val)); //父进程向管道里写数据
// read(sv[0], &val, sizeof(val)); //如果字进程不写数据,将会导致此处堵塞
//printf("%d receive message: %d\n", pid, val);
sleep(1);
}
}else{ /*子进程*/
int val = 0;
close(sv[0]); //字进程关闭sv[0]的读写权限
pid_t pid = getpid();
while(1){
read(sv[1], &val, sizeof(val)); //字进程从管道中取数据
printf("%d receive message: %d\n", pid, val);
// printf("%d receive message: %d\n", pid, val);
// write(sv[1], &val, sizeof(val));
}
}
}
上面需要注意一点的是:父子进程,读写都是一个管道端,比如父进程读写都是sv[0],子进程读写都是sv[1]。但是跨进程交流是不同的管道端,用法和PIPE类似,比如父进程sv[0]写,子进程sv[1]读,或者返回来,子进程sv[1]写,父进程是sv[0]读,而不是一个sv[0],另一个进程sv[0]读。
4.fcntl()函数
#include <fcntl.h>
#include <unistd.h>
int fcntl(int fd,int cmd,....);
此函数可以对打开的文件描述符进行一些操作或者设置。
cmd指定了fcntl函数的行为。
对于cmd,下面主要讲述三者 :
- 1、复制文件描述符(F_DUPFD);
- 2、获取/设置文件状态标志(F_GETFL、F_SETFL);
1).cmd为F_DUPFD表示复制文件描述符fd。调用成功会返回新的描述符。新描述符使用大于或等于arg参数的编号最低的可用文件描述符复制文件描述符fd。新描述符与旧的fd共享同一文件表项。但是,新描述符有它自己的一套文件描述符标志. (即类似于dup/dup2()函数的功能)
// text.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("./text", O_RDWR | O_CREAT | O_TRUNC, 0775);
int fcntlFd = fcntl(fd, F_DUPFD, 0); // 指定从 0 开始分配最小的可用描述符作为新描述符
int dupFd = dup(fd); // 等效于 fcntl(fd, F_DUPFD, 0);
close(fd);
close(fcntlFd);
close(dupFd);
return 0;
}
2).获得/设置文件描述符:
F_GETFL(void) :表示使用 F_GETFL 作为cmd时,不需要传入第三个参数。
功能:获取文件状态标志。
返回值:成功返回文件状态标志(int).
失败返回 -1.
访问方式标志:O_RDONLY 、O_WRONLY、O_RDWR。这3个值是互斥的,因此首先必须用屏蔽O_ACCMODE取得访问方式位,然后将结果与这3个值中的每一个相比较。
F_SETFL(int):表示使用 F_SETFL 作为cmd时,传入第三个参数是int型的。
功能:设置文件状态标志,第三个参数传入新的文件状态标志值。
返回值:成功返回 0
失败返回 -1.
在Linux上,只能设置这5个文件状态标志:O_APPEND、 O_ASYNC、 O_DIRECT、 O_NOATIME、O_NONBLOCK,其中最常用的是将文件描述符设置成非阻塞(O_NONBLOCK),特别是在网络编程中很常见。下面是设置非阻塞I/O
int setnonblocking(int fd)
{
int old_option = fcntl(fd, F_GETFL);//获得文件描述符的状态位
int new_option = old_option | O_NONBLOCK;//或运算进行设置非阻塞状态位
fcntl(fd, F_SETFL, new_option);//将状态位重新添加在文件描述符中
return old_option;
}
注意:文件描述符有多种,可以是文件,可以是套接字等等。
文件描述符的状态位如下:这个open()调用的选择模式的参数也是一样的。