일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- @published 프로퍼티 래퍼
- @environmentobject 프로퍼티 래퍼
- react-router-dom
- 리액트최적화
- react상태관리라이브러리
- 세로모드끄기
- 페이지전환
- 비동기함수
- BFS
- 리렌더링최적화
- CSS
- 반응형 css
- 동기 함수 내에서 비동기 함수 호출
- C++
- navigationBar 숨기기
- @observedobject 프로퍼티 래퍼
- zustand
- zustand란
- 컴퓨터네트워크
- SwiftUI Font
- 상단 빈공간 제거
- 블로그업로드확인
- react hook
- featured-sliced-design
- GridItem
- 페이지이동함수
- LazyVGrid
- react fsd
- 가로모드끄기
- LazyHGrid
- Today
- Total
leebaek
[UNIX 프로그래밍] 10강 - 파이프 본문
- pipe
- pipe를 이용한 client-server
- select 시스템 호출
- fd_set 관련 매크로
- timeval의 구조
- select 시스템 호출
- FIFO
■ pipe
- 한 프로세스에서 다른 프로세스로의 단방향 통신 채널
- write와 read로 data 통신 가능
#include <unistd.h>
int pipe(int fildes[2]);
- fildes[0] : 읽기용
- fildes[1] : 쓰기용
- process당 open file 수, 시스템 내의 file 수 제한됨
-> pipe 제거해줘야 함 ( file은 아니지만, file table에서 관리하기 때문 )
□ pipe의 특성
- FIFO 처리 ( 순서 보장이 필요한 데이터 전송에 적합함 )
- lseek는 작동하지 않음 ( 읽은 데이터는 사라지기 때문 )
- pipe는 fork()에 의해 상속 가능 ( fildes가 자식 process에 복사되기 때문 )
□ pipe를 이용한 단방향 통신 ( 부모 -> 자식 )
- pipe 생성
- fork()에 의해 자식 생성 & pipe 복사
- 부모는 읽기용, 자식은 쓰기용 pipe를 close
#include <unistd.h>
int main() {
char ch[10];
int pid, p[2];
if ( pipe(p) == -1) {
perror("pipe call");
exit(1);
}
pid = fork();
if ( pid == 0 ) {
close(p[1]);
read(p[0], ch, 10);
printf("%s\n", ch);
}
close(p[0]);
scanf("%s", ch);
write(p[1], ch, 10);
wait(0);
exit(0);
}
□ pipe를 이용한 양방향 통신
- pipe 2개 생성
- fork()에 의해 자식 생성 & pipe 2개 복사
- pipe1: 부모는 읽기용, 자식은 쓰기용 pipe를 close
- pipe2: 부모는 쓰기용, 자식은 읽기용 pipe를 close
- blocking read / blocking write
- read가 blocking되는 경우 : pipe가 비어 있는 경우
- write가 blocking 되는 경우 : pipe가 가득 찬 경우 ( 실패가 아니라 기다렸다가 pipe가 비면 쓰기를 수행함 )
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipe1[2], pipe2[2];
pid_t pid;
char buffer[100];
// 파이프 2개 생성
if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
perror("pipe");
return 1;
}
// 자식 프로세스 생성
pid = fork();
if (pid == 0) { // 자식 프로세스
close(pipe1[0]); // pipe1의 읽기 닫기
close(pipe2[1]); // pipe2의 쓰기 닫기
// 부모에게 메시지 보내기
char child_message[] = "Hello from child!";
write(pipe1[1], child_message, strlen(child_message) + 1);
// 부모로부터 메시지 읽기
read(pipe2[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
close(pipe1[1]); // pipe1의 쓰기 닫기
close(pipe2[0]); // pipe2의 읽기 닫기
} else { // 부모 프로세스
close(pipe1[1]); // pipe1의 쓰기 닫기
close(pipe2[0]); // pipe2의 읽기 닫기
// 자식으로부터 메시지 읽기
read(pipe1[0], buffer, sizeof(buffer));
printf("Parent received: %s\n", buffer);
// 자식에게 메시지 보내기
char parent_message[] = "Hello from parent!";
write(pipe2[1], parent_message, strlen(parent_message) + 1);
close(pipe1[0]); // pipe1의 읽기 닫기
close(pipe2[1]); // pipe2의 쓰기 닫기
}
return 0;
}
□ pipe를 닫기
- 쓰기 전용 pipe 닫기 : 다른 writer가 없는 경우, read를 위해 기다리던 process들에게 0을 return ( EOF와 같은 효과 )
- 읽기 전용 pipe 닫기 : 더 이상 reader가 없는 경우, wrtier들은 SIGPIPE signal을 받음
- signal handling이 되지 않으면 process는 종료
- signal handling이 되면 signal 처리 후 wrtier는 -1을 return
□ non-Blocking read / non-blocking write
- 여러 pipe를 차례로 polling 하는 경우 ( 여러 파이프를 순차적으로 검사하는 폴링에서 유용 )
- 데이터가 없거나 공간이 부족하면, 작업이 대기하지 않고 바로 실패
#include <fcntl.h>
int fcntl(filedes, F_SETFL, O_NONBLOCK);
- 쓰기 전용 파이프 (filedes가 쓰기 디스크립터):
- 파이프가 가득 차 있으면, 쓰기 작업은 대기하지 않고 즉시 -1 반환.
- errno는 EAGAIN으로 설정됨.
- 읽기 전용 파이프 (filedes가 읽기 디스크립터):
- 파이프가 비어 있으면, 읽기 작업은 대기하지 않고 즉시 -1 반환.
- errno는 EAGAIN으로 설정됨.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main() {
int pipe1[2], pipe2[2];
char buffer[100];
// 두 개의 파이프 생성
pipe(pipe1);
pipe(pipe2);
// 읽기 디스크립터를 non-blocking 모드로 설정
fcntl(pipe1[0], F_SETFL, O_NONBLOCK);
fcntl(pipe2[0], F_SETFL, O_NONBLOCK);
// 부모 프로세스에서 파이프 사용
if (fork() == 0) { // 자식 프로세스
close(pipe1[0]); // 읽기 닫기
close(pipe2[0]); // 읽기 닫기
write(pipe1[1], "Data from pipe1", 16); // 파이프 1에 데이터 쓰기
sleep(1);
write(pipe2[1], "Data from pipe2", 16); // 파이프 2에 데이터 쓰기
close(pipe1[1]); // 쓰기 닫기
close(pipe2[1]); // 쓰기 닫기
_exit(0);
} else { // 부모 프로세스
close(pipe1[1]); // 쓰기 닫기
close(pipe2[1]); // 쓰기 닫기
// 폴링 방식으로 두 파이프 읽기
for (int i = 0; i < 10; i++) {
ssize_t n = read(pipe1[0], buffer, sizeof(buffer));
if (n > 0) {
printf("Read from pipe1: %s\n", buffer);
} else if (errno == EAGAIN) {
printf("Pipe1 is empty, try again later.\n");
}
n = read(pipe2[0], buffer, sizeof(buffer));
if (n > 0) {
printf("Read from pipe2: %s\n", buffer);
} else if (errno == EAGAIN) {
printf("Pipe2 is empty, try again later.\n");
}
sleep(1);
}
close(pipe1[0]); // 읽기 닫기
close(pipe2[0]); // 읽기 닫기
}
return 0;
}
- 폴링 방식 데이터 읽기 (부모):
- 두 파이프를 반복적으로 읽음
- 데이터가 없으면 EAGAIN을 확인하고 다음 시도로 넘어감
■ pipe를 이용한 client-server
□ Client는 하나의 pipe로 request를 write
□ Server는 여러 개의 pipe로부터 request를 read
- 클라이언트:
- 하나의 파이프에 요청 데이터를 씀
- 요청이 없으면 대기하지 않고 즉시 쓰기를 시도
- 서버:
- 여러 클라이언트가 요청을 보낼 수 있는 여러 개의 파이프를 사용함
- 요청이 없으면 블로킹 상태에서 대기함
- 요청이 하나 이상 오면 요청 순서대로 읽기를 수행함
■ select 시스템 호출
□ select system call : 읽는게 아니라 확인만 함
- 지정된 file descriptor 집합 중 어느 것이 읽기/쓰기가 가능한지 표시
- 읽기, 쓰기가 가능한 file descriptor가 없으면 blocking
- 영구적으로 blocking을 막기 위해 time out 사용 가능
□ select의 return 값
- -1 : 오류 발생시
- 0 : timeout 발생시
- 0보다 큰 정수 : 읽기/쓰기 가능한 file descriptor의 수
- return시 mask를 지우고, 재설정해야함
□ select
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *wrtiefds, fd_set *errorfds, struct timeval *timeout);
- int nfds
- 감시할 파일 디스크립터의 최대 값 + 1.
- 예를 들어, 감시할 파일 디스크립터가 0, 1, 2라면 nfds는 3이어야 함
- fd_set *readfds
- 읽기가 가능한 파일 디스크립터를 감지하기 위한 집합
- NULL일 경우 읽기 파일 디스크립터를 감시하지 않음
- fd_set *writefds
- 쓰기가 가능한 파일 디스크립터를 감지하기 위한 집합
- NULL일 경우 쓰기 파일 디스크립터를 감시하지 않음
- fd_set *exceptfds
- 예외 상황(예: out-of-band 데이터)을 감지하기 위한 집합
- NULL일 경우 예외 상황을 감시하지 않음
- struct timeval *timeout
- 타임아웃 시간을 지정
- NULL: 영구적으로 블로킹(읽기/쓰기 가능할 때까지 대기)
- 0초: 파일 디스크립터를 즉시 검사하고 반환
- 타임아웃 시간을 지정
- readfds, writefds, errorfds 중 하나만 고르고, 나머진 NULL로 지정
■ fd_set 관련 매크로
□ void FD_ZERO(fd_set *fdset);
- fdset 초기화
□ void FD_SET(int fd, fd_set *fdset);
- fdset의 fd bit를 1로 설정
□ void FD_ISSET(int fd, fd_set *fset);
- fdset의 fd bit가 1인지 검사
□ void FD_CLR(int fd, fd_set *fset);
- fdset의 fd bit를 0으로 설정
■ timeval의 구조
□ timeval
struct timeval {
long tv_sec;
long tv_usec;
}
- timeout이
- NULL이면 해당 event가 발생시까지 blocking
- 0이면, non-blocking
- 0이 아닌 값이면, read/write가 없는 경우 정해진 시간 후에 return
■ FIFO ( 이름을 가진 파이프 )
- pipe는 동일 ancestor를 갖는 프로세스들만 연결 가능
- but! fifo는 모든 프로세스들을 연결 가능
- UNIX의 file 이름을 부여 받음
- 소유자, 크기, 연관된 접근 허가를 가짐
- 일반 file 처럼, open, close, read, wrtie, remove가 가능함
□ mkfifo() : FIFO 만들기
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- pipe는 read, write 준비가 다 되어야 open 가능함 ( 그 전까지 blocking 상태 )
- open("/tmp/fifo", O_WRONLY, | O_NONBLOCK) 기본설정이 block이라 따로 non-block 설정해줘야 함
- Non-blocking open 일 경우, 상대 프로세스가 준비되지 않으면, -1 return ( errno = ENXIO )
int main(void) {
int fd, n;
char buf[512];
mkfifo("fifo", 0600);
fd = open("fifo", O_RDWR);
for (;;) {
n = read(fd, buf, 512);
write(1, buf, n);
}
}
-> O_RDONLY로 변경, if ( n == 0 ) printf("..."); 변경 시
서버 쪽에서 wrtie 클라이언트 대기하고 있음
그런데 fd가 RDONLY여서 wrtie해주는 클라이언트가 없으면
반복문에서 read할 경우 0으로 리턴됨
그렇기 때문에 "..."을 계속 출력하게 됨
wrtie가 종료하고 read 클라이언트가 blocking 된 채로 기다리려면 서버는 RDWR로 open 하는게 더 효율적임
'수업 정리 > UNIX 프로그래밍' 카테고리의 다른 글
[UNIX 프로그래밍] 11-1강 메세지 큐 (0) | 2024.12.14 |
---|---|
[UNIX 프로그래밍] 11-3강 Semaphore (0) | 2024.12.07 |
[UNIX 프로그래밍] 11-2강 Shared memory (0) | 2024.12.07 |
[UNIX 프로그래밍] 9강 - 메모리 매핑 (0) | 2024.11.17 |
[UNIX프로그래밍] 8강 - 시그널 (0) | 2024.11.17 |