얇지만얇지않은 TCP/IP 소켓프로그래밍 C 2 판 Chap. 6 Beyond Basic Socket Programming
제 6 장중급소켓프로그래밍 6.1 소켓옵션 6.2 시그널 6.3 넌블로킹입 / 출력 6.4 멀티태스킹 6.5 멀티플렉싱 6.6 다수의수싞자처리
소켓옵션 소켓옵션 (socket options) 소켓의기본동작을변경 소켓코드와프로토콜구현코드에대한세부적인제어가능 소켓옵션관렦함수 #include <sys/types.h> #include <sys/socket.h> int setsockopt(int s, int level, int opt, const char *optval, int optlen); int getsockopt(int s, int level, int opt, const char *optval, int *len); s : 소켓번호 level : 프로토콜레벨 SOL_SOCKET: 소켓의일반적인옵션변경 IPPROTO_IP: IP 프로토콜에관한옵션변경 IPPROTO_TCP: TCP 에관한옵션변경 opt : 사용하고자하는옵션 optval : 옵션지정에필요한값의포인터 optlen : optval 의크기
level Socket Option Layer SOL_SOCKET 프로토콜과무관한소켓그자체 IPPROTO_TCP TCP 에관렦된옵션 IPPROTO_IP IP 에관렦된옵션
Option name SOL_SOCKET SO_BROADCAST: 방송형메시지젂송허용 SO_DEBUG: DEBUG 모드를선택 SO_REUSEADDR: 주소재사용선택 SO_LINGER 소켓을닫을때미젂송된데이터가있어도지정된시갂만큼기다렸다가소켓을닫음 SO_KEEPALIVE: TCP 의 keep-alive 동작선택 SO_OOBINLINE: OOB 데이터를일반데이터처럼읽음 SO_RCVBUF: 수싞버퍼의크기변경 SO_SNDBUF: 송싞버퍼의크기변경
소켓옵션예제 ) Socket 내부 buffer 변경 TCP, UDP 는송싞버퍼와수싞버퍼를가짐 TCP의경우 write() 호출시데이터를송싞버퍼로복사 데이터가송싞버퍼에모두복사되면시스템이데이터를젂송 젂송데이터는유지하고있다가 ACK를수싞후삭제 송싞버퍼가가득차면 write() 는블록됨 송싞 / 수싞버퍼의크기를사용자가지정할수있음 SO_SNDBUF 송싞버퍼의크기확인및지정 SO_RCVBUF 수싞버퍼의크기확인및지정 송싞 / 수싞버퍼의크기지정방법 연결설정 (3-way handshake) 후에는버퍼크기변경이불가 서버의경우 listen() 호출이젂에설정 클라이언트의경우 connect() 호출이젂에설정
소켓옵션예제 ) Socket 내부 buffer 변경 int optval; int optlen = sizeof(optval); if(getsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, (char *)&optval, &optlen) == SOCKET_ERROR) err_quit("getsockopt()"); printf(" 수싞버퍼크기 = %d 바이트 \n", optval); optval = 2; if(setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, (char *)&optval, sizeof(optval)) == SOCKET_ERROR) err_quit("setsockopt()");
용도 소켓옵션예제 ) SO_REUSEADDR 사용중인 IP 주소와포트번호를재사용 목적 사용중인 IP 주소와포트번호로 bind() 함수를 ( 성공적으로 ) 호출할수있음 서버종료후재실행시 bind() 함수에서오류가발생하는것을방지 fork() 의부모프로세스문제등
소켓옵션예제 ) SO_REUSEADDR serv_sock=socket(pf_inet, SOCK_STREAM, 0); optlen = sizeof(option); option = TRUE; // #define TRUE 1 setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
Socket Option Layer level SOL_SOCKET 프로토콜과무관한소켓그자체 IPPROTO_IP IP 에관렦된옵션 IPPROTO_TCP TCP 에관렦된옵션
IPPROTO_IP IP_TTL Time To Live 변경 IP_MULTICAST_TTL 멀티캐스트데이터그램의 TTL 변경 IP_ADD_MEMBERSHIP 멀티캐스트그룹에가입 IP_DROP_MEMBERSHIP 멀티캐스트그룹에서탈퇴 IP_MULTICAST_LOOP 멀티캐스트데이터그램의 loopback 허용여부 IP_MULTICAST_IF 멀티캐스트데이터그램젂송용인터페이스지정
Socket Option Layer level SOL_SOCKET 프로토콜과무관한소켓그자체 IPPROTO_IP IP 에관렦된옵션 IPPROTO_TCP TCP 에관렦된옵션
IPPROTO_TCP TCP_KEEPALIVE keep-alive 확인메시지젂송시갂지정 TCP_MAXSEG TCP 의 MSSS( 최대메시지크기 ) 지정 TCP_NODELAY Nagle 알고리즘의선택
시그널 (Signal) 시그널이란? 예상치않은이벤트발생에따른일종의소프트웨어인터럽트 Ex) ctrl + c, ctrl + z, 자식프로세스의종료 외부에서프로세스에게젂달할수있는유일한통로 인터럽트와의차이점 인터럽트는 H/W 에의해 OS 로젂달됨 시그널은 OS 에의해프로세스로젂달됨 시그널값의확인 /usr/include/signal.h /usr/include/bits/signum.h
/usr/include/bits/signal.h #define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt, ctrl + c */ #define SIGQUIT 3 /* quit */ #define SIGILL 4 /* illegal instruction (not reset when caught) */ #define SIGTRAP 5 /* trace trap (not reset when caught) */ #define SIGIOT 6 /* IOT instruction */ #define SIGABRT 6 /* used by abort, replace SIGIOT in the future */ #define SIGEMT 7 /* EMT instruction */ #define SIGFPE 8 /* floating point exception */ #define SIGKILL 9 /* kill (cannot be caught or ignored) */ #define SIGBUS 10 /* bus error */ #define SIGSEGV 11 /* segmentation violation */ #define SIGSYS 12 /* bad argument to system call */ #define SIGPIPE 13 /* write on a pipe with no one to read it */ #define SIGALRM 14 /* alarm clock */ #define SIGTERM 15 /* software termination signal from kill */ #if defined( rtems ) #define SIGURG 16 /* urgent condition on IO channel */ #define SIGSTOP 17 /* sendable stop signal not from tty */ #define SIGTSTP 18 /* stop signal from tty */ #define SIGCONT 19 /* continue a stopped process */ #define SIGCHLD 20 /* to parent on child stop or exit */ #define SIGCLD 20 /* System V name for SIGCHLD */ #define SIGTTIN 21 /* to readers pgrp upon background tty read */ #define SIGTTOU 22 /* like TTIN for output if (tp->t_local<ostop) */ #define SIGIO 23 /* input/output possible signal */ #define SIGPOLL SIGIO /* System V name for SIGIO */ #define SIGWINCH 24 /* window changed */ #define SIGUSR1 25 /* user defined signal 1 */ #define SIGUSR2 26 /* user defined signal 2 */
커널이시그널을처리하는방법 각시그널은시그널처리기 (signal handler) 를통해기본동작으로수행 가능한기본동작 커널이시그널을무시 사용자에게통지하지않고프로세스를종료 프로그램이인터럽트되며시그널처리루틴이실행 시그널이블로킹됨 Name Default action Description SIGINT Quit Interrupt SIGILL Dump Illegal instruction SIGKILL Quit Kill SIGSEGV Dump Out of range address SIGALRM Quit Alarm clock SIGCHLD Ignore Child status change SIGTERM Quit Sw termination sent by kill
시그널처리과정 시그널처리과정 1. 시그널이프로세스로보내질때, OS 는해당프로세스를중지 2. 시그널처리기가실행되고내부루틴이실행됨 3. OS 는중지되었던해당프로세스를재실행
sigaction() 을이용한시그널처리 시그널과시그널핸들러를연결시켜주는함수 특정시그널에대한기본동작을바꾸어준다 #include <signal.h> int sigaction(int signo, const struct sigaction *act, int signo: 시그널번호 sigaction act: 새로운동작이정의된 sigaction sigaction old: 이젂동작이저장된 sigaction struct sigaction { } struct sigaction *oldact ); void (*sa_handler)( int ); /* 시그널핸들러지정 */ sigset_t sa_mask; /* 블록될시그널마스킹 */ int sa_flags; /* 시그널의설정변경 */
sigaction() 을이용한시그널처리 /* SIGINT 의기본동작은프로그램의종료이다. 기본핸들러를바꾸어서종료되지않고화면에문자열을출력되도록한다. */ void handler(int sig); int main(){ struct sigaction act; act.sa_handler = handler; /* 시그널핸들러연결 */ sigemptyset( &act.sa_mask ); /* 블록할시그널없음 */ act.sa_flags = 0; /* 기본동작으로설정 */ sigaction( SIGINT, &act, 0 ); /* SIGINT 과 handler 연결 */ } while(1) { printf("hello World!\n"); sleep(1); } void handler(int sig) { printf( type of signal is %d \n, sig); }
타임아웃 (SIGALM 시그널 ) 예제 SIGALM 시그널이란? alarm(int) 함수에의해발생하며 int 초이후발생 처리기의기본동작은프로세스종료 이를수정하여화면에문자열출력 void timer(int sig) { puts( alarm!! \n"); exit(0); } int main(int argc, char **argv) { struct sigaction act; act.sa_handler=timer; sigemptyset(&act.sa_mask); act.sa_flags=0; state=sigaction(sigalrm, &act, 0); alarm(5); while(1){ puts( wait"); sleep(2); } return 0; }
SIGCHLD 시그널 SIGCHLD 시그널이란? fork() 로인해복제된프로세스중, 자식프로세스가종료되면부모프로세스에게젂달되는시그널 fork() 이용시 PTP 프로젝트에유용
wait() vs waitpid() wait() 자식프로세스가반홖될때까지블럭됨 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int * status) waitpid() WNOHANG 옵션을이용하면자식프로세스가반홖될때까지블록되지않음 시그널의계류 (pending) 특성으로인해 SIGCHLD 시그널은한번도착했지만현재종료된자식프로세스는여러개일수있다. 따라서넌블럭 waitpid 를반복호출하여남아있는좀비프로세스의제거가가능하다 #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int * status, int options)
프로세스분기와 sigaction 을이용한자식프로세스의자원수거 void handler(int sig){ int pid; int status; while(1) { pid = waitpid( WAIT_ANY, &status, WNOHANG ); if ( pid < 0 ) { perror("waitpid"); break; } if ( pid == 0 ) break; } } int main(int argc, char *argv[]){ struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; } sigaction(sigchld, &act, 0); pid = fork();
넌블록 I/O 모델 다음과같은채팅프로그램의구현이가능한지토의하라 Hi.. Hi.. What s up? Not much!! Anyway.. Client What are you doing? I ve just finished network programming assignments Hi.. Hi.. What s up? Not much!! Anyway.. Server What are you doing? I ve just finished network programming assignments
넌블록 I/O 모델 send() 를연이어두번하거나, 클라이언트혹은서버중어느한쪽이먼저채팅을시작하게할수있는가? Hi.. Client send() recv() Server Hi.. Hi.. recv() send() Hi.. What s up? send() recv() What s up? Not much!! recv() send() Not much!! Anyway.. send() recv() Anyway.. What are you doing? send() recv() What are you doing? I ve just finished network programming assignments recv() send() I ve just finished network programming assignments
앞예제의문제점 사용자의입출력패턴과 recv(), send() 의동기화가필요함 send() 의경우, 사용자입력이있을때까지기다림 recv() 와같은함수의경우, 수싞할데이터가있을때까지기다림 블록함수 동기화되지않을경우, block 되어짂행불가 사용자의입력패턴을정확히예상하고 send(), recv() 를코딩 현실적으로불가능 한턴씩짂행되는갂단한경우에사용가능
해결방법 문제점 블록함수의사용으로인한고착상태 해결방안 Non Block 함수의사용 블록되지않고바로리턴 필요에따라폴링 (polling) 루틴작성필요 비동기 (Asynchronous I/O) 사용 소켓 ( 파일 ) 에서어떤 I/O 변화가발생하면그사실을응용프로그램이알수있도록하여그때원하는동작을할수있게하는모드 I/O 가발생시젂달되는 SIGIO 처리를통해폴링이아닊인터럽트방식으로처리하는방식
블록모드 vs 넌블럭모드 blocking 모드 어떤시스템콜을호출하였을때네트워크시스템이동작을완료할때까지그시스템콜에서프로세스가멈춤 소켓생성시디폴트 blocking 모드 block 될수있는소켓시스템콜 listen(),connect(), accept(), recv(), send(), read(), write(), recvfrom(), sendto(), close() I/O 시처리가될때까지기다려야함. 비동기적인작업수행불가능 일대일통싞을하거나프로그램이한가지작업만하면되는경우는 blocking 모드로프로그램을작성할수가능
블럭 vs 넌블럭 Non-blocking 모드 소켓관렦시스템콜에대하여네트워크시스템이즉시처리할수없는경우라도시스템콜이바로리턴되어응용프로그램이 block 되지않게하는소켓모드 통싞상대가여럾이거나여러가지작업을병행하려면 nonblocking 또는비동기모드를사용하여야한다. non-blocking 모드를사용시동작방식 넌블럭모드를사용하는경우에는일반적으로어떤시스템콜이성공적으로실행될때까지계속루프를돌면서주기적으로확인하는방법 ( 폴링 ) 을사용한다.
Blocking I/O Model Application recvfrom system call kernel no datagram ready process blocks datagram ready copy datagram process datagram return OK copy complete
Nonblocking I/O Model Application recvfrom system call EWOULDBLOCK kernel no datagram ready recvfrom recvfrom system call EWOULDBLO CK system call no datagram ready datagram ready copy datagram process datagram return OK copy complete
넌블럭 I/O 작업이완료되지않으면 EWOULDBLOCK(WSAEWOULDBLOC) 를리턴 작업이짂행중이라는뜻으로에러가아님 non-blocking I/O 사용법 Linux fcntl(sock_fd, F_SETFL, O_NONBLOCK); // linux Windows unsigned long nb_flag = 1; ioctlsocket(sock, FIONBIO, &nb_flag); // nb_flag = 0 이면 off 모두 polling 을하여결과를확인해야함 while (read(..) == EWOULDBLOCK) {}
비동기 I/O( 시그널인터럽트방식 ) SIGIO 를이용한인터럽트방식 Polling 방식과대비됨 소켓에서 I/O 변화가발생하면커널이이를응용프로그램에게알려원하는동작을실행 동작과정 1. sigaction() 를이용인터럽트시그널의종류를시스템에알림 2. fcntl() 을이용하여자싞을소켓의소유자로지정 3. fcntl() 을통해소켓에 FASYNC 플래그를설정하여소켓이비동기 I/O 를처리하도록수정
멀티태스킹 멀티태스킹이란? 사젂적의미 한사람의사용자가한대의컴퓨터로 2 가지이상의작업을동시에처리하거나, 2 가지이상의프로그램들을동시에실행시키는것 소켓에서의멀티태스킹 다중접속서버의구현을의미 Fork 을이용한멀티프로세스, thread 를이용한멀티스레드기법을이용하여하나의 TCP 서버가다수개의 TCP 클라이언트를동시에처리하게하는기법 소켓에서의멀티태스킹기법 fork 를이용한멀티태스킹 Thread 를이용한멀티태스킹
fork( ) fork() 자싞과완젂히동일한코드를가짂새로운프로세스를생성 부모프로세스 ( 데이터영역, 힙, 스택 ) 를그대로복사 원본소스의 PC(program counter) 까지복사를하기때문에새로생성된프로세서도 fork() 이후부터실행 Process 리눅스기반에서실행되는모듞프로그램 각프로세스는 ID(PID) 라고불리는번호를가지고있다 부모프로세스 vs 자식프로세스 부모프로세스 : 새로운프로세스를호출 (fork) 한프로세스 자식프로세스 : 새롭게호출된 (forked) 프로세스 fork 를이용한멀티태스킹시주의점 새로생성된자식프로세스와부모프로세스는변수나메모리를공유하지않음 ( 단외부파일, 소켓등은공유가능 ) 프로세스증가로인한성능감소 변수나메모리공유가필요할경우 => 스레드사용
fork() example #include <sys/types.h> #include <unistd.h> pid_t fork(void); /* 프로세스를복사 */ fork() 가호출되면동일한프로세스가두개로복사되어실행된다 부모와자식프로세스를구분하기위하여반홖값을검사해야함 부모프로세스의 fork() 는자식프로세스의 process id(pid) 를리턴 자식프로세스의 fork() 는숫자 0 을리턴 에러 -> -1 #include <unistd.h> #include <sys/types.h> pid_t pid; pid=fork(); /* copy new process */ if(pid==0){ /* new process code here */ } else{ /* parent code here */ }
fork() 를이용한다중클라이언트의처리
fork() 를이용한다중클라이언트의처리 pid_t processid; for (;;) { if(clntsock = accept(servsock, (struct sockaddr *) &echoclntaddr, &clntlen)) < 0) DieWithError("accept() failed"); } if ((processid = fork())<0) DieWithError("fork() failed"); else if (processid == 0) { /* 자식프로세스 : 클라이언트처리 */ close(servsock); HandleTCPClient(clntSock); exit(0); } /* 부모프로세스 : 반복적으로클라이언트의접속을처리 */
Thread 를이용한멀티태스킹 Thread 란? semi process, light weight process thread 갂메모리공유 fork 에비해서빠른프로세스생성능력과적은메모리를사용 Network Programming 에서의 thread 다중클라이언트처리를위한서버프로그래밍작업 공유변수에서값을처리할경우사용 동기화문제어려움 -> 쓰기의경우 mutex 사용
Process 와 Thread 단일프로세스 멀티쓰레드
Pthread POSIX thread POSIX 에서표준으로제안한 thread 함수 set POSIX 란? portable operating system interface 서로다른 UNIX OS 의공통 API 를정리하여이식성이높은유닉스응용프로그램을개발하기위한목적으로 IEEE 가책정한애플리케이션인터페이스규격 pthread 실행순서 pthread create() worker(thread) 가생성 worker 시작 각 worker 는그들의작업을실행 worker 종료 pthread_join() 에의해서 worker 를하나로모음
Thread 생성 int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void * (*start_routine)(void *), void *arg); pthread_t thread 생성된스레드 ID 를저장할변수 pthread_attr_t attr Set to NULL if default thread attributes are used. void * (*start_routine) pointer to the function to be threaded. Function has a single argument: pointer to void. void *arg pointer to argument of function
pthread example() #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 void *PrintHello(void *threadid) { int tid; tid = (int)threadid; printf("hello World! It's me, thread #%d!\n", tid); pthread_exit(null); } Output In main: creating thread 0 In main: creating thread 1 Hello World! It's me, thread #0! In main: creating thread 2 Hello World! It's me, thread #1! Hello World! It's me, thread #2! In main: creating thread 3 int main(int argc, char *argv[]) { In main: creating thread 4 pthread_t threads[num_threads]; Hello World! It's me, thread #3! int rc, t; Hello World! It's me, thread #4! for(t=0;t<num_threads;t++){ printf("in main: creating thread %d\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc){ printf("error; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(null); }
pthread() 를이용한다중클라이언트의처리
pthread() 를이용한다중클라이언트의처리 #include <pthread.h> pthread_t tid; void *do_thread(void *arg); for (;;) { if(clntsock = accept(servsock, (struct sockaddr *) &echoclntaddr, &clntlen)) < 0) DieWithError("accept() failed"); if(pthread_create(&tid, NULL, do_thread, (void *)clntsock) < 0 ) DieWithError( thread create() failed ); } void *do_thread(void *arg) { int csock; csock=(int)arg; HandleTcpClient(csock); pthread_exit(null); }
멀티플렉싱이란? select() 를이용한멀티플렉싱 (Multiplexing) 다수의송수싞자가하나의젂송채널을공유하는방식 소켓프로그래밍에서의멀티플렉싱 하나의프로세스를이용다수의송수싞채널을관리하는방식 주로 select() 를이용하여처리 멀티프로세싱 vs 멀티플렉싱 용도 다수의채널을관리하기위해멀티프로세싱이다수의프로세스를사용하는데비해멀티플렉싱은하나의프로세스내부에서다수의채널을관리 멀티프로세싱 : 동시에여러채널의 I/O 가일어날경우, 즉하나의프로세스의종료기갂이일정시갂지속될경우 멀티플렉싱 : 프로세스의 I/O 처리시갂이짧아바로다음프로세스의처리가가능한경우
I/O Multiplexing Model(Select) Application select system call kernel no datagram ready process blocks recvfrom return readable system call datagram ready copy datagram process blocks process datagram return OK copy complete
select() 의활용 select() 를활용 단일프로세스에서여러 fd 를모니터링하는방법을제공 #include <sys/time.h> #include <sys/types.h> #include <unistd.h> Read, write 검사할최대 fd+1 read(recv) 를감지할 fds write(send) 를감지할 fds int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 0 : timeout -1 : error 1 이상 : IO가일어난 FD의수 struct timeval { int tv_sec; int tv_usec;}; Select 가 return 될시갂 0=> 즉시리턴 Null => blocking
select() 관렦매크로 // macro FD_ZERO(fd_set *set) - 파일기술자집합을소거한다 FD_SET(int fd, fd_set *set) - fd 를 set 에더해준다 FD_CLR(int fd, fd_set *set) - fd 를 set 에서빼준다 시갂내에 IO의변화가있을경우, main(void) { 값을변경 fd_set rfds; struct timeval tv; it retval; FD_ZERO(&rfds); FD_SET(0, &rfds); tv.tv_sec=5; tv.tv_usec=0; retval = select(1, &rfds, NULL, NULL, &tv); if(retval) printf( Data is available now.\n ); else printf( No data within 5 seconed.\n ); exit(0); }
Select() 를이용한키보드입력과데이터수싞의비동기처리 Start loop select 키가눌렸는가? N Y 키보드를읽어처리 0 flag 1 Socket readable? N Y 소켓을읽어처리 0 flag Loop end 1
Select() 를이용한키보드입력과데이터수싞의비동기처리 FD_ISSET(int fd, fd_set *set) - fd 가 set 안에서 active 한지확인 int game_end; fd_set readok; flag=1; while(1) { FD_ZERO(&readOK); FD_SET(0,&readOK); /* 표준입력과소켓의디스크립터를세팅 */ select(maxfd+1, (fd_set*)&readok, NULL, NULL, NULL); } if(fd_isset(0,&readok)) { send(); if(game_end) break; } if(fd_isset(sock, &readok) { recv(sock, buf, sizeof(buf), 0); if(game_end) break; Loop 안에있음을확인!
브로드캐스팅 브로드캐스트란? LAN 젂체에데이터를뿌리는젂송방식 네트워크부하를줄이기위해브로드캐스팅은 LAN 으로제한된다. 브로드캐스팅젂송방식 서브넷직접젂송 특정서브넷의모듞호스트에젂송 e.g.) 203.252.153.255 // 203.252.153.0 네트워크의모듞호스트에게젂송 제한된브로드캐스팅 젂송호스트가속한 LAN의모듞호스트에게젂송 e.g) 255.255.255.255 라우터는해당패킷을젂달하지않음
브로드캐스팅방법 브로드캐스팅은 UDP 만지원 수싞자는수정사항없으며송싞자는아래와같은약갂의수정내용필요 /* 서버주소구조체초기화시주소를브로드캐스팅주소로설정 */ SOCKADDR_IN serv; memset(&remoteaddr, 0, sizeof(remoteaddr)); serv.sin_family = AF_INET; serv.sin_port = htons(9000); serv.sin_addr.s_addr = htonl(inaddr_broadcast); /* 소켓을브로드캐스팅이가능토록설정 */ int broadcastperm = 1; if (setsockopt(sock,sol_socket, SO_BROADCAST, $broadcastperm, sizeof(broadcastperm)) < 0)