340 likes | 657 Views
Network Simulation. Prof. Jae-Hwoon Lee Dept. of Information & Communication Engineering, Dongguk University e-mail: jaehwoon@dongguk.edu http://cnel.dongguk.ac.kr. 4 장 고급 소켓 프로그래밍. 다중 처리 기술 소켓의 동작 모드 비동기형 채팅 프로그램 폴링형 채팅 프로그램. 4.1 다중 처리 기술. 다중 처리 동시에 여러 작업을 처리하는 것
E N D
Network Simulation Prof. Jae-Hwoon Lee Dept. of Information & Communication Engineering, Dongguk University e-mail: jaehwoon@dongguk.edu http://cnel.dongguk.ac.kr
4장 고급 소켓 프로그래밍 • 다중 처리 기술 • 소켓의 동작 모드 • 비동기형 채팅 프로그램 • 폴링형 채팅 프로그램
4.1 다중 처리 기술 • 다중 처리 • 동시에 여러 작업을 처리하는 것 • 멀티태스킹 (multitasking) • 여러 작업을 여러 프로세스(또는 스레드)들이 나누어 다중 처리하는 방법 • 다중화 (multiplexing) • 한 프로세스 내에서 여러 작업(다중 입출력 등)을 처리하는 다중 처리 기능
4.1.1 멀티태스킹 • 여러 작업(task)을 병행하여 처리하는 기법 • 유닉스에서는 멀티태스킹을 위해 프로세스를 여러 개 실행시키거나(멀티프로세스) 또는 스레드(thread)를 여러 개 실행시키는 방법(멀티스레드)를 사용
멀티프로세스 • 독립적으로 처리해야 할 작업의 수 만큼 프로세스를 만드는 방법 • 장점 : 각 프로세스들이 독립적으로 작업을 처리하므로 구현이 간편 • 단점 : • 병렬처리 해야 할 작업 수만큼 프로세스를 생성해야 함 • 프로세스가 많이 생성될수록 메모리 사용량이 증가하고 프로세스 스케줄링 횟수가 많아져 프로그램의 성능이 저하됨 • 동시에 개설할 수 있는 프로세스의 수가 제한됨 • 프로세스들 사이에 데이터를 공유하는 것이 불편함 • 프로세스간 데이터 공유를 위해서는 운영체제의 도움을 받아 프로세스간 통신(IPC: Inter-Process Communication)을 하여야 하며 이로 인한 프로그램 구현이 복잡
멀티스레드 • 스레드 – 프로세스 내에서 독립적으로 실행되는 작업 단위 • 프로세스에서 스레드를 생성하면 새로 만들어진 스레드는 원래 프로세스의 이미지를 같이 사용한다 • 단, 새로 생성된 스레드용 스택 영역은 스레드별로 별도로 배정된다. • 장점 - 한 프로세스 내에서 생성된 스레드들은 이미지를 공유하므로 전역 변수를 같이 사용하므로 서로 쉽게 데이터를 공유 • 단점 – 여러 스레드가 한 데이터에 동시에 접근하는 경우에 동기화 문제가 발생할 수 있음.
4.1.2 다중화 • 파일에서 데이터를 읽기 위해 read()를 호출하면 • 대부분 즉시 데이터를 읽어오거나 잠시 기다리면 결과를 얻게 됨 • 네트워크로부터 데이터를 읽기 위해 read()를 호출하면 • 수신 버퍼에 이미 수신된 데이터가 있었다면 read()가 바로 리턴 • 아직 수신한 데이터가 없으면 read()는 데이터가 도착할 때까지 계속 기다림(블록) • 한 프로세스에서 다수의 입출력을 처리해야 하는 경우에 블록될 수 있는 입출력 함수를 사용하면 안됨 • 한 곳에서 입출력이 블록되면 프로그램 전체가 블록되게 됨. • 동시에 처리해야 할 입출력의 수만큼 프로세스(또는 스레드)를 생성하고 각 프로세스가 입출력을 독립적으로 담당 • 생성할 수 있는 프로세스(또는 스레드) 수에 한계가 있어 사용에 제한 • 다중화란 한 프로세스(또는 스레드) 내에서 이루어지는 다중 처리 기법 • 폴링(polling) • 셀렉팅(selecting) • 인터럽트(interrupt)
다중화 (계속) • 폴링 • 처리해야 할 작업들을 순차적으로 돌아가면서 처리하는 방법 • 입출력 함수가 어느 한 곳에서 블록되지 않도록 파일이나 소켓을 non-block 모드로 설정해야 함 • 셀렉트 • 어떤 클라이언트로부터 데이터가 도착하면 서버는 데이터가 도착한 클라이언트의 입출력을 처리하는 방식 • 유닉스에서는 select() 함수를 이용하여 입출력을 처리할 파일이나 소켓들을 비동기 모드로 바꾸어야 함 • 인터럽트 • 프로세스가 어떤 작업을 처리하는 도중에 특정한 이벤트가 발생하면 해당 이벤트를 처리하는 방식 • 유닉스에서는 프로세스 사이에 이벤트 발생을 알려주기 위해서 시그널(signal)를 제공하는데 시그널을 사용하여 데이터 입출력을 인터럽트 방식으로 처리 • 시그널을 소프트웨어 인터럽트라고 함
4.1.3 다중처리 예 • 채팅 프로그램 • 채팅 클라이언트 • 채팅에 참가하는 절차가 필요하고 채팅 메시지 입력과 다른 클라이언트가 보낸 채팅 메시지를 (서버를 경유하여) 수신하고 화면에 출력하는 기능이 필요 • 채팅 서버 • 클라이언트가 채팅 참가 요청을 보내오면 이를 접수하여 채팅 참가 리스트에 추가 • 채팅에 참가한 클라이언트들이 보내오는 메시지를 수신하여 모든 채팅 참가자에게 메시지를 방송 • 서버에서는 여러 작업을 병행하여 처리하는 기능이 필요함 • 멀티프로세스형 서버 프로그램 • 새로운 클라이언트가 접속될 때마다 프로세스를 만들고 각 프로세스가 각 클라이언트와의 통신을 담당 • 폴링형 서버 프로그램 • 소켓을 넌블록 모드로 설정하고 돌아가면서 클라이언트의 입출력 상태를 확인 • 셀렉팅형 서버 프로그래밍 • 셀렉팅을 이용하여 서버가 다수의 클라이언트와의 통신을 처리 • 인터럽트형 서버 프로그램 • 시그널을 이용하여 클라이언트로부터 연결요청이 오거나 데이터가 도착하면 인터럽트 방식으로 처리
4.2 소켓의 동작 모드 • 블록(blocking) 모드 • 시스템 콜을 호출하였을 때 동작을 완료할 때까지 시스템 콜에서 프로세스가 멈추어 있는 모드 • 넌블록(non-blocking) 모드 • 소켓 관련 시스템 콜에 대하여 시스템이 즉시 처리할 수 있으면 바로 결과를 리턴하고, 즉시 처리할 수 없는 경우라도 시스템 콜이 바로 리턴되어 응용 프로그램이 블록되지 않도록 함 • 다중화를 위해 시스템 콜이 성공적으로 실행될 때까지 계속 확인(폴링)하는 방법을 주로 사용 • 비동기(asynchronous) 모드 • 소켓에서 어떤 I/O 변화가 발생하면 그 사실을 응용 프로그램이 알 수 있도록 하여 그 때 입출력 처리 등 원하는 동작을 하는 모드 • Select()를 이용하는 방법 • I/O 변화가 발생할 수 잇는 소켓들 전체를 대상으로 select()를 호출해두면 그 중 임의의 소켓에서 I/O 변화가 발생되었을 때 select() 문이 리턴되고 해당 소켓에 대해 원하는 작업을 하는 방법 • Fcntl()를 사용하여 소켓을 signal-driven I/O 모드로 변경하는 방법 • 특정 소켓에서 I/O 변화가 발생하였을 때 SIGIO 시그널을 발생시키고 응용 프로그램에서는 이 시그널을 받으면 필요한 작업을 하는 방법
4.3 비동기형 채팅 프로그램 • 셀렉팅을 이용하여 채팅 서버 프로그램 구현 • 채팅 서버의 기능 • 클라이언트의 채팅 참가 요청 수락 • 클라이언트들의 송신 메시지를 다른 클라이언트들에게 방송 • select()를 사용하여 소켓을 비 동기 모드로 설정
4.3.1 채팅 서버 프로그램 구조 Select()를 이용한 비동기형 채팅 서버 비동기형 채팅 서버 socket() bind() 모든 소켓의 I/O 변화 감지 select() (채팅 참가 신청) Y N (채팅 메시지 수신) 연결용 소켓? accept() 채팅 메시지 발송 client_s[]에 새로운 소켓번호 추가
4.3.1 채팅 서버 구조 채팅 서버와 클라이언트의 연결 관계 채팅 클라이언트 (chat_client.c) 채팅 서버 chat_server.c 채팅 참가자 1 select() 채팅 참가자 2 채팅 참가자 3 소켓 s client_s[] 채팅 참가자 4 TCP/IP 채팅 참가 신청중
Select() 시스템 콜의 사용문법 4.3.2 select() 시스템 콜 the descriptors that we want the kernel to test for reading, writing, and exception conditions the number of descriptors to be tested (“max fd plus 1”) #include <sys/types.h> /* fd_set data type */ #include <sys/time.h> /* struct timeval */ #include <unistd.h> /* function prototype might be here */ int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tvptr); Returns: count of ready descriptors, 0 on timeout, -1 if error
tvptr의 구조체 select()가 기다리는 시간을 지정 • tvptr == NULL • wait forever • tvptr->tv_sec == 0 && tvptr->tv_usec == 0 • don’t wait at all • tvptr->tv_sec != 0 || tvptr->tv_usec != 0 • wait the specified number of seconds and microseconds struct timeval *tvptr struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ };
fd_set 타입의 구조체와 소켓번호와의 관계 fd0 fd1 fd2 fd3 • Four macros on a variable of the fd_set data type • FD_ZERO (fd_set *fdset); /* fdset의 모든 비트를 지운다 */ • FD_SET (int fd, fd_set *fdset); /* fdset 중 소켓 fd에 해당하는 비트를 1로 한다 */ • FD_CLR (int fd, fd_set *fdset); /* fdset 중 소켓 fd에 해당하는 비트를 0으로 한다 */ • FD_ISSET (int fd, fd_set *fdset); /* fdset 중 소켓 fd에 해당하는 비트가 세트되어 있으면 양수 값을 리턴한다 */ readfd 1 0 0 1 … | | one bit per possible descriptor writefd 0 1 0 1 … | | fd_set data type exceptfd 0 0 0 0 … maxfdp1 - 1
select() 호출 • fd_set 구조체 구성의 예 • read_fds를 select()의 두 번째 인자(읽기 변화 감지용 fd_set)로 지정하고 쓰기 및 예외발생에 해당하는 fd_set은 NULL로 지정하여 select()를 호출한 예 fd_set read_fds; FD_ZERO(&read_fds); // read_fds의 모든 소켓을 0으로 초기화 FD_SET(s, &read_fds); // 연결용 소켓을 선택 for(i=1; i< num_chat; i++) // 모든 통신용 소켓을 선택 FD_SET(clisock_list[i], &read_fds); select(maxfdp1, &read_fds, NULL, NULL, NULL);
select() 호출 (cont.) • FD_ISSET 매크로를 이용하여 어떤 소켓에서 I/O 변화가 발생했는지 확인하는 예 if (FD_ISSET(s, &read_fds)){ // 연결용 소켓 s에서 입력 발생 // 채팅 참가 신청 처리 } // 클라이언트 소켓번호 어레이 client[]를 차례로 검색 for(i=0;i<num_chat;i++){ if(FD_ISSET(clisock_list[i], &read_fds)){ // 통신용 소켓 clisock_list[i]에서 채팅 메시지 수신 // 모든 참가자에게 채팅 메시지 방송 } }
4.3.3 채팅 서버 프로그램 • chatting server program • chat_server.c … void addClient(int s, struct sockaddr_in, *newcliaddr); // 새로운 채팅 참가자 처리 int getmax(); // 최대 소켓 번호 찾기 void removeClient(int s); // 채팅 탈퇴 처리 함수 … socket(); /* socket open */ bind(); /* call bind() */ listen(); /* wait for chatting client */ maxfdp = s + 1; /* max socket descriptor + 1 */ …
채팅 서버 프로그램 (Cont.) • chatting server program • chat_server.c … while(1) { FD_ZERO(&read_fds); /* initialize the set : all bits off */ FD_SET(s, &read_fds); /* turn on bit for listening socket descriptor */ for(i=0 ; i<num_chat ; i++) FD_SET(client_s[i], &read_fds); /* turn on bit for connected socket descriptor */ maxfdp1 = getmax(s) + 1; /* maxfdp1 recalculate */ select(maxfdp1, &read_fds, (fd_set *)0, (fd_set *)0,(struct timeval *)0); if(FD_ISSET(s, &read_fds)) { /* new client connection */ accept(); client_s[num_chat] = client_fd; /* save descriptor */ num_chat++; }
채팅 서버 프로그램 (Cont.) • chatting server program • chat_server.c … for(i=0 ; i<num_chat ; i++) { /* check all clients for data */ if(FD_ISSET(client_s[i], &read_fds)) { recv(client_s[i], rline, MAXLINE, 0); } for(j=0 ; j<num_chat ; j++) /* send data to all client */ send(client_s[j], rline, n, 0); } …
채팅 서버 프로그램 (Cont.) • chatting server program • Result (x86 Linux System : cnel-cnp1) [cnel-cnp1] %chat_server 9013 [cnel-cnp2] %client_chat 210.94.213.194 9013 manuj [cnel-cnp1] % 1번째 사용자 추가. [cnel-cnp2] %client_chat 210.94.213.194 9013 jaehwoon [cnel-cnp1] % 1번째 사용자 추가. 2번째 사용자 추가.
통신용 소켓 구분 • 채팅 서버는 클라이언트와 통신을 위해 다수의 통신용 소켓(accept()가 리턴한 소켓들)을 개설 • 다수의 소켓들이 모두 같은 포트 번호를 사용 • 각 클라이언트의 연결을 구분하기 위하여 “클라이언트의 IP 주소와 포트번호”를 내부적인 키로 사용 채팅 서버 통신용소켓 연결용소켓 상대방(클라이언트)의 (IP+포트번호)로 구분 TCP 포트 번호 4001 IP TCP 서버에서 다수의 통신용 소켓을 구분하는 방법
4.3.4 채팅 클라이언트 프로그램 • TCP 소켓을 이용한 채팅 클라이언트 프로그램 작성 • select() 시스템 콜을 이용한 non-block chatting client • 메시지 입력 • 키보드로부터의 표준 입력 • 메시지 출력 • 모니터 화면으로 표준 출력 • 프로그램 종료 • 특정 종료문자 입력 시 프로그램 종료
채팅 클라이언트 프로그램 구성 방법 • tcp_chatcli.c의 사용 방법의 예 • 클라이언트가 보내는 모든 채팅 메시지 앞부분에 클라이언트의 이름을 붙여서 보내는 방법 $ tcp_chatcli 210.94.213.194 철수 char bufall[MAXLINE+NAME_LEN], // 이름+메시지를 저장하기 위한 버퍼 *bufmsg; // bufall에서 메시지 시작부분의 포인터 sprintf(bufall, “[%s] : “, “철수”); // bufall의 앞부분에 이름을 저장 namelen=strlen(bufall); bufmsg = bufall+namelen; // 메시지의 시작 부분을 찾음 fgets(bufmsg, MAXLINE, stdin); send(s, bufall, namelen+strlen(bufmsg), 0);
채팅 클라이언트 프로그램 • chatting client program • chat_client.c … fd_set read_fds; /* fd_set struct */ FD_ZERO(&read_fds); /* initialize the set : all bits off */ maxfdp1 = s + 1; /* maxfdp1 calculate */ while(1) { FD_SET(0, &read_fds); /* turn on bit for standard input */ FD_SET(s, &read_fds); /* turn on bit for connected socket descriptor */ select(maxfdp1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); if (FD_ISSET(s, &read_fds)) /* check receive message from server */ if (FD_ISSET(0, &read_fds)) /* check send message to server */ } …
채팅 클라이언트 프로그램 (Cont.) • chatting client program • Result (x86 Linux System : cnel-cnp2) [cnel-cnp2] %client_chat 210.94.213.194 9013 manuj [cnel-cnp2] %client_chat 210.94.213.194 9013 jaehwoon [cnel-cnp1] %chat_server 9013 1번째 사용자 추가. 2번째 사용자 추가. [cnel-cnp2] %client_chat 210.94.213.194 9013 manuj 서버에 접속되었습니다. Connected to chat_server Hello world! [manuj] Hello world! [jaehwoon] OK exit Good bye.
4.4 폴링형 채팅 프로그램 • 폴링 방식으로 채팅 서버를 구현 • 소켓을 넌블록 모드로 설정하고 채팅 메시지 수신 여부를 폴링 방식으로 점검
4.4.1 fcntl() • file control을 의미하며 소켓을 넌블록 모드나 비동기 모드로 변경할 수 있음 • 소켓을 넌블록 모드로 설정 • 소켓을 비동기 모드로 설정 • 소켓의 소유자를 설정하거나 현재 소유자를 얻어옴 • fcntl()의 사용문법 • 구체적인 옵션을 설정하는 데 사용 • O_NONBLOCK : 넌블록 모드로 설정 • O_ASYNC : SIGIO 시그널에 의해 구동되도록 비동기 모드로 설정 #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, long flag); • 명령 • F_SETFL : 플래그 세트 • F_GETFL : 플래그 읽기 • F_SETOWN : 소켓의 소유자를 설정 • F_GETOWN : 현재 소켓의 소유자를 얻어옴. 모드 변경을 원하는 소켓 디스크립터
모드 설정 • 넌블록 모드 설정 코드 • 비동기 모드 설정 코드 • 소켓의 소유자를 현재 프로세스로 정하는 방법과 소유자를 알아내는 코드 int val; if((val = fcntl(sock_fd, F_GETFL, 0))<0 exit(1); val |= O_NONBLOCK; if((fcntl(sock_fd, F_SETFL, val) <0) exit(1); int val; if((val = fcntl(sock_fd, F_GETFL, 0))<0 exit(1); val |= O_ASYNC; if((fcntl(sock_fd, F_SETFL, val) <0) exit(1); fcntl(fd, F_SETOWN, getpid()); fcntl(fd, F_GETOWN, &pid);
4.4.2 폴링형 채팅 서버 • 넌블록 모드 • 소켓들을 fcntl()을 이용하여 넌블록 모드로 설정한 후 무한 루프를 돌면서 입출력을 폴링 방식으로 처리 • 넌블록 모드의 소켓에 대해 read()를 호출한 경우의 처리 방법 • accept()가 리턴한 통신용 소켓의 디폴트는 블록 모드 n=recv(s, buf, length, 0); if(n>0) { // 정상적으로 읽은 데이터처리 } else if (n==-1 && errno == EWOULDBLOCK) // 에러가 아니므로 다음 작업으로 진행 else if (n==-1 && errno != EWOULDBLOCK) // 시스템 콜에서 에러가 발생함 // 에러 처리
4.4.2 폴링형 채팅 서버 (cont.) int is_nonblock(int sockfd) { int val; // 기존의 플래그 값을 얻어온다 val = fcntl(sockfd, F_GETFL, 0); //넌블록 모드인지 확인 if(val & O_NONBLOCK) return 0; return -1; } • 소켓이 넌블록 모드인지를 확인하는 함수 • 소켓이 넌블록 모드인지를 확인한 후 아니면 넌블록 모드로 변경하는 함수 addrlen=sizeof(cliaddr); accp_sock = accept(s, (struct sockaddr *)&cliaddr, &clilen); if(accp_sock == -1 && errno != EWOULDBOLCK) errquit(“accpt fail”); else if(accp_sock >0){ // 채팅 클라이언트 목록에 추가 clisock_list[num_chat] = accp_sock; if(is_nonblock(accp_sock) != 0 && set_nonblock(acp_sock) < 0) errquit(“set_nonblock fail”); num_chat++ send(accp_sock, start, strlen(start), 0); printf(“%d번째 사용자 추가. \n”, num_chat); }
Network Simulation ? Prof. Jae-Hwoon Lee Dept. of Information & Communication Engineering, Dongguk University e-mail: jaehwoon@dongguk.edu http://cnel.dongguk.ac.kr