# FIFO Programming

## ไปป์ระบุชื่อ (Named Pipes - FIFOs)

FIFO หรือย่อมาจาก "<mark style="color:blue;">**First In, First Out**</mark>" (อ่านว่า *Fy-Foh*) ในอีกชื่อหนึ่งก็คือ *named pipe* ซึ่งหมายถึง pipe ที่มีชื่อไฟล์ของตัวเอง โดยตัว named pipe นี้จะเหมือน pipe ทั่วไปยกเว้นมันสามารถถูกตั้งชื่อได้ ซึ่งจะมีข้อดีคือสามารถระบุ pipe ได้และโปรเซสอื่นๆก็สามารถเรียกใช้ได้ง่ายขึ้นซึ่งจะคล้ายกับการเปิดไฟล์ทั่วไปด้วยคำสั่ง `open()` ดังนั้นก่อนที่จะใช้ named pipe (FIFO) จะต้องใช้คำสั่ง mknod() เพื่อสร้างไฟล์ชนิดนี้เป็นอย่างแรก ดังตัวอย่างข้างล่างนี้

```c
mknod("myfifo", S_IFIFO | 0644 , 0);
```

จากตัวอย่างข้างล่างนี้ตัวไฟล์ FIFO ถูกตั้งชื่อว่า myfifo นอกจากนั้นจะมีการกำหนดค่าอาร์กิวเม้นต์เพื่อระบุโหมดของไฟล์เช่น `S_IFIFO | 0644` ซึ่งเป็นการระบุว่าเป็นไฟล์ FIFO และมีสิทธิ์การเข้าถึง (access permission) เป็น 644 (เลขฐานแปด) หรือเทียบเท่ากับ `rw-r--r--` ซึ่งรายละเอียดของโหมดนั้นจะถูกเก็บอยู่ในไฟล์ `sys/stat.h` ในอีกทางหนึ่งสามารถสร้างไฟล์ FIFO ได้ด้วยคำสั่ง `mknod` หรือ `mkfifo` ดังข้างล่างนี้&#x20;

```shell-session
$ mknod myfifo p
$ chmod 0644 myfifo 
$ ls -al myfifo
prw-r--r-- 1 wiroon wiroon 0 Sep 18 14:06 myfifo
```

โดยที่ p เป็นตัวระบุให้เป็นไฟล์ชนิด pipe หรืออาจจะใช้คำสั่ง mkfifo ดังตัวอย่างข้างล่างนี้

```shell-session
$ mkfifo -m 0644 myfifo
$ ls -al myfifo
prw-r--r-- 1 wiroon wiroon 0 Sep 18 14:06 myfifo
```

{% hint style="info" %}
**ฟังก์ชันและตัวแปรที่เกี่ยวข้อง**

* ไลบรารีที่เกี่ยวข้องคือ <mark style="color:blue;">**sys/types.h**</mark> และ <mark style="color:purple;">**sys/stat.h**</mark>
* ฟังก์ชัน <mark style="color:orange;">`int mkfifo(char *path, mode_t mode)`</mark>
  * โดยที่ตัวแปรพอยเตอร์ <mark style="color:orange;">`path`</mark> จะหมายถึงไฟล์ FIFO ที่ต้องการจะสร้างขึ้น และตัวแปร <mark style="color:orange;">`mode`</mark> จะหมายถึงการระบุสิทธิ์การเข้าถึง (ดูเพิ่มได้จากคำสั่ง `unmask` และ `chmod` ซึ่งจะส่งค่ากลับที่ไม่เท่ากับศูนย์ในกรณีที่ไม่มีความผิดพลาดขณะสร้างไฟล์&#x20;
    {% endhint %}

{% hint style="success" %}

### ตัวอย่างที่ 1

{% endhint %}

แสดงตัวอย่างโปรแกรมสำหรับทดสอบการส่งข้อมูลผ่านไฟล์ FIFO ดังแสดงข้างล่างนี้&#x20;

```c
/*
 *  nampipes.c
 *  simply opens a pre-created named pipe (a "fifo") and reads
 *  stuff from it as soon as there's something available.
 *  Created by Mij <mij@bitchx.it> on 02/02/05.
 *  Original source file available on http://mij.oltrelinux.com/devel/unixprg/
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#define MAX_TEXT_LENGTH 20

int main(int argc, char *argv[]) {
	int pipe, count = 0;
	char ch;

	pipe = open("/tmp/myfifo", O_RDONLY);
	if (pipe < 0) {
		perror("Opening pipe");
		exit(1);
	}

	/* preparing to read from the pipe... */
	printf("Waiting data from the pipe... \n");

	/* reading one char a time from the pipe */
	while (1) {
		if (read(pipe, &ch, 1) < 0) {
			perror("Read the pipe");
			exit(2);
		}

		if (count < MAX_TEXT_LENGTH) {
			printf(“%c”, ch);
			count++;
		} else
			break;
	}

	/* leaving the pipe */
	printf("\n");
	close(pipe);

	return 0;
}
```

โดยมีขั้นตอนการสร้างไฟล์ FIFO และส่งข้อมูลผ่านไฟล์ FIFO ไปยังโปรแกรม nampipes โดยมีขั้นตอนดังนี้

{% tabs %}
{% tab title="หน้าต่าง Terminal 1" %}

```shell-session
$ mkfifo -m 0644 /tmp/myfifo
$ ls -al myfifo
prw-r--r-- 1 wiroon wiroon 0 Sep 18 14:27 /tmp/myfifo
$ gcc -o nampipes nampipes.c -Wall
$ ./nampipes
```

{% endtab %}

{% tab title="หน้าต่าง Terminal 2" %}

```shell-session
$ echo “This message is for you” > /tmp/myfifo
```

{% endtab %}
{% endtabs %}

เมื่อกลับมาดูหน้าต่าง Terminal 1 จะแสดงผลดังแสดงข้างล่างนี<หน้าต่างที่ 1>

```shell-session
$ ./nampipes
Waiting data from the pipe... 
This message is for you
```

{% hint style="success" %}

### ตัวอย่างที่ 2

{% endhint %}

แสดงตัวอย่างการสร้าง name pipe ด้วยการเรียกฟังก์ชัน `mkfifo()` ตาม path ที่กำหนดคือ `/tmp/AtoB`  โดยโปรเซสแม่ จะทำการสร้างโปรเซสลูกขึ้นมา เพื่อให้โปรเซสลูกเป็นผู้เขียนข้อมูล (writer) เท่านั้น (`O_WRONLY)` แล้วโปรเซสแม่จะเป็นผู้อ่านข้อมูล (reader) เท่านั้น (`O_RDONLY)`ซึ่งโปรเซสลูกจะทำการคอยด้วยฟังก์ชัน `wait(NULL);` จนกว่าโปรเซสแม่ได้รับข้อมูลครบจึงจะลบ named pip ออกด้วยฟังก์ชัน `unlink();`

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FyNJ09ArvOwPtFfaqcUrS%2Fnamed_pipe.png?alt=media&#x26;token=20a39de8-58e8-4ef5-a86f-0eeee0fb6239" alt=""><figcaption></figcaption></figure>

```c
// fifo_parent_child.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>

#define FIFO_PATH "/tmp/AtoB" // Path to the named pipe

void writer_process() {
	int fd;
	char message[] = "Hello, named pipe!";

	// Open the named pipe for writing
	fd = open(FIFO_PATH, O_WRONLY);
	if (fd == -1) {
		perror("open");
		exit(EXIT_FAILURE);
	}

	// Write the message to the named pipe
	write(fd, message, strlen(message) + 1);
	printf("Message sent: %s", message);

	// Close the named pipe
	close(fd);
}

void reader_process() {
	int fd;
	char buffer[100];

	// Open the named pipe for reading
	fd = open(FIFO_PATH, O_RDONLY);
	if (fd == -1) {
		perror("open");
		exit(EXIT_FAILURE);
	}

	// Read the message from the named pipe
	read(fd, buffer, sizeof(buffer));
	printf("Message received: %s", buffer);

	// Close the named pipe
	close(fd);
}

int main() {
	pid_t pid;

	// Create the named pipe
	mkfifo(FIFO_PATH, 0666);

	// Fork a child process
	pid = fork();

	if (pid == -1) {
		perror("fork");
		exit(EXIT_FAILURE);
	} else if (pid == 0) {
		// Child process (writer)
		writer_process();
	} else {
		// Parent process (reader)
		reader_process();

		// Wait for the child process to finish
		wait(NULL);

		// Remove the named pipe
		unlink(FIFO_PATH);
	}
	return 0;
}
```

```shell-session
$ gcc -o fifo_parent_child fifo_parent_child.c 
$ ./fifo_parent_child
Message sent: Hello, named pipe!
Message received: Hello, named pipe!
```

{% hint style="success" %}

### ตัวอย่างที่ 3

{% endhint %}

แสดงตัวอย่างการดำเนินการทำงานในลักษณะของ FIFO โดยการสร้างไฟล์ pipe ในลักษณะไฟล์สตรีม (file stream) โดยใช้ฟังก์ชัน `fopen()` และ `fclose()` ซึ่งแตกต่างจากคำสั่ง `open()` เนื่องจากคำสั่ง `open()` นี้จะต้องมีการสร้างไฟล์ pipe ที่มีอยู่จริงๆบนระบบไฟล์ภายในฮาร์ดดิสของระบบ แต่ในขณะที่คำสั่ง `fopen()` จะเป็นการสร้างไฟล์ pipe อยู่ภายในตัวเคอร์เนลของระบบปฏิบัติการลีนุกซ์ ดังตัวอย่างข้างล่างนี้

สร้างโปรแกรมที่ทำตัวเองเป็น server โดยการเปิดไฟล์ pipe (MYFIFO) เพื่อรอรับข้อมูล โดยการเปิดอ่านไฟล์ pipe ที่ถูกสร้างขึ้นในเคอร์เนล ดังตัวอย่างข้างล่างนี้

{% hint style="success" %}
**หน้าต่างTerminal ที่ 1**
{% endhint %}

```c
/*****************************************************************************
 Excerpt from "Linux Programmer's Guide - Chapter 6"
 (C)opyright 1994-1995, Scott Burkett
 ***************************************************************************** 
 Filename: fifoserver1.c
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include <linux/stat.h>

#define FIFO_FILE       "MYFIFO"

int main(void) {
	FILE *fp;
	char readbuf[80];

	/* Create the FIFO if it does not exist */
	umask(0);
	mknod(FIFO_FILE, S_IFIFO | 0666, 0);

	while (1) {
		fp = fopen(FIFO_FILE, "r");
		fgets(readbuf, 80, fp);
		printf("Received string: %s\n", readbuf);
		fclose(fp);
	}

	return (0);
}
```

ทำการคอมไพล์โปรแกรม และรันโปรแกรม ซึ่งการทำงานของ FIFO นั้นจะทำงานรออยู่ตลอดเวลา

```shell-session
$ gcc -o fifoserver1 fifoserver1.c 
$ ./fifoserver1
```

สร้างโปรแกรมที่ทำหน้าที่ส่งข้อมูลไปยังโปรแกรม fifoserver1 โดยการส่งผ่าน (write) ไฟล์ pipe ชื่อ `MYFIFO` ที่ถูกเปิดรออยู่แล้วในเคอร์เนล ดังตัวอย่างข้างล่างนี้

{% hint style="success" %}
**หน้าต่างTerminal ที่ 2**
{% endhint %}

```c
/*****************************************************************************
 Excerpt from "Linux Programmer's Guide - Chapter 6"
 (C)opyright 1994-1995, Scott Burkett
 ***************************************************************************** 
 MODULE: fifoclient1.c
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define FIFO_FILE       "MYFIFO"

int main(int argc, char *argv[]) {
	FILE *fp;

	if (argc != 2) {
		printf("USAGE: fifoclient [string]\n");
		exit(1);
	}

	if ((fp = fopen(FIFO_FILE, "w")) == NULL) {
		perror("fopen");
		exit(1);
	}

	fputs(argv[1], fp);

	fclose(fp);
	return (0);
}
```

ทำการคอมไพล์โปรแกรมและรันโปรแกรมเพื่อทำการส่งข้อความ "Hello from FIFO Client" ไปยังโปรแกรม fifoserver1 โดยการทำงานของ FIFO นั้นจะทำงานรออยู่ตลอดเวลา

```shell-session
$ gcc -o fifoclient1 fifoclient1.c 
$ ./fifoclient1 
USAGE: fifoclient1 [string]

$ ./fifoclient1 "Hello from FIFO Client"
```

ดังนั้นเมื่อมองกลับมาที่หน้าต่าง Terminal ที่ 1 ก็จะเห็นข้อมูลที่ตัวโปรแกรม fifoserver1 ได้รับมาจากไฟล์ pipe (`MYFIFO`) ดังแสดงข้างล่างนี้

```shell-session
$ gcc -o fifoserver1 fifoserver1.c 
$ ./fifoserver1
Received string: Hello from FIFO Client
```

{% hint style="success" %}

### ตัวอย่างที่ 4

{% endhint %}

แสดงตัวอย่างการประยุกต์ให้รองรับ multi-client แล้วตัว server จะส่งข้อความกลับไปยัง client ตัวนั้นๆผ่านไฟล์ `MYFIFO2` ดังไฟล์ server แสดงข้างล่างนี้&#x20;

```c
/**********  Multi-Client Fifo Server!  **********/
// fifoserver2.c
#include <sys/types.h>  
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>  
#include <sys/fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>

#define FIFO_FILE       "MYFIFO2"

int main(void) {
	int readfifo, writefifo, dummyfd;
        char buf[80], outbuf[80], filename[80], *fifoid, *message;
        void handler(int signum);

	signal(SIGPIPE, handler); /******  In case client disappears ******/
	signal(SIGTTOU, handler); /******  To keep server alive on printf *****/

	sprintf(filename, FIFO_FILE);
	if ((mkfifo(filename, S_IRUSR | S_IWUSR) < 0) && (errno != EEXIST)) {
		printf("Cannot make server fifo!");
		exit(1);
	}

	if ((readfifo = open(filename, O_RDONLY)) < 0) {
		printf("Cannot open server fifo!");
		exit(2);
	}
	if ((dummyfd = open(filename, O_WRONLY)) < 0) {
		printf("Cannot open server fifo dummy!");
		exit(3);
	}

	while (read(readfifo, buf, 80) == 80) {
		fifoid = strtok(buf, "\040");
		message = strtok(NULL, "\n");
		printf("Got %s from client\n", buf);
		if ((writefifo = open(fifoid, O_WRONLY)) < 0) {
			printf("Cannot open fifo to client!");
			continue;
		}
		memset(outbuf, 0, 80);
		sprintf(outbuf, "%s\n", message);
		write(writefifo, outbuf, 80);
		close(writefifo);
	}
	return 0;
}

void handler(int signum) {
	printf("Fifo has been closed in reader!");
	exit(0);
}
```

ไฟล์ fifo client ดังแสดงข้างล่าง

```c
/**********  Fifo Client  ***********/
// fifoclient2.c
#include <sys/types.h>  
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>  
#include <sys/fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE       "MYFIFO2"

int main() {
        int clientfifo, serverfifo;
        char buf[80], filename[80], outbuf[256], inbuf[80], fifoname[80];

        sprintf(filename, FIFO_FILE);
        if ((serverfifo = open(filename, O_WRONLY)) < 0) {
                perror("Cannot open server's fifo!");
                exit(3);
        }

        sprintf(fifoname, "fifo.%d", getpid());
        if ((mkfifo(fifoname, S_IRUSR | S_IWUSR) < 0) && (errno != EEXIST)) {
                perror("mkfifo error!");
                exit(1);
        }

        memset(buf, 0, 80);
        printf("Enter string: ");
        while (fgets(buf, 80, stdin) && strcmp(buf, "quit\n") != 0) {
                /*************  Tell server who we are
                 *************/
                memset(outbuf, 0, 256);
                sprintf(outbuf, "fifo.%d %s", getpid(), buf);
                write(serverfifo, outbuf, 80);

                /*************  Open must be done here!  It blocks if done above loop
                 *************  because opens for read block until data arrives!!
                 *************/
                memset(inbuf, 0, 80);
                if ((clientfifo = open(fifoname, O_RDONLY, 0)) < 0) {
                        perror("Could not open client fifo!");
                        exit(2);
                }
                read(clientfifo, inbuf, 80);
                printf("From server: %s", inbuf);
                memset(buf, 0, 80);
                printf("Enter string: ");
                close(clientfifo);
        }
        unlink(fifoname); /******  Get rid of filesystem entry.  ******/
}
```

{% tabs %}
{% tab title="หน้าต่าง Terminal 1" %}

```shell-session
$ gcc -o fifoserver2 fifoserver2.c -Wall
$ ./fifoserver2 &
```

{% endtab %}

{% tab title="หน้าต่าง Terminal 2" %}

```shell-session
$ gcc -o fifoclient2 fifoclient2.c -Wall
$ ./fifoclient2                                                                                                                                  ─╯
Enter string: Hi, There ... I'm from Client 1
From server: Hi, There ... I'm from Client 1
```

{% endtab %}

{% tab title="หน้าต่าง Terminal 3" %}

```shell-session
$ ./fifoclient2                                                                                                                                  ─╯
Enter string: Hi, There ... I'm from Client 2
From server: Hi, There ... I'm from Client 2
```

{% endtab %}
{% endtabs %}

เมื่อกลับมาดูหน้าต่าง Terminal 1 จะแสดงผลดังแสดงข้างล่างนี้<หน้าต่างที่ 1>

<pre class="language-shell-session"><code class="lang-shell-session">$ ./fifoserver2 &#x26;
<strong>Got fifo.26203 from client &#x3C;-- Client ตัวที่ 1 จากหน้าต่าง Terminal 2
</strong>Got fifo.26203 from client &#x3C;-- Client ตัวที่ 2 จากหน้าต่าง Terminal 3
</code></pre>
