復制文件描述符
dup函數
作用 : 文件描述符復制
語法
????????#include <unistd.h>
????????int dup(int oldfd);
參數 :
????????所需復制的文件描述符
返回值
????????復制得到的文件描述符
功能 : 從文件描述符表中 , 尋找一個最小可能的文件描述符(通過返回值返回)作為 oldfd復制
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int newFd = dup(1);write(newFd, "hello world", 11);return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int fd = open("a.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);close(1);dup(fd);printf("啦啦啦,德瑪西亞");close(fd);return 0;
}
dup2函數(推薦)
#include <unistd.h>
int dup2(int oldfd, int newfd);
參數 :
????????oldfd:原文件描述符
????????newfd:指定復制到的文件描述符 , 如果該文件描述符存在 , 那么將原有的關閉
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int fd = open("a.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);printf("123");return 0;
}
無名管道
又名管道 (pipe)
????????無名管道是一種特殊類型的文件,在應用層體現為兩個打開的文件描述符,1 個描述符寫 fd[1], 1 個描述符讀 fd[0]
核心 :0 讀 1 寫
特點:
????????1,管道不是普通的文件 , 不屬于某個文件系統 , 其只存在于內存中。
????????2,半雙工,數據在同一時刻只能在一個方向上流動
補充
????????單工: 指數據傳輸只支持數據在一個方向上傳輸
????????雙工: 指二臺通訊設備之間,允許有雙向的資料傳輸
????????全雙工: 允許二臺設備間同時進行雙向數據傳輸。一般的電話、手機就是全雙工的系統,因為在講話時同時也可以聽到對方的聲音。
????????半雙工: 允許二臺設備間進行雙向數據傳輸 , 但不能同時進行。因此同一 時間只允許一設備傳送資料,若另一設備要傳送資料,需等原來傳送資料的設備傳送完 成后再處理。
????????3,數據只能從管道的一端寫入,從另一端讀出。
????????4,寫入管道中的數據遵循先入先出的規則。
????????5,管道所傳送的數據是無格式的,這要求管道的讀出方與寫入方必須事先約定好數 據的格式,如多少字節算一個消息等
????????6,管道在內存中對應一個緩沖區。不同的系統其大小不一定相同。
????????7,從管道讀數據是一次性操作,數據一旦被讀走,它就從管道中被拋棄,釋放空間 以便寫更多的數據
????????8,管道沒有名字,只能在具有公共祖先的進程之間使用。
補充 :
????????管道可以用于任意兩個或更多相關進程之間的通信,只要在創建子進程 的系列調用之前通過一個共同的祖先進程創建管道即可。
????????如管道可用于一個進程和其子孫進程之間的通信。第一個進程創建管 道,然后創建子進程,接著子進程再創建第一個進程的孫子進程。
????????管道通常用于兩個兄弟進程之間的通信—— 它們的父進程創建了管道,并 創建兩個子進程。
pipe函數
作用 : 創建無名管道
語法
????????#include <unistd.h>
????????int pipe(int fd[2]);
參數:
????????fd 為 int 型數組的首元素地址,其存放了管道的文件描述符 fd[0] 、 fd[1] 。
????????fd[0]為讀而打開, fd[1] 為寫而打開管道。
返回值:
????????成功:返回 0
????????失敗:返回-1
如:? ? ? ? int fd[2];
????????????????pipe(fd);
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{// 1,創建管道int fd[2];pipe(fd);// 2,創建子進程,子進程與父進程具有公共祖先int pid = fork();if (pid < 0){printf("創建子進程失敗");return 0;}else if (pid == 0){// 子進程,讀取父進程傳遞的數據// 因為子進程只讀取消息,所以寫入無用,可以關閉close(fd[1]);char buf[128];read(fd[0], buf, sizeof(buf));printf("子進程接收到的消息:%s\n", buf);// 讀取結束關閉讀close(fd[0]);// 關閉子進程_exit(-1);}else if (pid > 0){// 父進程,寫入數據// 因為父進程只寫入消息,所以讀無用,可以關閉close(fd[0]);write(fd[1], "hello gd", 8);printf("父進程發送消息完成\n");close(fd[1]);wait(NULL);}return 0;
}
讀寫特點
????????1、默認用 read 函數從管道中讀數據是阻塞的。
????????2、調用 write 函數向管道里寫數據,當緩沖區已滿時 write 也會阻塞。管道的緩沖區的大小: 64Kb
????????3、通信過程中,讀端口全部關閉后,寫進程向管道內寫數據時,寫進程會(收到 SIGPIPE 信號)退出。
????????4,從管道中讀數據的特點 編程時可通過 fcntl 函數設置文件的阻塞特性。設置為阻 塞:fcntl(fd, FSETFL,0); 設置為非阻塞: fcntl(fd, FSETFL, O_NONBLOCK);
示例 1 :緩沖區已滿時 write 也會阻塞。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int fd[2];pipe(fd);// 0讀1寫int pid = fork();if (pid == 0){close(fd[1]);sleep(2);close(fd[0]);_exit(0);}else if (pid > 0){close(fd[0]);int count = 0;for (int i = 1; i < 10000; i++){char buf[1024] = {0};write(fd[1], buf, 1024);count += 1024;printf("i=%d\tcount=%d\n", i, count);}close(fd[1]);wait(NULL);}return 0;
}
示例 2 :通信過程中,寫端關閉,讀端將解阻塞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{int pd[2];pipe(pd);int pid = fork();if (pid == 0){close(pd[1]);char buf[100] = {0};printf("等待寫入\n");read(pd[0], buf, 100);close(pd[0]);printf("子進程結束\n");_exit(0);}else if (pid > 0){close(pd[0]);printf("5秒后關閉寫\n");sleep(5);close(pd[1]);wait(NULL);}return 0;
}
示例 3: 通信過程中 讀端關閉 寫端將收到 SIGPIPE 信號 退出寫端進程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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]);int i = 0;while (1){char buf[128] = "";int len = read(fd[0], buf, sizeof(buf));i++;printf("len=%d\n", len);if (i == 5)break;}// 通信完記得關閉讀端close(fd[0]);}else if (pid > 0) // 父進程 寫{// 讀端 無用 可刪除close(fd[0]);while (1){printf("父進程%u寫入數據\n", getpid());write(fd[1], "hello pipe", 10);sleep(1);}// 通信完記得關閉寫端close(fd[1]);wait(NULL); // 等待子進程結束}return 0;
}
綜合案例
要求:使用代碼實現ps -A | grep bush
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{int fd[2];pipe(fd);int i = 0;for(i = 0; i < 2; i++){pid_t pid = fork();if(pid == 0){break;}}if(i == 0){close(fd[0]);dup2(fd[1],1);execl("/bin/ps","ps","-A",NULL);_exit(0);}else if(i == 1){close(fd[1]);dup2(fd[0],0);execl("/bin/grep","grep","bash",NULL);_exit(0);}else if(i == 2){while (1){int id = waitpid(-1,NULL,WNOHANG);if(id == -1){break;}} }return 0;
}
有名管道
又名 : 命名管道 (FIFO)
特點 :
????????1、半雙工,數據在同一時刻只能在一個方向上流動。
????????2、寫入 FIFO 中的數據遵循先入先出的規則。
????????3、 FIFO 所傳送的數據是無格式的,這要求 FIFO 的讀出方與寫入方必須事先約 定好數據的格式,如多少字節算一個消息等。
????????4、 FIFO 在文件系統中作為一個特殊的文件而存在,但 FIFO 中的內容卻存放在 內存中。
????????5、管道在內存中對應一個緩沖區。不同的系統其大小不一定相同。
????????6、從 FIFO 讀數據是一次性操作,數據一旦被讀,它就從 FIFO 中被拋棄,釋放 空間以便寫更多的數據。
????????7、當使用 FIFO 的進程退出后, FIFO 文件將繼續保存在文件系統中以便以后使 用。
????????8、 FIFO 有名字,不相關的進程可以通過打開命名管道進行通信。
mkfifo函數
作用 : 創建有名管道
語法
????????#include <sys/types.h>
????????#include <sys/stat.h>
????????int mkfifo(const char *pathname, mode_t mode);
參數 :
????????pathname:文件名
????????mode:文件操作模式 , 一般用 0666( 所有用戶可讀可寫 )
返回值 :
????????成功:0
????????失敗:-1, 一般失敗是因為存在與 pathname 名相同的文件
讀寫特點
1、open打開管道 不指定O_NONBLOCK (阻塞)
????????1、 open 以只讀方式打開 FIFO 時,要阻塞到某個進程為寫而打開此 FIFO
????????2、 open 以只寫方式打開 FIFO 時,要阻塞到某個進程為讀而打開此 FIFO 。
????????3、 open 以只讀、只寫方式打開 FIFO 時會阻塞,調用 read 函數從 FIFO 里讀數據 時 read 也會阻塞。
????????4、通信過程中若寫進程先退出了,則調用 read 函數從 FIFO 里讀數據時不阻塞;若 寫進程又重新運行,則調用 read 函數從 FIFO 里讀數據時又恢復阻塞。
????????5、通信過程中,讀進程退出后,寫進程向命名管道內寫數據時,寫進程也會(收到 SIGPIPE 信號)退出。
????????6、調用 write 函數向 FIFO 里寫數據,當緩沖區已滿時 write 也會阻塞。
2、open打開管道 指定O_NONBLOCK (非阻塞)
????????1、先以只讀方式打開:如果沒有進程 , 已經為寫而打開一個 FIFO, 只讀 open 成功, 并且 open 不阻塞。
????????2、先以只寫方 式打開:如果沒有進程 , 已經為讀而打開一個 FIFO ,只寫 open 將出錯返回-1 。
????????3、 read 、 write 讀寫命名管道中讀數據時不阻塞。
????????4、通信過程中,讀進程退出后, 寫進程向命名管道內寫數據時,寫進程也會(收到SIGPIPE 信號)退出。
3、 注意: open 函數以可讀可寫方式打開 FIFO 文件時的特點:
????????1、 open 不阻塞。
????????2、調用 read 函數從 FIFO 里讀數據時 read 會阻塞。
????????3、調用 write 函數向 FIFO 里寫數據 , 當緩沖區已滿時 write 也會阻塞
綜合案例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{// 1,創建有名管道,san發si接的管道mkfifo("sanToSi", 0666);// 2,創建有名管道,si發san接的管道mkfifo("siToSan", 0666);int i = 0;for (i = 0; i < 2; i++){// 創建進程int pid = fork();if (pid == 0){// pid==0說明是子進程進入的,子進程無需在創建進程break;}}if (i == 0){int fd;
// 子進程1發送消息
#ifdef USER1fd = open("siToSan", O_WRONLY);
#endif // DEBUG
#ifdef USER2fd = open("sanToSi", O_WRONLY);
#endif // DEBUGif (fd < 0){perror("打開發送管道失敗\n");_exit(-1);}while (1){char buf[128];fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;write(fd, buf, sizeof(buf));printf("my:%s\n", buf);if (strcmp(buf, "886") == 0){break;}}close(fd);_exit(-1);}else if (i == 1){// 子進程2,接收消息int fd = 0;
#ifdef USER1fd = open("sanToSi", O_RDONLY);
#endif // DEBUG
#ifdef USER2fd = open("siToSan", O_RDONLY);
#endif // DEBUGif (fd < 0){perror("打開接收管道失敗\n");_exit(-1);}while (1){char buf[128];int len = read(fd, buf, sizeof(buf));printf("讀取的字節數:%d\n", len);if (len > 0){printf("si:%s\n", buf);if (strcmp(buf, "886") == 0){break;}}}close(fd);_exit(-1);}else if (i == 2){printf("父進程%d正在執行\n", getpid());// 父進程while (1){pid_t id = waitpid(-1, NULL, WNOHANG);if (id > 0){printf("子進程%d被回收了\n", id);}else if (id == 0){continue;}else if (id < 0){break;}}}return 0;
}
// 命令代碼編譯gcc 文件名 -o 生成可執行文件名 -D USERX
//-D 相當于定義一個宏
總結
無名管道與有名管道的使用場景
????????1,無名管道應用與有血緣關系的進程中
????????2,有名管道應用與沒有血緣關系的進程中
無名管道與有名管道的區別
????????1,無名管道基于內存 , 無需文件管理系統
????????2,有名管道基于文件和內存 , 需要文件管理系統
dup2
????????作用: 復制文件描述
????????意義: 可以實現文件的重定向