730 likes | 1.29k Views
얇지만 얇지 않은 TCP/IP 소켓 프로그래밍 C 2 판. (TCP/IP Sockets in C 2/e, Morgan Kaufmann). 마이클 도나후 (Michael J. Donahoo) 케네스 칼버트 (Kenneth L. Calvert). Chapter 06 Beyond Basic Socket Programming. 제 6 장 중급 소켓 프로그래밍. 6.1 소켓 옵션 6.2 시그널 6.3 넌블로킹 입 / 출력 6.4 멀티태스킹 6.5 멀티플렉싱 6.6 다수의 수신자 처리.
E N D
얇지만 얇지 않은 TCP/IP 소켓 프로그래밍 C 2판 (TCP/IP Sockets in C 2/e, Morgan Kaufmann) 마이클 도나후(Michael J. Donahoo)케네스 칼버트(Kenneth L. Calvert)
Chapter 06 Beyond Basic Socket Programming 제 6장 중급 소켓 프로그래밍 • 6.1 소켓 옵션 • 6.2 시그널 • 6.3 넌블로킹 입/출력 • 6.4 멀티태스킹 • 6.5 멀티플렉싱 • 6.6 다수의 수신자 처리
소켓 옵션 • 소켓 옵션(socket options) • 소켓의 기본 동작을 변경 • 소켓 코드와 프로토콜 구현 코드에 대한 세부적인 제어 가능 • 소켓 옵션관련함수 • s : 소켓번호 • level : 프로토콜 레벨 • SOL_SOCKET: 소켓의 일반적인 옵션 변경 • IPPROTO_IP: IP 프로토콜에 관한 옵션 변경 • IPPROTO_TCP: TCP에 관한 옵션 변경 • opt : 사용하고자 하는 옵션 • optval : 옵션 지정에 필요한 값의 포인터 • optlen : optval의 크기 #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);
Socket Option Layer • level • SOL_SOCKET • 프로토콜과 무관한 소켓 그 자체 • IPPROTO_TCP • TCP에 관련된 옵션 • IPPROTO_IP • IP에 관련된 옵션
SOL_SOCKET • Option name • SO_BROADCAST: 방송형 메시지 전송 허용 • SO_DEBUG: DEBUG 모드를 선택 • SO_REUSEADDR: 주소 재사용 선택 • SO_LINGER • 소켓을 닫을 때 미전송된 데이터가 있어도 지정된 시간만큼 기다렸다가 소켓을 닫음 • SO_KEEPALIVE: TCP의 keep-alive 동작 선택 • SO_OOBINLINE: OOB 데이터를 일반 데이터처럼 읽음 • SO_RCVBUF: 수신버퍼의 크기 변경 • SO_SNDBUF: 송신버퍼의 크기 변경
TCP Timer • TCP Retransmission Timer • TCP Persist Timer • TCP Keepalive Timer • TCP Time-Waited Timer
소켓 옵션 예제) 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 • 멀티캐스트 데이터그램 전송용 인터페이스 지정
멀티캐스트(Multicast) • 전송 방식. • - UDP를 기반으로 하는 전송 방식. • - 멀티캐스트 그룹을 기반으로 멀티캐스트 패킷을 주고 받음. • - 하나의 멀티캐스트 패킷은 라우터를 통해서 다수의 호스트에 전송.
멀티캐스트(Multicast) • 2. 라우팅(Routing)과 TTL(Time To Live) • - 라우터에 의해서 패킷이 경로를 찾는 과정을 라우팅이라 한다. • - 멀티캐스트 패킷 내에는 TTL 정보가 포함된다. TTL은 거쳐 갈 수 있는 • 라우터의 수를 의미한다.
멀티캐스트(Multicast) 3. 멀티캐스트 Sender와 Receiver. - Sender : 임의의 멀티캐스트 그룹에 데이터를 전송하는 호스트 - Receiver : 임의의 멀티캐스트 그룹으로부터 데이터를 수신하는 호스트, 4. 멀티캐스트 Sender와 Receiver의 구현
브로드캐스트(Broadcast) • 전송 방식. • - UDP를 기반으로 하는 전송 방식(멀티캐스트와 같다). • - 일반적인 UDP 패킷과의 차이점은 전송 목적지 IP주소 뿐이다. • - 동일 네트워크에 속하는 모든 호스트에 동시 전송(멀티캐스트와의 차이점). • - 인터넷상에서는 지역 네트워크내에서만 브로드캐스트를 허용한다(네트워크의 • 부하를 고려).
브로드캐스트(Broadcast) • 2. 주소선택에 따른 브로드캐스트 방식의 구분. • - 지정된 브로드캐스트 : 예 192.12.31.255 • - 지역적 브로드캐스트 : 예 255.255.255.255 192 12 31 xxx 네트워크 IP 호스트 IP 192 12 31 255 브로드캐스트 address 255 255 255 255 브로드캐스트 address
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 알고리즘의 선택
Nagle 알고리즘에 대한 이해. • 네트워크상의 패킷 수를 줄이기 위해 제안된 알고리즘. • ACK를 수신해야만 다음 전송을 진행하는 알고리즘.
Nagle 알고리즘의 장점과 단점. • 장점 : 네트워크의 효율성이 높아진다.(적은 패킷의 양) • 단점 : 전송 속도가 느리다(ACK 수신 후 패킷 전송). • 생각해 볼 문제 : Nagle 알고리즘의 중단이 데이터 전송 속도를 무조건 • 향상시켜 주는 것은 아니다.
TCP_NODELAY • Nagle 알고리즘을 Disable 시키기 위한 옵션의 변경. • TCP 소켓은 생성시 기본적으로 Nagle 알고리즘 적용. serv_sock=socket(PF_INET, SOCK_STREAM, 0); opt_val = TRUE; // #define TRUE 1 setsockopt(serv_sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, sizeof(opt_val));
멀티태스킹 • 멀티태스킹이란? • 사전적 의미 • 한 사람의 사용자가 한 대의 컴퓨터로 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_tpid; pid=fork(); /* copy new process */ if(pid==0){ /* new process code here */ } else{ /* parent code here */ }
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() 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 In main: creating thread 4 Hello World! It's me, thread #3! Hello World! It's me, thread #4! #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); } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc, t; 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()를 이용한 다중 클라이언트의 처리 #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); }
시그널(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 */
Name Default action Description SIGINT Quit Interrupt SIGILL Dump Illegal instruction SIGKILL Quit Kill SIGSEGV Dump Out of range addr SIGALRM Quit Alarm clock SIGCHLD Ignore Child status change SIGTERM Quit Sw termination sent by kill 커널이 시그널을 처리하는 방법 • 각 시그널은 시그널 처리기(signal handler)를 통해 기본 동작으로 수행 • 가능한 기본 동작 • 커널이 시그널을 무시 • 사용자에게 통지하지 않고 프로세스를 종료 • 프로그램이 인터럽트 되며 시그널 처리 루틴이 실행 • 시그널이 블로킹됨
시그널 처리 과정 • 시그널 처리 과정 • 시그널이 프로세스로 보내질 때, OS는 해당 프로세스를 중지 • 시그널 처리기가 실행되고 내부 루틴이 실행됨 • OS는 중지되었던 해당 프로세스를 재 실행
sigaction()을 이용한 시그널 처리 • 시그널과 시그널 핸들러를 연결시켜 주는 함수 • 특정 시그널에 대한 기본 동작을 바꾸어 준다 • int signo: 시그널 번호 • sigaction act: 새로운 동작이 정의된 sigaction • sigaction old: 이전 동작이 저장된 sigaction #include <signal.h>int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact ); struct sigaction { 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() vswaitpid() • wait()– 자식프로세스가 반환될 때까지 블럭됨 • waitpid()– WNOHANG옵션을 이용하면 자식 프로세스가 반환될 때까지 블록 되지 않음 • 시그널의 계류(pending) 특성으로 인해 SIGCHLD 시그널은 한번 도착했지만 현재 종료된 자식 프로세스는 여러 개 일 수 있다. 따라서 넌블럭 waitpid를 반복 호출하여 남아있는 좀비 프로세스의 제거가 가능하다 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int * status) #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 모델 • 다음과 같은 채팅 프로그램의 구현이 가능한지 토의하라 Client Server Hi.. Hi.. What’s up? Not much!! Anyway.. What are you doing? I’ve just finished network programming assignments Hi.. Hi.. What’s up? Not much!! Anyway.. What are you doing? I’ve just finished network programming assignments
넌 블록 I/O 모델 • send()를 연이어 두 번 하거나, 클라이언트 혹은 서버 중 어느 한쪽이 먼저 채팅을 시작하게 할 수 있는가? Server Client recv() Hi.. Hi.. What’s up? Not much!! Anyway.. What are you doing? I’ve just finished network programming assignments send() Hi.. Hi.. What’s up? Not much!! Anyway.. What are you doing? I’ve just finished network programming assignments send() recv() recv() send() send() recv() recv() send() send() recv() send() recv()
앞 예제의 문제점 • 사용자의 입출력 패턴과 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 모드를 사용 시 동작 방식 • 넌블럭 모드를 사용하는 경우에는 일반적으로 어떤 시스템 콜이 성공적으로 실행될 때까지 계속 루프를 돌면서 주기적으로 확인하는 방법(폴링)을 사용한다.