Zaraz zaraz, po kolei... Jeśli dobrze rozumiem, to wielkiej filozofii tu nie ma. Weźmy najprostszy przykład z jednym komunikatem do przesłania:
Kopiuj
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#define BUFFER_SIZE 512
void readDataFromStd(char buffer[]) {
memset(buffer, 0, BUFFER_SIZE + 1);
printf(">>> ");
scanf("%s", buffer);
}
int main() {
char buffer[BUFFER_SIZE + 1];
int pipefd[2];
ssize_t bytesCount = 0;
pid_t pid;
printf("Hello, World!\n");
pipe(pipefd);
pid = fork();
if (pid < 0) {
perror("Can't fork! Quitting...");
return 1;
}
if (pid == 0) {
close(pipefd[0]);
readDataFromStd(buffer);
bytesCount = write(pipefd[1], buffer, BUFFER_SIZE);
close(pipefd[1]);
printf("Child sent %ld bytes\n", bytesCount);
_exit(EXIT_SUCCESS);
} else {
close(pipefd[1]);
memset(buffer, 0, BUFFER_SIZE + 1);
bytesCount = read(pipefd[0], buffer, BUFFER_SIZE);
printf("Parent received %ld bytes:\n%s\n", bytesCount, buffer);
close(pipefd[0]);
wait(NULL);
_exit(EXIT_SUCCESS);
}
}
W tym kodzie funkcja write
działa w trybie blokującym, więc poczeka na na proces piszący. A zatem:
- oba procesy startują,
- proces czytający dochodzi do funkcji write i czeka na dane,
- w tym samym momencie proces piszący prosi o wpisanie danych i chwilę później pisze do rury,
- proces czytający może ruszyć, więc czyta,
- proces piszący nie ma nic do roboty i kończy się,
- proces czytający sprawdza czy są dzieci do czekania na nie (funkcją
wait
) i - ponieważ owych dzieci nie ma - wychodzi.
Jeśli zatem dodamy kod jeszcze jeden etap komunikacji, też to zadziała:
Kopiuj
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#define BUFFER_SIZE 512
void readDataFromStd(char buffer[]) {
memset(buffer, 0, BUFFER_SIZE + 1);
printf(">>> ");
scanf("%s", buffer);
}
int main() {
char buffer[BUFFER_SIZE + 1];
int pipefd[2];
ssize_t bytesCount = 0;
pid_t pid;
printf("Hello, World!\n");
pipe(pipefd);
pid = fork();
if (pid < 0) {
perror("Can't fork! Quitting...");
return 1;
}
if (pid == 0) {
close(pipefd[0]);
readDataFromStd(buffer);
bytesCount = write(pipefd[1], buffer, BUFFER_SIZE);
printf("Child sent %ld bytes\n", bytesCount);
readDataFromStd(buffer);
bytesCount = write(pipefd[1], buffer, BUFFER_SIZE);
close(pipefd[1]);
printf("Child sent %ld bytes\n", bytesCount);
_exit(EXIT_SUCCESS);
} else {
close(pipefd[1]);
memset(buffer, 0, BUFFER_SIZE + 1);
bytesCount = read(pipefd[0], buffer, BUFFER_SIZE);
printf("Parent received %ld bytes:\n%s\n", bytesCount, buffer);
memset(buffer, 0, BUFFER_SIZE + 1);
bytesCount = read(pipefd[0], buffer, BUFFER_SIZE);
printf("Parent received %ld bytes:\n%s\n", bytesCount, buffer);
close(pipefd[0]);
wait(NULL);
_exit(EXIT_SUCCESS);
}
}
Druga sprawa: zabawa z strlen. Nie wiem jaki był cel. Jeśli ustalenie długości, to są inne sposoby. Przed użyciem zmiennej typu char[]
czyścimy ją zerami. Ponieważ tekst w C jest zakończony zerem, taki ciąg znaków ma zerową długość. Jeśli do tablicy znaków o długości 512 wpiszemy 10 znaków, to cała reszta dalej jest wyzerowana. Jakakolwiek funkcja szukająca znaku 0
znajdzie go na jedenastym miejscu i dalej przestanie szukać. Poza tym, jeśli chcemy mieć pewność, że funkcja scanf
wczyta maksymalnie tyle, ile chcemy, piszemy:
Kopiuj
char buffer[100];
scanf("%10s", buffer);
W tym przypadku %10s
znaczy, że oczekujemy dziesięciu znaków.
To, co napisałem w tym kodzie, powinno wystarczyć w prostych apkach. W poważnych programach można się pokusić o to, żeby drugi proces nie pisał nic na konsolę, jeśli pierwszy nie skończy czytać. W tym momencie mam coś takiego:
Kopiuj
Hello, World!
>>> 123
Child sent 10 bytes
>>> Parent received 10 bytes:
123
567
Child sent 10 bytes
Parent received 10 bytes:
567
Trochę tekst się pomieszał, ale w tym przypadku to nic. W tym momencie to nie boli a zabawa w synchronizację jest ciężka.
fork()
tworzysz nowy proces będący dokładną kopią obecnego. Do tworzenia wątków w *nixach służy bibliotekapthreads
.