Unix Select Poll - Multiplex 서버 구현위한 자료 1

unix select 와 poll

 

2. 파일 핸들링 일반 (파이프와 소켓 포함)
 
2.1 다중 연결(multiple connection)관리는 어떻게 하는가?
   
2.1.1 select()는 어떻게 사용하는가?
   
2.1.2 poll()은 어떻게 사용하는가?
   
2.1.3 select나 poll처럼 동시에 SysV IPC를 사용할 수 있는가?
 
2.2 반대쪽 연결이 끊겼는지 알수 있습니까?
 
2.3 디렉토리를 읽는 가장 좋은 방법은?
 
2.4 어떤 다른 사람이 파일을 열었는지 알 수 있습니까?
 
2.5 어떻게 파일에 락을 걸 수 있습니까?
 
2.6 파일이 다른 프로세스에 의해 업데이트 되었는지 어떻게 알 수 있습니까?
 
2.7 'du' 프로그램은 어떻게 작동 하는 것입니까?
 
2.8 파일의 크기는 어떻게 알 수 있습니까?
 
2.9 쉘에서 처럼 '~'를 파일 이름에서 어떻게 확장할 수 있습니까?
 
2.10 named pipe인 (FIFOs)를 가지고 무엇을 할 수 있는가?
   
2.10.1 named pipe가 뭡니까?
   
2.10.2 named pipe를 어떻게 만듭니까?
   
2.10.3 named pipe를 어떻게 사용합니까?
   
2.10.4 NFS를 통해서 named pipe를 사용할 수 있습니까?
   
2.10.5 동시에 여러 프로세스가 파이프에 쓸 수 있습니까?
   
2.10.6 응용프로그램에서의 named pipe 사용


2. 파일 핸들링 일반 (파이프와 소켓 포함)
******************************************************

다음에 있는 소켓 FAQ도 같이 보라:

`http://kipper.york.ac.uk/~vic/sock-faq/'

`ftp://rtfm.mit.edu/pub/usenet/news.answers/unix-faq/socket'

2.1 다중 접속(multitple connection)은 어떻게 관리하나?
=======================================

   
나는 하나 이상의 장치를 모니터링 해야 한다. 그들 모두를 어떻게 관리 할수 있는가?

select() 또는 poll()을 사용하라.

select()는 BSD에 의해서 소개 된 것이고 poll()은 System V Stream에 의해 만들어 진 것이다.
순수 BSD시스템은 여전히 poll로는 부족하고 예전 SVR3는 select()를 가지고 있지 않고, SVR4에 서 select()가 추가 되었다. 그리고 Posix.1g에서 이 둘은 표준으로 채택되었다.

select()와 poll()은 작은 부분을 제외 하고는 원천적으로 같은 일을 한다. 둘다 파일 디스크립터의 묶음에 어떠한 이벤트가 발생 하는 것을 테스트하며,
 
선택적으로 시간을 옵션으로 주어 일정 시간동안 이벤트가 일어나길 기다릴 수도 있다.

중요한 것은 select()나 poll() 둘다 일반 파일에서는 아무런 의미가 없다는 것이다. 이들은 소켓이나 파이프, 터미널 장치, 또는 문자 장치 등에서 사용 가능하다. 물론 시스템 의존적이지만.

2.1.1 select()는 어떻게 사용 하는가?
----------------------------

select()하기 위한 인터페이스는 'fd_set'의 개념에
  주로 기반을 둔다. 이것은 파일 
디스크립터의 모임인데 보통 bit 벡터로서 구현 된다. 예전에는 보통 파일 디스크립터가 32보다 작았었다. 그리고 그 모임에 넣기 위해 int형을 사용 했었다. 그러나 요즘은 그 보다 더 많은 파일 디스크립터를 사용 할 수 있다. 그래서 이 fd_set을 다루기 위해 표준 매크로를 사용하는 것이 중요하다고 하겠
다.

   
fd_set set;
    FD_ZERO(&set);   
/* empties the set */
    FD_SET(fd,&set) 
/* adds FD to the set */
    FD_CLR(fd,&set); 
/* removes FD from the set */
    FD_ISSET(fd,&set) 
/* true if FD is in the set */

대부분의 경우 fdset이 모든 범위의 파일 디스크립터를 다룰
  수 있도록 확신 할 수 있도록 하는 것은 시스템의 책임이다. 그러나 몇몇 경우에 여러분들은 FD_SETSIZE매크로를 미리  정의해 놓아야 한다. 이것도 물론 시스템 의존적이지만 일단 여러분의 시스템의 select()에 대한 매뉴얼  페이지를 참조 하라. 또한 몇몇 시스템은 1024개 
이상의 파일 디스크립터를 핸들링 하는데 문제가 있는 경우도 있다.

select하기 위한 기본적인 인터페이스는 다음과 같다:

   
int select(int nfds, fd_set *readset,
                         
fd_set *writeset,
                         
fd_set *exceptset, struct timeval *timeout);

`nfds'
    검사할 파일 디스크립터의 개수이다. 이것은 반드시 fdset의 
파일 디스크립터의 최대값보다 커야 하며, 파일 디스크립터의 실제 값은 아니라는 점을 알아둬야 한다.

`readset'
   
읽기 가능인지를 시험하기 위한 파일 디스크립터의 모임(set)

`writeset'
   
쓰기 가능인지를 시험하기 위한 파일 디스크립터의 모임(set)

`exceptfds'
   
예외 상태에 대한 시험을 하기 위한 파일 디스크립터의 모임(set)
   
(error는 예외 상태가 아니다)

`timeout'
    무한정 시간을  위해 NULL  값이며, 또는 최대  대기  시간을 지시한다.(  만약 
tv_sec과
tv_usec모두가 0이라면 파일 디스크립터의 상태는 poll된다. 그러나
 
그 호출은 결코 블록되지 않는다.

호출은 'ready'된 파일 디스크립터의 숫자를 되돌린다. 그리고 세 개의 fdset은 변경되는데 ready 상태인 파일 디스크립터만 set에
  남게 된다. 리턴된 파일  디스크립터 묶음을 테스트 하기 
위해 FD_ISSET 매크로를 사용하라.

여기 간단한 샘플 프로그램이 있다. 이 프로그램은
 
하나의 파일 디스크립터가 읽기 가능인지 테스트 한다.

   
int isready(int fd)
   
{
       
int rc;
       
fd_set fds;
       
struct timeval tv;
   

       
FD_ZERO(&fds);
       
FD_SET(fd,&fds);
       
tv.tv_sec = tv.tv_usec = 0;
   

       
rc = select(fd+1, &fds, NULL, NULL, &tv);
       
if (rc < 0)
         
return -1;
   

       
return FD_ISSET(fd,&fds) ? 1 : 0;
   
}

우리는 우리가 테스트에 관심 없을 때는 fdset에 NULL을 넣을 수도 있다는 것을 알아두라.

2.1.2 poll()은 어떻게 사용하나?
--------------------------

Poll은 struct pollfd의 리스트로 포인터를 입력 받는다. 이것은 디스크립터 이고 여러분이
  저장된 것에 대하여 poll하기를 바라는 이벤트이다. 이 이벤트는 스트럭춰의 이벤트 필트에서 bitwise마스크를 통하여 지정된다. 그 스트럭춰의 구성요소는 후에 어떤 발생되어 여러분에게 전달될 이벤트로 저장된다. SVR4에서 매크로는 poll.h에 의해 정의 되고(아마 그 이전버전도 그럴 듯 
하다) 필드에서 이벤트를 정의하는데 사용된다. 타임아웃은 밀리초로 지정되는데 지원되는 type형은 정수형이다.(정수형은 참 혼란스런 타입이긴 하다.) 타임아웃이 0이면 poll()은 즉시 리턴되고, 만약 타임아웃이 -1이면 poll은 어떤 이벤트가 발생할때까지 일시정지(suspend)된다.

   
struct pollfd {
        int fd;       
/* The descriptor. */
        short events; 
/* The event(s) is/are specified here. */
       
short revents; /* Events found are returned here. */
   
};

select와 아주 비슷하게도, 리턴값이 양의 숫자일 때 이는 얼마나 많은 디스크립터가 조건 이벤트 요구을 만족하는지를 나타낸다. 요구된 이벤트가 지정된 시간에 없을 경우엔
 
0이 리턴된다. 만약 음수가 리턴되면 이것을 에러를 뜻하기 때문에 반드시 즉시 에러를 체크 해야 한다.

만약 이벤트가 발견되지 않으면 이벤트가 clear되고 여러분들을
 
위해 이것은 더 이상 필요 없게 된다.

리턴된 이벤트들은 그 이벤트를 포함하고 있는지 테스트 된다.

예제를 보면..:

   
/* 일반 데이터 혹은 높은 priority를 갖는 데이터에 대한 두 개의 디스크립터에서 Poll한다.
       
만약 어떤 발견된 것이 적당한 파일 디스크립터로 함수 handle()을 호출 하면
       
타임아웃을 걸지 말고, 에러 일 경우에만 또는 파일 디스크립터중의 하나가 hang up
       
일 경우에만 그만 두도록 하라 */
   

   
#include
   
#include
   

   
#include
   
#include
   
#include
   

   
#include
   
#include
   
#include
   

   
#define NORMAL_DATA 1
   
#define HIPRI_DATA 2
   

   
int poll_two_normal(int fd1,int fd2)
   
{
       
struct pollfd poll_list[2];
       
int retval;
   

       
poll_list[0].fd = fd1;
       
poll_list[1].fd = fd2;
       
poll_list[0].events = POLLIN|POLLPRI;
       
poll_list[1].events = POLLIN|POLLPRI;
   

       
while(1)
       
{
           
retval = poll(poll_list,(unsigned long)2,-1);
           
/* 이 경우에 Retval은 항상 0 또는 -1 보다 클 것이다.
               
왜냐면 우리는 그것이 블록킹 하는 동안 수행하기 때문이다
           
*/
   

           
if(retval < 0)
           
{
               
fprintf(stderr,"Error while polling: %s\n",strerror(errno));
               
return -1;
           
}
   

           
if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
               
((poll_list[0].revents&POLLERR) == POLLERR) ||
               
((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
               
((poll_list[1].revents&POLLHUP) == POLLHUP) ||
               
((poll_list[1].revents&POLLERR) == POLLERR) ||
               
((poll_list[1].revents&POLLNVAL) == POLLNVAL))
             
return 0;
   

           
if((poll_list[0].revents&POLLIN) == POLLIN)
             
handle(poll_list[0].fd,NORMAL_DATA);
           
if((poll_list[0].revents&POLLPRI) == POLLPRI)
             
handle(poll_list[0].fd,HIPRI_DATA);
           
if((poll_list[1].revents&POLLIN) == POLLIN)
             
handle(poll_list[1].fd,NORMAL_DATA);
           
if((poll_list[1].revents&POLLPRI) == POLLPRI)
             
handle(poll_list[1].fd,HIPRI_DATA);
       
}
   
}

2.1.3 select나 poll처럼 System V IPC도 동시에 사용할 수 있는가?
------------------------------------------------------------

*아뇨* (이것을 허용하기 위해 지저분하고 신뢰성이 떨어지 구현된 AIX를 제외하고)

일반적으로 System V의 메시지 큐를 함께 사용하여 select나 Poll을 조합하여 사용하는건 문제를 일으킨다. System V IPC 오브젝트는 파일 디스크립터에 의해
 
제어 되지 않는다. 그래서 그들은 select()나 poll()에 전달 될 수 없다. 이런 추한 부분(?)이 많다.

 
- 시스템 V IPC는 버려 버려라! 🙂

 
- fork()를 수행한 후, 자식 프로세스로 하여금 시스템V IPC를 핸들 하도록 하면, 부모 프로세스와 자식 프로세스간에 파이프와 소켓을 통하여 통신할 수 있는데, 이 파이프와 소켓은 부모 프로세스가 select()할 수 있다.

 
- 위에 처럼은 가능하지만 자식 프로세스가 select()하거나 부모 프로세스와 메시지 큐를 통하여 통신하는 것은 안된다.

 
- 각 메시지 후에 여러분에게 신호를 전달하기 위해 메시지를 전달하는 프로세스를 정돈하라.
경고하는데 이러한 권한을 핸들링 하는 것은 사소한
 
일이 아니며, 이러한 방법을 사용하면 잠재적으로 메시지를 잃어 버리거나 데드락에 걸리는 문제를 일으키는 프로그램을 짜기 쉽다.

(또한 다른 방법들도 존재한다.)

2.2 반대쪽 연결이 끊겼는지 알수 있습니까?
=================================================================

만약 여러분이 파이프나 소켓, FIFO 등을 통해 읽으려고 한다면, 반대쪽 연결이 close될 때 여러분은 EOF메시지를 얻을 수 있다.(read()함수는 0을 리턴한다.) 만약 여러분이
  파이프나 소켓등으로 write하려고 하면, 읽는 도중이 상대편이 close할 
때 SIGPIPE 신호가 프로세스에게 전달되고 신호가 캐치되지 않은 그것을 죽이게 된다.(만약 여러분이 그 신호를 블록하게 되면 write() 호출은 EPIPE 와 함께 에러를 리턴한다.)

2.3 디렉토리를 읽는 가장 좋은 방법은?
=================================

역사적으로 이를 위한 여러 가지 인터페이스가 존재해 왔다. 요즘 Posix.1표준으로 자리잡은 유일한 하나는 의 함수를 쓰는 것이다.

opendir() 함수는 특정 디렉토리를 열며, readdir()함수는
 
그것으로 부터 디렉토리 표준화된 포맷으로 엔트리를 읽어들이고; closedir()은 닫는다. 또한 rewinddir(), telldir()그리고 seekdir()을 사용할 수 있다.

만약 여러분이 와일트카드 이름을 쓰기 원한다면, 대부분의 시스템이 glob()함수를 지원하다는 것을 알아둬라. 또한 와일드 카드 이름이 매칭되는지 확인하는 fnmatch()함수가 존재하는지도 확인하고, 디렉토리 전체를 훑어 내리는 ftw()함수도 봐라.

2.4 한 파일을 어떤 사람이 열고 있는지 알수 있는가?
=======================================================

이것은 또 다른 "Frequently Unanswered Question"의 한 후보가 될 듯 싶다. 왜냐면 일반적으로 여러분들의 프로그램은 다른 누가 파일을 열었는지는 관심이 없을 것이기 때문이다. 만약 여러분이 파일에 대한 동시 액세스를 다루길 원한다면 locking에 대한 것을 찾아봐라.

이것은 일반적으로 말해서 매우
  어려운 일이다. fuser와 lsof와 
같은 프로그램들이 이러한 일을 해주기는 하지만 커널 데이터를 통해서 안좋은 방법으로 찾아내는 것들이기 때문이다.
여러분들은 이러한 프로그램을 여러분의 프로그램에서 호출해서 쓸 필요는 없다. 왜냐하면 그 파일이 열려 있건 아니건 간에 그 사실을 확인한 그 시점에 그 사실 자체가 쓸모 없게 되기 때문이다.

2.5 파일에 lock을 어떻게 걸 수 있는가?
===========================

세가지 방법이 있다. 근데 만약 락을 걸고자
 
하는 데이터 파일이 다른종류의 프로그램이 동시에 공유해서 쓰는 일이 있다면 아주 세심하게 신경을 써서 락킹 체제를 세울 필요성이 있다.

실제로 몇몇 유닉스들을 sgid bit를 통해서 락킹을 위임 하고 있다.

몇몇 어플리케이션은 락 파일을
  사용하기도 한다.(filename.lock과 같은).  단순히 그러한 파일이 존재함을 판단함으로써 락이 걸려있는지만 확인할 수  있도록 할 수 있다. 이 방법은  UUCP에서 주로 사용되는데 모뎀이 사용중인지를 알리기 위해서 등을 위해 쓰인다. 예를 들어 
PID를 락 파일로 쓰기도 한다. 그러나 뭐 물론 이것이 확실한 방법은 아니다. 왜냐하면 PID는 반복적으로 순환되기 때문이다.

락킹 함수는 다음과 같은 것들이 있다.

       
flock();
       
lockf();
       
fcntl();

flock()은 BSD에서 출발한 것이고 현재는 대부분의 시스템들이 지원하고 있다.(물론 전부는 아니지만). 이것은 간단하면서도 효과적으로 하나의 호스트 상에서 동작한다, 다만 NFS상에서는 동작하지 않는다. 또한 이것은 파일 전체에 락을 건다. 좀
 
속임수적인 방법으로지만, PERL에서도 이것을 구현하고 있다.

fcntl()은 POSIX 호환의 유일한
  락킹 메커니즘이다. 따라서 유일한  호환성이 있는 락킹 체계라 하겠다. 이것은 또한 매우 강력하지만 사용하기도 쉽지  않다. NFS상태에서도 지원하는데 fcntl() 요청이 NFS서버(rpc.lockd)에 전달되어 NFS에서의 락킹도 구현하고 있다. 그리고 
fcntl()은 레코드 락킹도 지원하고 있다.

lockf()는 fcntl()을 단순화한 프로그래밍 인터페이스이다.

어떤 락킹 메커니즘을 쓰던지 간에, 락이 수행되고 있을때는 여러분의
 
모든 파일 IO를 sync하는 것이 중요하다.

       
lock(fd);
       
write_to(some_function_of(fd));
       
flush_output_to(fd); /* 출력이 버퍼링 되어 있는동안은 결코 unlock할 수 없다*/
       
unlock(fd);
        do_something_else; 
/* 다른 프로세스가 업테이트 한다.*/
       
lock(fd);
       
seek(fd, somewhere); /* 파일 포인터가 안전하지 않기 때문 */
       
do_something_with(fd);
       
...

fcntl()의 유용한 락킹 비책! (단순화를 위해 에러 핸들링은 제외):

   
#include
   
#include
   

    read_lock(int fd) 
/* 파일 전체에 대해 공유 락을 건다 */
   
{
       
fcntl(fd, F_SETLKW, file_lock(F_RDLCK, SEEK_SET));
   
}
   

    write_lock(int fd) 
/* 파일 전체에 대해 배제락을 건다*/
   
{
       
fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET));
   
}
   

   
append_lock(int fd) /* 존재하는 레코드들에 대한 엑세스를 막기
                       
위해 파일의 끝에 락을 건다
                       
*/
   
{
       
fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_END));
   
}

위의 것에 의해 사용되는 file_lock 함수

   
struct flock* file_lock(short type, short whence)
   
{
       
static struct flock ret ;
       
ret.l_type = type ;
       
ret.l_start = 0 ;
       
ret.l_whence = whence ;
       
ret.l_len = 0 ;
       
ret.l_pid = getpid() ;
       
return &ret ;
   
}

2.6 다른 프로세스에 의해 파일이 업데이트 되었는지 알 수 있나?
=========================================================

이것도 역시 "Frequently Unanswered Question"의 후보가 될 만한데 그 이유는 사람들이 일반적으로 파일이 변경된 것에 대한 노티를 찾으려고 하는데 이것에 대한 호환성
  있는 솔루션이 없기 때문이다.(IRIX의 경우에는 표준이 아닌 파일 액세스에 대한 모니터링 툴이 
있기는 하다, 그러나 나는 다른 곳에 이런 것이 있다는 말을 들어본 적이 없다.)

일반적으로 여러분들이 사용할 수 있는 가장 유용한 것은 fstat()인데 이것은 오버헤드 때문에 무척 느리다. 보통은 stat()보다도 더 느리다. 암튼 이 함수에서 ctime과 mtime을 이용해 파일이 최근에 언제 액세스 되었는지는 알 수 있으며, 삭제, link된거, 이름이 바뀐것 등을 알 수 있다.

암튼 굳이 이러한 기능이 필요한지 다시한번 생각해 보길 바란다.

2.7 du 프로그램이 어떻게 작동 되는가?
===================================

du는 단순히 stat()함수를 통해 디렉토리를 돌아다니면서 디렉토리와 파일의 블록 수를 더해서 이를 보여주는 일을 한다.

여러분이 좀더 자세한 정보를 얻고 싶다면 답은:

       
소스를 사용해라. 행운을 빈다.

BSD시스템(FreeBSD, NetBSD, OpenBSD)에 대한 소스는 ftp사이트에 많이 있고 GNU버전에 대한 소스는 GNU mirror사이트에 많이 돌아다닌다.

2.8 파일의 사이즈를 어떻게 알 수 있는가?
=====================================

stat()를 사용하고 만일 Open된 파일이 있다면 fstat()를 사용하라.

이 호출은 데이터 구조체를 포함하고 있는데 이 구조체에는 파일에 관련된 정보를 모두 포함하고 있다. 예를 들어 파일의 소유주, 그룹, 퍼미션, 크기, 최근 접근시간, 최근 수정시간 등.

다음 루틴은 stat()을 사용해서 파일 크기를 구하는 것을 보여준다.

   
#include
   
#include
   

   
#include
   
#include
   

   
int get_file_size(char *path,off_t *size)
   
{
     
struct stat file_stats;
   

     
if(stat(path,&file_stats))
       
return -1;
   

     
*size = file_stats.st_size;
     
return 0;
   
}

2.9 쉘에서처럼 '~'를 파일 이름에서도 사용할 수 있는가?
==========================================================

파일 이름의 시작부분에 오는 '~'에 대한 표준적인 해석은 다음과 같다.
만약 혼자 사용되거나 또는 '/' 다음에 오게 되면 현재 사용자의 홈디렉토리로 대체 된다. 만약 사용자 이름 다음에 오면 그것은 사용자의 홈디렉토리로 대체된다. 만약
 
적당한 확장(expansion)이 발견되지 않으면 쉘은 그 파일이름을 변경시키지 않은채로 남겨둔다.

그러나 실제로 '~'로 시작하는 파일 이름에
  대해 주의하라. 마구잡이적인 tilde-expansion은 그러한 파일 이름을 프로그램에게 지정하는걸 어렵게 만들 수 있다.  쿼테이션 마크(")를 사용하면 쉘이 그러한 확장 개념을 사용하는 것을 막아 준며, 프로그램은  tilde(~)자체를 파일 이름으로 보게 된다. 일반적으로 프로그램에게 커맨드라인의 파라미터로  전달되는 파일 이름이나 환경변수에서는 tilde를  사용하지 말라.  (물론,  프로그램에서  사용자  입력으로 생성되는  파일 이름이나 configuration파일로부터 파일이름을 읽을 때  등은 tilde-expansion을 사용하기에 좋은 
후보감이다.)

여기 이러한 일을 하도록 만들어진 표준 문자 class를 사용하는 C++코드가 있다.

   
string expand_path(const string& path)
   
{
       
if (path.length() == 0 || path[0] != '~')
         
return path;
   

       
const char *pfx = NULL;
       
string::size_type pos = path.find_first_of('/');
   

       
if (path.length() == 1 || pos == 1)
       
{
           
pfx = getenv("HOME");
           
if (!pfx)
           
{
               
// Punt. We're trying to expand ~/, but HOME isn't set
               
struct passwd *pw = getpwuid(getuid());
               
if (pw)
                 
pfx = pw->pw_dir;
           
}
       
}
       
else
       
{
           
string user(path,1,(pos==string::npos) ? string::npos : pos-1);
           
struct passwd *pw = getpwnam(user.c_str());
           
if (pw)
             
pfx = pw->pw_dir;
       
}
   

       
// if we failed to find an expansion, return the path unchanged.
   

       
if (!pfx)
         
return path;
   

       
string result(pfx);
   

       
if (pos == string::npos)
         
return result;
   

       
if (result.length() == 0 || result[result.length()-1] != '/')
         
result += '/';
   

       
result += path.substr(pos+1);
   

       
return result;
   
}

2.10 named pipe(FIFOs)를 가지고 무엇을 할 수 있는가?
============================================

2.10.1 named pipe가 무엇인가?
----------------------------

"named pipe"는 관련된 프로세스간에 데이터를 전달하기위한 특별한 파일이다.
  하나 또는 그 이상의 프로세스들이 그것에 데이터를 쓰면  다른 프로세스는 그것을 읽는다.  Named pipe는 파일 시스템에서 Visible한 파일이어서 ls프로그램 등으로  다른 일반 파일 처럼 볼 
수도 있다.(named pipe는 또한 fifo's라고도 불리우는데 이것은 "First In, First Out"을 뜻한다.)

Named pipe는 일반 파이프가 부모/자식간에만 연결할 수
 
있는것에 비해 관련없는 프로세스간에도 데이터를 전달하곤한다.

비록 시스템에서 무명씨 pipe는 양방향성이지만, Named pipe는 절대적으로 한쪽 방향성이다.

2.10.2 named pipe를 어떻게 생성할 수 있는가?
------------------------------------

인터렉티브하게 named pipe를 만들기 위해서는 'mknod'또는 'mkfifo'를 실행해야 한다.
 
어떤 시스템에서는 mknod가 /etc디렉토리에 있다. 다시 말하면 여러분의 패스에 속해 있다는 말이다. 여러분의 매뉴얼 페이지를 보면 자세한걸 알수 있다.

C프로그램에서 named pipe를 만들기위해 mkfifo()함수가 쓰인다.

   
/* 명시적으로 umask를 설정하면 여러분은 그것이 어디에 존재하는지 알수 없다.*/
   
umask(0);
   
if (mkfifo("test_fifo", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP))
   
{
       
perror("mkfifo");
       
exit(1);
   
}

mkfifo()가 없으면 mknod()를 사용해야 한다.

   
/* set the umask explicitly, you don't know where it's been */
   
umask(0);
   
if (mknod("test_fifo",
               
S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
               
0))
   
{
       
perror("mknod");
       
exit(1);
   
}

2.10.3 어떻게 named pipe를 사용하는가?
---------------------------------

pipe를 사용하기위해서 일반 파일처럼 open하고 일반 pipe처럼 read()와 write()를 수행한다.
그러나 open()은 블록될 것이다. 다음의 룰들이 적용된다.

 
* 만약 (O_RDWR)의 형태, 즉 읽기,쓰기의 형태로 파일을 열면 블록되지 않는다.

  * 만약 읽기로 열면(O_RDONLY) 다른  프로세스가 write로 열때까지 블록된다. 그러나 
만약 O_NONBLOCK이 지정되면 open은성공적으로 될 것이다.

  * 만약 O_WRONLY 다른 프로세스가 FIFO를  읽기로 열때까지 블록된다. 이것도 역시 
만약 파일을 열 때 O_NONBLOCK이 지정되면 블록되지 않는다.

FIFO를 일고 쓸 때 일반 Pipe나 소켓에서
  같이 적용되는 고려 사항은 read() 등의 함수는  모든 Writer가 상대 reader가 없어서 close되거나 write()가 
SIGPIPE를 발생시키면 EOF를 리턴 시킨다는 것이다. (만약 SIGPIPE가 블록되거나 무시되면 그 호출은 EPIPE의 에러를 만들면서 실패한다)

2.10.4 NFS를 통해서 named pipe를 사용할 수 있는가?
-----------------------------------------

안된다. NFS프로토콜엔 이러한 기능을 지원하지 않는다.(그러나 NFS로 마운트된
 
파일 시스템에서 같은 클라이언트 프로그램에서 프로세스간 통신을 하기위해 named pipe를 사용할 수는 있다.)

2.10.5 여러 프로세스가 파이프에 동시적으로 write할 수 있나?
---------------------------------------------------------------

만약 pipe에 쓰여질 각 데이터들이 PIPE_BUF보다 작다면 그리고 그들이 인터리브되지 않는다면 가능하다. 그러나 write가 유지되지 않는(not preserved) 영역은 존재한다. 파이프로부터 읽을 때, 비록 여러 쓰기로 부터의 데이터라고 할지라도
  읽기 호출은 가능한 많은 데이터를 
리턴할 것이다.

PIPE_BUF의 값은 Posix에 의해서 적어도
  512는 되도록 보장된다. 이건 
에 정의되어
있을 것이다. 그러나 pathconf()함수나 fpathconf()함수에 의해 자동으로 질의 된다.

2.10.6 어플리케이션에서의 named pipe사용
----------------------------------------

   
서버와 몇몇 클라이언트간의 2-way 통신을 어떻게 구현하는가?

한 번에 하나 이상의 클라이언트가 서버와
 
통신하는게 가능하다. PIPE_BUF 보다 클라이언트가 보내는 데이터가 작은 한은 같은 pipe를 통해 클아이언트 들이 서버와 통신할 수 있다. 모든 클라이언트들은 서버의 incoming fifo의 이름을 쉽게 알 수가 있다.

그러나, 하나의 파이프를 가지고 그 클라이언트들과 통신할 수는 없다. 만약
  하나 이상의 클라이언트들이 같은 pipe를 통해 읽기를 수행한다면, 올바른 클라이언트가 적당한 주저진 데이터를 
읽게 된다는 보증을 할 수 없기 때문이다.

솔루션은 클라이언트가 서버에 데이터를 보내기 전에 자신들의 incoming pipe를 만들거나 클라이언트로부터 데이터를 받은 후에 서버의 out going pipe를 만드는 것이다.

클라이언트의 PID를 사용하여 pipe의 이름을 사용하는 것이 일반적인 방법이다. 이렇게 함으로써 서버는 클라이언트에게 올바른 데이터를 주는 것을 보장 할 수 있다

위로 스크롤