4. UNIX 소켓응용프로그래밍 4.1 소켓의동작모드 소켓의동작모드 blocking, non-blocking, 그리고비동기 (asynchronous) 모드 소켓을처음생성하면 blocking 모드의소켓이생성 blocking 모드소켓 어떤소켓관련시스템콜을호출하였을때네트웍시스템 ( 즉, TCP/IP) 이동작을완료할때까지응용프로세스가멈추어있게 (block) 되는소켓 응용프로그램에서는필요에따라이 blocking 모드의소켓을 non-blocking 모드또는비동기모드로변경하여사용 Non-blocking 모드소켓 소켓관련시스템콜에대하여네트웍시스템이일단결과를바로리턴하여응용프로그램이 block 되지않게하는소켓 소켓관련시스템콜중에 block 될수있는것은 listen(), connect(), accept(), send(), recv(), close() 등 비동기모드 non-blocking 모드에서처럼 block 될수있었던소켓시스템콜에대해서일단리턴을하고시스템콜의해당동작이완료되면비동기적으로 (asynchronously) 그결과를응용프로그램에게알려주는소켓의동작모드 채팅서버와클라이언트프로그램 여러가지 I/O 작업을병행하여처리하는것이필요 select() 시스템콜을사용하여소켓을비동기모드로사용 - 83 -
4.2 채팅서버프로그램 4.2.1 채팅서버구조 채팅서버 (chat_server.c) 임의의클라이언트가채팅에참가하는요청을하면이를채팅참 가리스트에추가 채팅에참가하고있는클라이언트들이보내오는메시지를모든 채팅참가자에게다시방송 두가지일을병행하여처리하기위해서새로운클라이언트가접 속될때마다프로세스를만들는 concurrent 서버로구현 처리해야할작업이생길때마다프로세스를만들어나가는방식은프로그램작성은편리하지만시스템자원의활용면이나프로그램의안정적인동작면에서는불리 채팅서버프로그램 chat_server.c 에서는 apparent concurrent 서버구조를사용 concurrent 서버처럼여러클라이언트에게병행하여서비스를제공하면서도프로세스를클라이언트수만큼계속복제하지않는방식 하나의프로세스가여러클라이언트와의통신을담당 Select() 시스템콜의사용 채팅서버프로그램은클라이언트의접속요청을처리하는동시에다른클라이언트들이보내온메시지를모든채팅참가자클라이언트에게전달해야하므로프로세스가어느한곳에멈추어있을수있는 blocking 모드로동작하면안됨 소켓을통한 I/O 를비동기모드로처리하여야하는데이를위하여 select() 시스템콜을사용 apparent concurrent 서버모델을이용한채팅서버의동작 서버는먼저 socket() 을호출하여채팅참가자를접수할소켓을 개설 ( 초기소켓이라부름 ) 하고서자신의소켓주소와 bind() 이초기소켓을대상으로 select() 를호출하여초기소켓에어떤 I/O 변화가생길때까지기다림 - 84 -
초기소켓에서처음발생할수있는 I/O 변화는채팅참가자가연결요청을보내왔을때인데이때서버는 accept() 를호출하여새로운참가자접속을처리하고 accept() 가리턴하는소켓번호를참가자리스트 (client_s[]) 에등록 서버는이새로생긴소켓과초기소켓을대상으로하여다시 select() 를호출 select() 함수의기능은소켓에서발생하는 I/O 변화를기다리다가지정된 I/O 변화가감시대상소켓들중하나에서라도발생하면 select() 문이리턴 응용프로그램에서는 select() 가리턴되었을때어떤소켓에서어떤 I/O 변화가발생하였는지를확인하여필요한작업을처리 - 85 -
채팅프로그램의전체적인구성 s: 초기소켓, client_s[ ]: 채팅참가자의소켓번호배열 서버는초기소켓 s 를통하여새로운채팅참가자를접수 (accept) 하며새로참가한클라이언트들의소켓번호는배열 client_s[ ] 에들어있게됨 4.2.2 select() 시스템콜 select() 시스템콜의사용문법 int select ( int maxfdp1, /* 최대파일 ( 및소켓 ) 번호크기 + 1 */ fd_set *readfds, /* 읽기상태변화를감지할소켓지정 */ fd_set *writefds, /* 쓰기상태변화를감지할소켓지정 */ fd_set *exceptfds, /* 예외상태변화를감지할소켓지정 */ struct timeval *tvptr); /* select() 시스템콜이기다리는시간 */ - 86 -
select() 의첫번째인자 maxfdp1 은 'I/O 변화를감시할총소켓 의개수 +1' 의값을지정 보통현재 open 된소켓번호중가장큰값에 1 을더한값을사용 시스템내에서개설할수있는파일기술자의최대값은 <sys/types.h> 에 FD_SETSIZE 로정의 fd_set(file descriptor set) 타입의인자 readfds, writefds, exceptfds 는각각읽기, 쓰기, 예외상황발생과같은 I/O 변화가 발생하였을때이를감지할대상이되는소켓들을지정하는배열 형구조체 이세가지구조체를통하여어떤소켓에서어떤 I/O 변화발생을감지할지를선택하여지정 마지막인자 tvptr 은 select() 시스템콜이기다리는시간을지정 tvptr 이 NULL 인경우에는지정한 I/O 변화가발생할때까지계속기다리며, 0 인경우에는기다리지않고바로리턴되고, 그외의값인경우에는지정된시간만큼또는도중에 I/O 변화가발생할때까지기다림 fd_set 타입의구조체 (readfds, writefds, exceptfds) 와소켓번호 와의관계 readfds 구조체의소켓번호 0 번과 3 번이 1 로세트되어있으므로키보드나 ( 파일번호 0 번 ), 소켓번호 3 에서어떤데이터가입력되어응용프로그램이이를읽을수있는상태가되면 select() 문은리턴 writefds 에서는소켓번호 1 번과 3 번이 1 로세트되어있으므로파일기술자 1 번 ( 표준출력 ) 이나소켓번호 3 번이 write 를할수있는상태로변하면 ( 예를들면송신버퍼가비워짐 ) select() 문이리턴 각각 fd_set 타입구조체에 I/O 변화를감지할소켓 ( 또는파일 ) 번호 - 87 -
를 1 로세트하여 select() 를호출해두면해당조건이만족되는순간 select() 문이리턴 fd_set 를사용하기위한매크로 UNIX 에서는 fd_set 타입의구조체를편리하게처리할수있도록, 예를들면특정소켓의 I/O 변화감지를쉽게선택하거나취소할수있도록매크로를제공 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 에해당하는비트가세트되어있으면양수값인 fd 를리턴한다. fd_set 구조체생성 select() 를호출하려면먼저 fd_set 구조체를만들어야함 chat_server.c 의경우에는읽기에대한 I/O 변화만확인하면되므로 fd_set 타입의구조체 read_fds 하나만선언 FD_ZERO(&read_fds) 를호출하여 read_fds 의모든소켓번호위치를초기화시킴 I/O 변화에관심을갖는소켓번호 ( 또는파일기술자 ) 들을선택 chat_server.c 에서는클라이언트와의접속요구를처리할초기소켓 s 와각채팅클라이언트마다배정된소켓번호들의배열 client_s[ ] 두가지의소켓이용 FD_SET 를사용하여 I/O 변화를감지할소켓들을선택 fd_set read_fds; FD_ZERO(&read_fds); /* *read_fds 의모든소켓을 0 으로초기화 */ FD_SET(s, &read_fds); /* 초기소켓선택 */ for(i = 0; i < num_chat; i++) /* 모든클라이언트접속소켓선택 */ FD_SET(client_s[i], &read_fds); - 88 -
select() 호출 초기화한 read_fds 를 select() 의두번째인자 ( 즉, 읽기변화감지 용 fd_set) 로지정하고, 쓰기및예외발생에해당하는 fd_set 는 NULL 로지정하여다음과같이함 select(maxnfdsp1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); 첫번째인자 maxnfdspl 은 'I/O 변화를감시할총소켓의개수 +1' 의값으로서현재개설된 ' 최대소켓번호 +1' 을사용 select() 는두가지종류의입력에대하여리턴되므로 select() 가리턴되면어떤입력이발생하였는지를판단하여야하며이를위하여 FD_ISSET 매크로를사용 FD_ISSET 의사용법 매크로 FD_ISSET 는 read_fds 구조체에서소켓번호 s 에서 I/O 변화가있었으면양수값인소켓번호 s 를리턴 FD_ISSET(s, &read_fds); 아래에서 FD_ISSET 실행결과가양수이면클라이언트로부터연결요청 ( 즉, 채팅참가요청 ) 이온것이므로 accept() 를불러채팅참가요청을처리 client_s[ ] 중하나의소켓이세트되었다면어떤채팅참가자가채팅메시지를전송한것이므로이메시지를받아모든참가자에게방송 if (FD_ISSET(s, &read_fds)) { /* 초기소켓 s 에서입력발생 */ clilen =sizeof(client_addr); client_fd = accept(s, (struct sockaddr *)&client_addr, &clilen); /* 클라이언트소켓번호배열 client[] 를차례로검색 */ for(i = 0; i < num_chat; i++) { if (FD_ISSET(client_s[i], &read_fds)) { /* 소켓 client_s[i] 에서채팅메시지수신 */ /* 모든참가자에게채팅메시지방송 */ - 89 -
채팅종료 클라이언트가채팅에서탈퇴하려면종료문자 ( 예 : exit) 를전송 서버는종료문자를수신한경우이를확인하고 (exitcheck() 함수사용 ) client_s[ ] 배열에서해당클라이언트의소켓번호를삭제한후채팅참가자수 num_chat 을 1 감소시킴 채팅서버수행결과 # chat_server 4001 대화방서버실행.. 4.2.3 프로그램주요부분설명 chat_server.c 에서소켓개설 인터넷프로토콜체계 (PF_INET) 를사용하였으며스트림형태의 (TCP) 프로토콜을선택 소켓을만들고소켓에주소를부여하는 (bind) 과정 s = socket(pf_inet, SOCK_STREAM, 0); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = PF_INET; server_addr.sin_addr.s_addr = htonl(inaddr_any); server_addr.sin_port = htons(atoi(argv[1])); 소켓주소구조체의내용을널 (NULL) 로초기화하기위하여함수 bzero() 를사용 IP 주소로는현재채팅서버프로그램이실행되는호스트의 IP 주소를 (INADDR_ANY), 그리고서비스를제공할포트번호는서버프로그램실행시사용자가입력한값 ( 예를들면 4001) 을사용 메시지방송기능 어떤클라이언트가보낸메시지를다른모든클라이언트에게전 송하는동작 배열 client_s[] 에는각채팅참가자들과연결되는소켓번호들 - 90 -
첫번째 for() 루프는현재채팅에참가하는모든클라이언트를대상으로보내온채팅메시지가있는지검색 두번째 for() 루프에서는채팅메시지를모든클라이언트에게방송 /* 채팅메시지가도착했는지검사 */ for(i = 0; i < num_chat; i++) { if(fd_isset(client_s[i], &read_fds)) { if((n = recv(client_s[i], rline, MAXLINE,0)) > 0) { rline[n] = '\0'; /* 종료문자입력시채팅탈퇴처리 */ if(exitcheck(rline, escapechar, 5) == 1) { shutdown(client_s[i], 2); /* client_s[] 크기조절 */ if (i!= num_chat - 1) client_s[i] = client_s[num_chat - 1]; num_chat--; continue; /* 모든채팅참가자에게메시지방송 */ for(j = 0; j < num_chat; j++) send(client_s[j], rline, n, 0); printf("%s", rline); 토크프로그램 (3 장에서소개 ) 과의차이점 채팅프로그램에서는각채팅참가자 ( 클라이언트 ) 들이키보드에 서입력한문자열만서버로전송하는것이아니라자신 ( 채팅참 가자 ) 의이름을다음과같이메시지앞에붙여서전송 [ 철수 ] hello everybody? 종료문자열확인 클라이언트가채팅을탈퇴하기위하여 exit 와같은종료문자를입력하면서버는이것을찾아내기위하여클라이언트가전송한메시지전체를검색하여종료문자열이들어있는지를확인 chat_server.c 에서는이것을처리하기위하여사용자정의함수 - 91 -
exitcheck() 를사용 4.2.4 chat_server.c 프로그램리스트 /*---------------------------------------------------------------------------------------------- 파일명 : chat_server.c 기능 : 채팅참가자관리, 채팅메시지수신및방송컴파일 : cc -o chat_server chat_server.c readline.c -lsocket -lnsl 실행예 : chat_server 4001 -----------------------------------------------------------------------------------------------*/ #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <signal.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #define MAXLINE 1024 #define MAX_SOCK 512 char *escapechar = "exit\n"; int readline(int, char *, int); int main(int argc, char *argv[]) { char rline[maxline], my_msg[maxline]; char *start = " 대화방에오신걸환영합니다...\n"; int i, j, n; int s, client_fd, clilen; int nfds; /* 최대소켓번호 +1 */ fd_set read_fds; /* 읽기를감지할소켓번호구조체 */ int num_chat = 0; /* 채팅참가자수 */ /* 채팅에참가하는클라이언트들의소켓번호리스트 */ int client_s[max_sock]; struct sockaddr_in client_addr, server_addr; if (argc < 2) { printf(" 실행방법 :%s 포트번호 \n",argv[0]); printf(" 대화방서버초기화중...\n"); /* 초기소켓생성 */ if ((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { - 92 -
printf("server: Can't open stream socket."); /* server_addr 구조체의내용세팅 */ bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(inaddr_any); server_addr.sin_port = htons(atoi(argv[1])); if (bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0) { printf("server: Can't bind local address.\n"); /* 클라이언트로부터연결요청을기다림 */ listen(s, 5); nfds = s + 1; /* 최대소켓번호 +1 */ FD_ZERO(&read_fds); while(1) { /* ( 최대소켓번호 +1) 값을갱신 */ if ((num_chat-1) >= 0) nfds = client_s[num_chat-1] + 1; /* 읽기변화를감지할소켓번호를 fd_set 구조체에지정 */ FD_SET(s, &read_fds); for (i=0; i<num_chat; i++) FD_SET(client_s[i], &read_fds); /*---------------------------------- select() 호출 ------------------------------------- */ if (select(nfds, &read_fds, (fd_set *)0, (fd_set *)0,(struct timeval *)0) < 0) { printf("select error\n"); /*-------------------------- 클라이언트연결요청처리 --------------------------- */ if (FD_ISSET(s, &read_fds)) { clilen = sizeof(client_addr); client_fd = accept(s, (struct sockaddr *)&client_addr, &clilen); if (client_fd!= -1) { /* 채팅클라이언트목록에추가 */ client_s[num_chat] = client_fd; num_chat++; send(client_fd, start, strlen(start), 0); printf("%d 번째사용자추가.\n",num_chat); /*- 임의의클라이언트가보낸메시지를모든클라이언트에게방송 - */ - 93 -
for (i = 0; i < num_chat; i++) { if (FD_ISSET(client_s[i], &read_fds)) { if ((n = recv(client_s[i], rline, MAXLINE,0)) > 0) { rline[n] = '\0'; /* 종료문자입력시채팅탈퇴처리 */ if (exitcheck(rline, escapechar, 5) == 1) { shutdown(client_s[i], 2); if(i!= num_chat-1) client_s[i] = client_s[num_chat-1]; num_chat--; continue; /* 모든채팅참가자에게메시지방송 */ for (j = 0; j < num_chat; j++) send(client_s[j], rline, n, 0); printf("%s", rline); /* ------------------------------- 종료문자확인함수 ---------------------------- exitcheck() 는다음의세개의인자를필요로한다 rline: 클라이언트가전송한문자열포인터 escapechar: 종료문자포인터 len: 종료문자의크기 ---------------------------------------------------------------------------------------------*/ int exitcheck(rline, escapechar, len) char *rline; /* 클라이언트가전송한메시지 */ char *escapechar; /* 종료문자 */ int len; { int i, max; char *tmp; max = strlen(rline); tmp = rline; for(i = 0; i<max; i++) { if (*tmp == escapechar[0]) { if(strncmp(tmp, escapechar, len) == 0) return 1; else tmp++; - 94 -
4.3 채팅클라이언트프로그램 4.3.1 프로그램개요 채팅클라이언트프로그램 (chat_client.c) 사용자의입력메시지를서버로전송하고, 서버가보내온모든 메시지를사용자화면에출력 토크클라이언트프로그램 talk_client.c 와유사한동작 talk_client.c 에서는사용자의키보드입력처리와수신메시지출력두가지일을동시에수행하기위해서 fork() 를이용하여두개의프로세스를만들어각프로세스가이두가지일을담당 chat_client.c 에서는 chat_server.c 에서와같이 select() 시스템콜을이용하여소켓을비동기모드로바꾸어두가지입출력을하나의프로세스에서처리하도록구현 chat_client.c 가수행되기 위하여는 채팅 chat_server.c 가서버에서먼저실행되어야함. 서버 프로그램 chat_server.c 가사용하는포트번호와서버의 IP 주소를클라이언트에서알고있어야함. 클라이언트에서는채팅참가자의이름을모든메시지앞에붙여 서서버로전송하여야하기때문에자신의이름을프로그램실행 시입력하여야함 # chat_client server_ip server_port my_name server_ip 는채팅서버의 IP 주소 (dotted decimal) server_port 는채팅서버프로그램 (chat_server.c) 실행시지정한포트번호 my_name 은채팅에서사용할자신의이름 chat_client.c 의실행예 # chat_client 203.252.65.3 4001 철수 - 95 -
접속에성공했습니다.. 대화방에오신걸환영합니다... 4.3.2 프로그램주요부분설명 구조체 Name chat_client.c 에서는먼저채팅메시지앞에항상붙여서전송할자신의이름 (argv[3]) 을구조체 Name 에기록 struct Name { char n[20]; /* 채팅에서사용할이름 */ int len; /* 이름의실제크기 */ name; /* 참가자이름을 Name 구조체에기록 */ sprintf(name.n, "[%s]", argv[3]); name.len = strlen(name.n); fd_set 구조체 chat_client.c 에서 select() 를호출하려면먼저 fd_set 구조체를 생성 chat_client.c 에서는읽기에대한 I/O 만확인하면되므로 fd_set 구조체로 read_fds 하나만선언 FD_ZERO(&read_fds) 를호출하여 read_fds 의모든소켓번호를 disable 시킴 I/O 변화에관심을갖는소켓번호 ( 파일기술자 ) 들을세트 chat_client.c 에서는키보드입력을위한파일기술자 0 과서버와접속되어있는소켓 s 두개의파일기술자가있음 FD_SET 이용방법 fd_set read_fds; /* 읽기변화를감지하기위한 fd_set 구조체선언 */ FD_ZERO(&read_fds) /* 초기화 */ FD_SET(0, &read_fds); /* 키보드입력용파일기술자 (0) 세트 */ - 96 -
FD_SET(s, &read_fds); /* 서버와연결된소켓번호 (s) 세트 */ select() 호출 read_fds 를 select() 의두번째인자 ( 즉, 읽기변화감지용 fd_set) 로지정 세번째와네번째인자즉, 쓰기및예외발생에해당하는 fd_set 는 NULL 로지정 select() 문의첫번째인자로사용할 ' 최대소켓번호 +1' 의값으 로는 s+1 을사용 select(nfds, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); select() 가리턴되었을때, read_fds 내의소켓번호 s 값이세트되어있다면서버에서메시지를보내온것이므로이메시지를읽어화면에출력 파일기술자 0 이세트된경우는자신의키보드입력이발생한것이므로이메시지를서버에게전송 if (FD_ISSET(s, &read_fds)) /* 서버가보내오는메시지를수신하여출력 */ if (FD_ISSET(0, &read_fds)) /* 키보드입력데이터를서버로전송 */ - 97 -
4.3.3 chat_client.c 프로그램리스트 /*----------------------------------------------------------------------------------------- 파일명 : chat_client.c 기능 : 서버에접속한후키보드의입력을서버에전달하고, 서버로부터오는메시지를화면에출력한다. 컴파일 : cc -o chat_client chat_client.c readline.c -lsocket -lnsl 실행예 : chat_client 203.252.65.3 4001 사용자 _ID -------------------------------------------------------------------------------------------*/ #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #define MAXLINE 1024 #define MAX_SOCK 512 char *escapechar = "exit\n"; int readline(int, char *, int); int s; /* 서버와연결된소켓번호 */ struct Name { char n[20]; /* 대화방에서사용할이름 */ int len; /* 이름의크기 */ name; int main(int argc, char *argv[]) { char line[maxline], sendline[maxline+1]; int n, pid, size; struct sockaddr_in server_addr; int nfds; fd_set read_fds; if( argc < 4 ) { printf(" 실행방법 : %s 호스트 IP 주소포트번호사용자이름 \n", argv[0]); /* 채팅참가자이름구조체초기화 */ sprintf(name.n, "[%s]", argv[3]); name.len = strlen(name.n); /* 소켓생성 */ if ((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { printf("client : Can't open stream socket.\n"); /* 채팅서버의소켓주소구조체 server_addr 초기화 */ - 98 -
bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(atoi(argv[2])); /* 연결요청 */ if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { printf("client : Can't connect to server.\n"); else { printf(" 접속에성공했습니다..\n"); nfds = s + 1; FD_ZERO(&read_fds); while(1) { /* -------------------------------------- selelct() 호출 ---------------------------------------*/ FD_SET(0, &read_fds); FD_SET(s, &read_fds); if(select(nfds, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0) { printf("select error\n"); /*------------------------- 서버로부터수신한메시지처리 -------------------------*/ if (FD_ISSET(s, &read_fds)) { char recvline[maxline]; int size; if ((size = recv(s, recvline, MAXLINE, 0)) > 0) { recvline[size] = '\0'; printf("%s \n", recvline); /* --------------------------------- 키보드입력처리 ----------------------------------*/ if (FD_ISSET(0, &read_fds)) { if (readline(0, sendline, MAXLINE) > 0) { size = strlen(sendline); sprintf(line, "%s %s", name.n, sendline); if (send(s, line, size + name.len, 0)!= (size+name.len)) printf("error : Written error on socket.\n"); if (size == 5 && strncmp(sendline, escapechar, 5) == 0) { printf("good bye.\n"); close(s); /* end of 키보드입력처리 */ /* end of while() */ - 99 -
4.4 멀티서버 멀티서버 하나의프로세스에서두가지이상의서비스를동시에제공하거 나, 동일한서비스를두가지이상의프로토콜 ( 즉, TCP 와 UDP) 로 제공하는서버를말함 멀티서버의장점 하나의프로세스가하나의서비스를제공하는것에비해시스템의자원을적게사용하면서동일한서비스를제공 4.4.1 프로그램개요 멀티서버프로그램 multi_server.c 두가지서비스 echo 와 daytime 를제공하기위하여두개의 소켓을사용 echo 서비스는소켓번호 echo_fd 를, daytime 서비스는소켓번호 daytime_fd 를아래와같이만들어사용 /* echo 서비스를위한소켓생성 */ echo_fd = socket(pf_inet, SOCK_STREAM, 0); /* daytime 서비스를위한소켓생성 */ daytime_fd = socket(pf_inet, SOCK_STREAM, 0); 소켓을생성한다음무한루프를돌며클라이언트의서비스요구 를기다림 두가지서비스에대한요청을구분하기위해 select() 시스템콜 을사용 FD_SET 매크로를사용하여소켓 echo_fd 와 daytime_fd 를 fd_set 타입의구조체 read_fds 에지정하며향후에이소켓에서읽기변화가발생하면 select() 문이리턴되도록함 - 100 -
while(1) { FD_SET(echo_fd, &read_fds); FD_SET(daytime_fd, &read_fds); select(nfds, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); select() 문이리턴되었을때어떤서비스요구가발생하였는지를구분하기위하여 FD_ISSET 매크로를사용 FD_ISSET 는 read_fds 구조체에서해당소켓이세트되어있으면양수값인소켓번호를리턴 /* echo 클라이언트가접속을요구해온경우 */ if(fd_isset(echo_fd, &read_fds)) echo_process(s); /* echo 서비스수행 */ /* daytime 클라이언트가접속을요구해온경우 */ if(fd_isset(daytime_fd, &read_fds)) daytime_process(s); /* daytime 서비스수행 */ echo_process() 구현부분 echo_process() 는 echo 를처리하도록멀티서버에서구현한사용자정의함수로서소켓번호 echo_fd 를함수인자로받으며소켓을통해입력된문자열을그소켓으로그대로출력 int echo_process(int echo_fd) { while((len = read(echo_fd, buf, sizeof(buf)))!= 0) write(echo_fd, buf, len); return 0; daytime_process() 구현부분 소켓번호 daytime_fd 를인자로받으며, time() 시스템콜을이용하여시스템의현재시각을얻음 ctime() 시스템콜을이용하여아래와같은형태의시각을나타내 - 101 -
는문자열로변경한후이문자열을클라이언트로전송 Wed May 21 12:45:35 1997 Int daytime_process(int daytime_fd) { time(&now); sprintf(buf, "%s\n", ctime(&now)); write(daytime_fd, buf, strlen(buf)); return 0; 4.4.2 multi_server.c 프로그램리스트 /* ----------------------------------------------------------------------------------------------------------- 파일명 : multi_server.c 기능 : echo 와 daytime 을같이제공하는멀티서버컴파일 : cc -o multi_server multi_server.c -lsocket -lnsl 실행예 : multi_server 3001 --------------------------------------------------------------------------------------------------------------*/ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/wait.h> #include <stdio.h> #include <time.h> #include <string.h> extern int errno; #define MAXLINE 1024 int main(int argc, char *argv[]) { int echo_fd, daytime_fd, s; /* 소켓번호 */ int echo_port, daytime_port; /* 포트번호 */ int nfds, len; fd_set read_fds; struct sockaddr_in server_addr, client_addr; /* 소켓주소구조체 */ if (argc!= 2) { printf(" 실행방법 : %s 포트번호 \n", argv[0]); echo_port = atoi(argv[1]); /* daytime 서비스포트번호를임의로배정 */ daytime_port = echo_port + 1; - 102 -
/* echo 서비스를위한소켓생성 */ echo_fd = socket(pf_inet, SOCK_STREAM, 0); bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(inaddr_any); server_addr.sin_port = htons(echo_port); bind(echo_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); /* daytime 서비스를위한소켓생성 */ daytime_fd = socket(pf_inet, SOCK_STREAM, 0); server_addr.sin_port = htons(daytime_port); bind(daytime_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)); listen(echo_fd, 5); listen(daytime_fd, 5); /*----------------------------------------- select() 호출 ------------------------------------------*/ nfds = daytime_fd + 1; FD_ZERO(&read_fds); while(1) { FD_SET(echo_fd, &read_fds); FD_SET(daytime_fd, &read_fds); if(select(nfds, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0) { printf("select error: %s\n", strerror(errno)); /*------------------------------------------------------------------------------------------------------ echo 클라이언트가접속을요구해온경우 ------------------------------------------------------------------------------------------------------*/ if (FD_ISSET(echo_fd, &read_fds)) { len = sizeof(client_addr); bzero((char *)&client_addr, len); s = accept(echo_fd, (struct sockaddr *)&client_addr, &len); echo_process(s); close(s); /*------------------------------------------------------------------------------------------------------ daytime 클라이언트가접속을요구해온경우 ------------------------------------------------------------------------------------------------------*/ if (FD_ISSET(daytime_fd, &read_fds)) { len = sizeof(client_addr); s = accept(daytime_fd, (struct sockaddr *)&client_addr, &len); daytime_process(s); close(s); - 103 -
/*-------------------------------------------------------------------------------------------------------------- echo 서비스처리함수정의 -----------------------------------------------------------------------------------------------------------------*/ int echo_process(int echo_fd) { int len; char buf[maxline]; while((len = read(echo_fd, buf, sizeof(buf)))!= 0) { if (len < 0) { printf("echo: read error - %s\n", strerror(errno)); if (write(echo_fd, buf, len) < 0) { printf("echo: write error - %s\n", strerror(errno)); return 0; /*---------------------------------------------------------------------------------------------------------------- daytime 서비스처리함수 ----------------------------------------------------------------------------------------------------------------*/ int daytime_process(int daytime_fd) { time_t now; char buf[maxline]; time(&now); sprintf(buf, "%s\n", ctime(&now)); if (write(daytime_fd, buf, strlen(buf)) < 0) { printf("daytime: write error - %s\n", strerror(errno)); return 0; - 104 -