Chapter 5. 소켓프로그래밍
목차 5.1 소켓옵션들 5.2 신호 (Signals) 5.3 넌블로킹입 출력 (Nonblocking I/O) 5.4 멀티태스킹 (Multitasking) 5.5 멀티플렉싱 (Multiplexing) 5.6 여러수신자들 (Multiple Recipients)
5.1 소켓옵션들 TCP/IP 프로토콜개발자들은대부분의애플리케이션들을만족시킬수있는디폴트동작에대해많은시간고려 소켓의특정부분은소켓옵션에연관되어있음 연관된소켓옵션의값을변경함으로써자신의애플리키에션에맞는소켓의수신버퍼크기변경가능 소켓의작용은소켓레벨옵션에의해제어된다. 이러한옵션들은 <sys/socket.h> 에정의되어있다. Setsockopt(2) 와 getsockopt(2) 는옵션을설정하고얻는데사용된다.
[ 옵션을변경하는함수 ] int getsockopt(int socket, int level, int optname, void *optval, unsigned int *optlen) int setsockopt(int socket, int level, int optname, const void *optval, unsigned int optlen) 소켓의옵션을추출한다. 소켓옵션들은디폴트소켓동작을변경하는데사용된다. socket level optname optval optlen 소켓옵션단계옵션이름옵션값을기록할버퍼의주소옵션값의길이 ( 바이트수 ) getsockopt() 옵션값버퍼의길이 ( 바이트수 ) - setsockopt() 오류가발생하지않으면 0 을, 그렇지않으면 -1 을반환한다.
Level : 가능한소켓옵션들은프로토콜스택계층 (layer) 에대응되는레벨 (level) 들로나뉘는데, 두번째파라미터가해당옵션의레벨을나타낸다. SOL_SOCKET 옵션 프로토콜에무관하여소켓계층자체에서처리 IPPROTO_TCP 옵션 전송프로토콜에특화됨 IPPROTO_IP 옵션 인터네트워크프로토콜에의해서처리 optname : 옵션그자체는정수 optname 에의해서표현됨 optval : 버퍼의포인터 Getsockopt() 에서옵션의현재값은그버퍼에저장되고 Setsockopt() 에서소켓옵션은그버퍼에있는값으로지정됨 optlen : 버퍼의길이를지정하는데이것은해당특정옵션에대해정확해야함 Getsockopt() 에서 optlen 은 in-out 파라미터로서초기에버퍼의크기를포함하는정수를가리키며, 반환시옵션값의크기를포함하는정수를가리키게됨.
[ 소켓옵션 ] optname 형태값설명 SOL_SOCKET 단계 SO_BROADCAST Int 0,1 브로드캐스트를허용함 SO_KEEPALIVE Int 0,1 킵얼라이브 ( 살아있음 ) 메세지들가능 ( 프로 토콜에의해구현된경우 ) SO_LINGER Linger{} 시간 확인을기다리며 close() 반환을지연시키기 위한시간 (6.4.2절참조 ) SO_RCVBUF Int 바이트 소켓수신버퍼의바이트들 (73쪽코드및 6.1절참조 ) SO_RCVLOWAT Int 바이트 Recv() 의반환을유발하는최소사용가능 바이트의수 SO_REUSEADDR Int 0.1 이미사용중인주소나포트 ( 특정조건하에 결합을허용함 (6.4 및 6.5절참조 ) SO_SNDLOWAT Int 바이트보내는최소바이트들 SO_SNDBUF int 바이트소켓송신버퍼의바이트들 (6.1 절참조 )
optname 형태값설명 IPPROTO_TCP 단계 TCP_MAX Int 초 킵얼라이브 ( 살아있음 ) 메세지들사이의 초단위시간 TCP_NODELAY Int 0,1 데이터통합 (merging) 을위한지연 불허 (Nagle의알고리즘 ) IPPROTO_IP 단계 IP_TTL int 0-255 유니캐스트 IP 패킷들을위한 time-to-live IP_MULTICAST_TTL IP_MULTICAST_LO OP IP_ADD_MEMBERS HIP IP_DROP_MEMBER SHIP Unsigned char 0-255 멀티캐스트 IP 패킷들을위한 time-tolive(120 쪽의 MulticastSender.c 참조 ) int 0,1 멀티캐스트소켓이자신이보낸패킷들 수신가능 Ip_mreq{} Ip_mreq() 그룹주소 그룹주소 특정멀티캐스트그룹으로보내진패킷들의수신가능 (122 쪽 MulticastReceiver.c 참조 )- 지정하기만함 특정멀티캐스트그룹으로보내진패킷들의수신불가능 지정하기만함
5.2 신호 (Signals) 신호는프로그램에게어떤사건들이일어났다는것을알리는체계를제공한다. EX) 1. 사용자가 인터럽트 문자를입력한경우 2. 타이머가만료된경우 전달된신호의 4가지처리유형 1. 신호가무시시킴 2. 프로그램을강제종료시킴 3. 신호가블록됨 4. 신호처리루틴을수행함
[ 소켓에주로사용되는신호들 5 가지 ] 신호 유발사건 디폴트동작 SIGALRM 경보타이머의만료 종료 SIGCHLD 자식프로세스에서빠져나옴 무시 SIGINT 인터럽트문자입력 종료 SIGIO 소켓입, 출력준비완료무시 SIGPIPE 닫힌소켓에쓰려고시도종료
[ 신호의디폴트동작변경함수 ] int sigaction( int whichsignal, const struct sigaction* newaction, struct sigaction* oldaction) 성공시 0, 실패시 -1 을반환 sigaction( ) 인수 whichsignal : 동작이변경되는신호 newaction : 해당신호형태의새로운동작을정의하는 sigaction 구조체를가리킨다. oldaction : 값이널이아니라면해당신호의이전동작을나타내는 sigaction구조체가여기에복사된다.
sigaction() 구조체 -1 struct sigaction { void ( *sa_handler ) (int); sigset_t sa_mask; }; int sa_flags; sigaction() 구조체-2 sa_handler : 신호가전달되었을때제어하는함수를가리키는함수포인터 sa_handler의 3가지 1. SIG_IGN : 신호가무시됨 2. SIG_DFL : 디폴트동작이수행됨 3. 함수주소 : 함수가실행됨
sigaction() 구조체 -3 sa_mask 필드는 whichsignal 을처리하는도중블록된신호들을나타내는데, 이것은 sa_handler 가 SIG_IGN 이거나 SIG_DFL 일때만의미가있다. 디폴트로 whichsignal 은 sa_mask 에상관없이항상블록된다. sa_flags 필드는 whichsignal 이처리되는방식을추가적으로제어한다. sigaction() 구조체-4 sa_mask-bool형태의플래그들의집합으로구현됨. 한플래그가각신호형태에해당되고이들의집합은다음의네가지함수로서조작가능. int sigemptyset(sigset_t * set) 다 0으로셋팅 int sigfillset(sigset_t * set) 각플래그값안에 1로셋팅 int sigaddset(sigset_t * set, int whichsignal) - 세부 int sigdelset(sigset_t * set, int whichsignal) - 네가지함수는성공시0, 실패시-1 반환
sigaction() 구조체 -5 int sigfillset(sigset_t * set) -> 주어진집합의모든플래그들을지정한다. int sigemptyset(sigset_t * set) -> 주어진집합의모든플래그들을해지한다. int sigaddset(sigset_t * set, int whichsignal) 주어진집합의신호번호에의해지정된플래그들을지정한다. int sigdelset(sigset_t * set, int whichsignal) 주어진집합의신호번호에의해지정된플래그들을해지한다.
예제 sigaction.c - 1 0 #include <stdio.h> /* for printf() */ 1 #include <signal.h> /* for signal() */ 2 #include <unistd.h> /* for pause() */ 3 4 void DieWithError(char *errormessage); /Error handling function*/ 5 void InterruptSignalHandler(int signaltype);/*interrupt signal handling function*/ 6 7 int main(int argc, char * argv[]) 8 { 9 Struct sigaction handler; /* Signal handler specification structure */ 10 11 /* Set InterruptSignalHandler as handler function*/ 12 Handler.sa_handler = InterruptSignalHandler; 13 /*Create mask that masks all signals*/ 14 If (sigfillset(&handler.sa_mask) < 0) 15 DieWithError( sigfillset() failed ); 16 /* No flags */ 17 Handler.sa_flags = 0;
18 19 /*Set signal handling for interrupt signals */ 20 If (sigaction(sigint, &handler, 0) < 0) 21 DieWithError( sigaction() failed ); 22 23 for(;;) 24 pause(); /* suspend program until signal received */ 25 26 exit(0); 27 } 28 29 void InterruptSignalHandler(int signaltype) 30 { 31 printf( Interrupt Received. Exiting program.\n ); 32 exit(1); 33 }
예제 sigaction.c 소스분석 1. 신호처리기함수프로토타입 : 5 줄 2. 신호처리기설정 : 9 21 줄 신호처리를위한함수지정 : 12 줄 신호마스크채움 : 14-15 줄 SIGINT 를위한신호처리기설정 : 20-21 줄 3. SIGINT 까지무한루프 :23 24 줄 pause() 가신호를받을때까지프로세스를중단 4. 신호처리를위한함수 :29 33 줄 InterruptSignalHandler() 가메시지를인쇄하고프로그램에서빠져나온다.
[ 신호의중첩 ] 신호의중첩이란 - 신호가처리중일때다른신호가전달된경우전달된신호는처리기가일을끝낼때까지연기된다. 이런신호를계류중이라고한다. 즉, 신호는계류중이거나그렇지않거나이다. 같은신호가처리중에두번이상전달되면처리기는원래의실행을마치고오직한번만더수행한다.
5.3 블로킹넌블로킹 [ 유닉스의 I/O] Linux 는다른 Unix 와마찬가지로일반적인파일을비롯해서다른모든것들이파일로처리되므로 ( 소켓, 각종디바이스 ) I/O 란곧파일에대한입출력을말한다. 소켓에대한접근또한파일에대한입출력으로접근한다. 유닉스의 I/O 의종류 Blocking I/O : 봉쇄입출력이라고한다. Non-Blocking I/O : 비봉쇄입출력이라고한다. I/O Multiplexing : 입출력다중화라고한다. Asynchronous : 비동기입출력이라고한다.
5.3.1 블로킹이란..? 일반적으로우리가함수를호출하면그함수가수행을마치고리턴할때까지다른아무일을할수없는상태가발생한다. -> 이를블록상태라고말한다. 소켓호출의기본동작은블록킹이다. 예 ) 1. recv() 함수는적어도하나의메시지를받을때까지반환하지않는다.( 즉블록된다 ) 2. send() 의경우전송할데이터를저장할공간이충분하지못하면블록된다. 3. connect() 의경우도연결이설정될때까지는블록된다. 블로킹의문제점. 송신측은필요할때 send() 을호출하여데이터를전송하면되지만수신측은언제 Recv() 을호출하여전달된데이터를읽어들여야할지그점이명확하지많다이때수신측에서블로킹함수를사용한다면송신측에서데이터는보내지않는한다른작업을수행할수가없다
5.3.2 넌블록킹의이용 블로킹의해결책넌블록킹과비동기화를사용하면된다. 넌블록킹이란? 넌블록킹화시키면 I/O 함수는읽을값이없을경우블록하지않고바로 error 를리턴한다. 단, 표준입출력장치의 error 가아닌경우 error 값을 EAGAIN 으로선택하여리턴하므로써, 입출력장치의 error 인지데이터가준비되지않는것인지구분할수있다. 즉 EAGAIN 은넌 - 블록킹 I/O 가 O_NONBLOCK 을사용하여선택되어졌고즉시읽을수있는데이터가없다라는 error 메시지다. 넌블록킹의사용방법함수의기본동작이블록킹으로작성되었다할지라도 fcntl 과같은 I/O 제어함수를통해서동작을변경시켜줄수있다. 소켓의경우시스템은 errno 를 connect() 의경우 EINPROGRESS 로반환그밖의경우는 errno 를 EWOULDBLOCK 으로반환한다. # 여기서 EWOULDBLOCK 은 EAGAIN 과같은값이다.
5.3.3 fcntl() 함수의사용 File Control fcntl 시스템호출은이미열려있는파일의특성제어를위해서사용된다 int fcntl(int fd, int cmd, long argumnet) :fd file descriptor int fd : open이나 socket 등의시스템호출을통해서만들어진파일지정자이다 int cmd: fd에대한특성을제어하는값 long argumnet: cmd 를지정할때사용되는 argment 플래그를읽어올때 (F_GETFL) argment 는 0 플래그를쓸때 (F_SETFL) argment는플래그값이된다. 자세한내용은뒷편 (Flag_Setting) SIGIO 시그널을받을프로세스를설정할때 (F_SETOWN) argment는프로세스 ID나프로세스그룹 ID가된다.
argument 로사용되는플레그 Open() 에서사용되는플래그들과비슷하므로 open 에사용되는플래그들을나열하겠음. 반드시하나만정의되어야하는플래그 O_RDONLY : 읽기전용 O_WRONLY : 쓰기전용 O_RDWR : 읽기, 쓰기가능중복지정이가능한플래그 ( 보통 연산으로중복 ) O_APPEND : 모든쓰기작업은파일의끝에서수행된다. O_CREAT : 파일이없을경우파일을생성한다. ( 세번째인자필요 ) O_EXCL : O_CREAT 와같이쓰이며, 파일이있는경우에 error 를발생시킨다. O_TRUNC : O_CREAT 와같이쓰이며, 파일이있는경우에기존파일을지운다. O_NONBLOCK : blocking I/O 를 nonblocking 모드로바꾼다. O_SYNC : 매쓰기연산마다디스크 I/O 가발생하도록설정한다. O_ASYNC : 비동기적입출력을사용하기위하도록플래그를설정한다. FASYNC 를쓰기도한다.
명령의종류 (cmd) 붉은색이자주쓰는것 F_DUPFD : 파일디스크립터를복사. 세번째인수보다크거나같은값중, 가장작은미사용의값을리턴한다. F_GETFD : 파일디스크립터의플래그를반환 (FD_CLOEXEC) F_SETFD : 파일디스크립터의플래그를설정 F_GETFL : 파일테이블에저장되어있는파일상태플래그를반환 F_SETFL : 파일상태플래그의설정 (O_APPEND, O_NONBLOCK, O_SYNC 등을지정 ) F_GETOWN : SIGIO, SIGURG 시그널을받는프로세스 ID 와프로세스그룹 ID 를반환 F_SETOWN : SIGIO, SIGURG 시그널을받는프로세스 ID 와프로세스그룹 ID 를설정
5.3.4 errono 의정리 입출력함수사용시리턴되는값들 (open() 과비슷하므로 open() 함수의 return 값 (error) 을정리함. EINTR 어떤데이터를읽기도전에함수가신호에의해인터럽트되었다. EAGAIN 넌 - 블록킹 I/O 가 O_NONBLOCK 을사용하여선택되어졌고즉시읽을수있는데이터가없다. EIO I/O 에러. 이것은백그라운드프로세스그룹에있는프로세스가제어되는 tty 단말기에서읽기를시도할때, 그리고이것이무시되거나봉쇄되는 SIGTTIN 이거나또는프로세스그룹이고아일때일어난다. 또한디스크나테이프에서읽는동안저레벨 (low-level) I/O 에러가있을때일어난다. EISDIR fd 가디렉토리를가리킨다. EBADF fd 가유효한파일기술자가아니거나읽기위해열려지지않았다. EINVAL fd 가읽기에적당하지않은객체와연결되었다. EFAULT buf 는접근할수없는주소공간을가리키고있다.
5.3.4 비동기적입출력 넌블로킹의어려운점 주기적으로성공할때까지폴링을해야한다. 비동기적입출력이란? 운영체제가소켓호출이성공적이었을때프로그램에게알려주도록하는방법임의입출력과관련 event가소켓에서발생하면 SIGIO신호를프로세스에전달하는방식을사용하고있다. 비동기적입출력생성법 1 단계 : SIGIO 를위한신호핸들러설정이필요하다. 2 단계 : 소켓에관련된신호들이이프로세스에게전달되도록설정한다.( 즉소켓의소유자프로세스설정 ) 3 단계 : 비동기입출력이가능하도록설정한다. Fctnl, Asynchronous
[ 책의예제를통한접근 ] sigaction(sigio, &handler, 0) SIGIO 신호를통해소켓입출력준비완료를통보받아적합한루틴을실행하기위해설정 fcntl(sock, F_SETOWN, getpid()) sock 이나타내는소켓을 cmd: F_SETOWN SIGIO, SIGURG 시그널을받는프로세스 ID 와프로세스그룹 ID 를설정하겠다.( 같은소켓을여러프로세스가사용할수있으므로 ) Argment: getpid() 현재프로세스아이디를아규먼트값으로넣는다. fcntl(sock,f_setel,o_nonblock FASYNC) cmd : F_SETEL F_SETFL 에의해서파일지정자에대한값 ( 특성 ) 을세팅한다. Argment: O_NONBLOCK : blocking I/O 를 noneblocking 으로바꿈 FASYNC : FASYNC 상황플래그를설정하면 SIGIO 신호는파일기술자상에서입력이나출력이가능하게될때마다보내어진다. Void SIGIOHandler(int signaltype){.} 단지 SIGIO 의신호를받으면 recvfrom() 을시도하며, 만약 recvfrom 의 return 값이 EWOULDBLOCK 이아니면실패로간주하고프로세스를마무리한다.
[fcntl() 의참고문서 ] Fcntl() 에대해더자세히알고싶을경우참고문서를덧붙인다. http://cantata.kaist.ac.kr/%7ezerone/data/c/unix_c/8.htm#10 http://www.joinc.co.kr/modules/moniwiki/wiki.php/article_fcntl%c 0%BB_%C0%CC%BF%EB%C7%D1_%C6%C4%C0%CF%C1 %A6%BE%EE Signal 에대한문서 http://www.joinc.co.kr/modules/moniwiki/wiki.php/article_signal% 20%B4%D9%B7%E7%B1%E2%202
5.3.3 타임아웃 문제제기 : 만약 UDP 패킷이유실된다면? 클라이언트는패킷이유실됬는지알지못한다. 즉무한이기다리게된다. 방법제시 : 시간제한을두어패킷의유실여부를파악블로킹함수를호출하기전에 alarm() 함수를통해경보를설정한다. 사용함수 : unsigned int alarm(unsigned int secs) alarm() 은타이머를구동시키며지정된시간이지난후만료될때 SIGALRM 신호가보내진다. 이신호를받고적적한핸들러를구동시킨다. 책의예제는 trie 라는값을두어 recv() 의시도를체크한다. 만약정해놓은수만큼시도를하고그이후에도패킷을받지못하는경우유실로파악하고종료를한다.
[ 교제예제를통한타임아웃이해 ] myaction.sa_handler = CatchAlarm; Sigfillset(&myAction.sa_mask) 주어진집합의모든플래그들을지정한다.( 즉한신호가처리중일경우다른신호들은계류시킨다 ) If(sigaction(SIGALRM, &myaction,0) Sigaction 을통해 SIGALRM 신호에대해 myaction 을호출한다 Sendto( ) : 패킷을보낸다. While((recvfrom( ))<0) : 받은패킷이 0 보다작은동안반복해서체크한다. If(errno == EINTR) 즉어떤데이터를읽기도전에함수가신호에의해인터럽트되었다면 ( 이때인터럽트는 alarm 에의해생성된다 ) If(tries <MAXTRIES) 최대읽기시도값보다현재시도한값이적다면 Sendto( ) 다시한번보내보고 Alarm(TIMEOUT_SECS); alarm 함수를다시시작한다. Else DieWithError(..) 최대읽기시도값보다현재시도값이같거나클경우종료를한다. Void CatchAlarm(int ignored) { tries += 1} Alarm 이 SIGALRM 을보내면받아이함수를처리한다.( 현재시도한값을 1 올린다.)
5.4 Multitsaking ( 한번에여러개의클라이언트다루기 )