1. select 함수를 기반으로 서버를 구현할 때 코드상에서 확인할 수 있는 단점 두 가지는 무엇인가?
>select 함수 호출 이후에 항상 등장하는 모든 파일디스크립터를 대상으로 하는 반복문과 select 함수를 호출할 때마다 인자로 매번 전달해야하는 관찰 대상에 대한 정보들이 단점이다.
2. select 방식이나 epoll 방식이나, 관찰의 대상이 되는 파일 디스크립터의 정보를 함수호출을 통해서 운영체제에게 전달해야 한다. 그렇다면 이들 정보를 운영체제에게 전달하는 이유는 어디에 있는가?
>select과 epoll은 파일 디스크립터, 정확히 말하면 소켓변화의 관찰이 요구되는 방식이다. 그런데 소켓은 운영체제에 의해 관리가 된다. 즉, select 방식과 epoll 방식은 절대적으로 운영체제에 의해 완성되는 방식이다. 따라서 관찰대상의 정보를 운영체제에게 전달해야한다.
3. select 방식과 epoll 방식의 가장 큰 차이점은 관찰의 대상이 되는 파일 디스크립터를 운영체제에게 전달하는 방식에 있다. 어떻게 차이가 나는지, 그리고 그러한 차이를 보이는 이유는 무엇인지 설명해보자.
>epoll은 select 방식과 달리 관찰이 되는 파일 디스크립터의 정보를 한번씩만 전달하면 된다. epoll 방식은 select 방식의 단점을 보완하여 리눅스 커널에서 관찰의 대상 정보를 기억하는 방식이기 때문이다.
4. select 방식을 개선시킨 것이 epoll 방식이긴 하지만 select 방식도 나름의 장점이 있다. 어떠한 상황에서 select 방식을 선택하는 것이 보다 현명한 선택이 될 수 있는가?
>서버의 접속자 수가 많지 않으면서(고성능 요구X) 다양한 운영체제에서 운영이 가능한 경우 select 방식이 유리하다.
5. epoll은 레벨 트리거 방식, 또는 엣지 트리거 방식으로 동작한다. 그렇다면 이 둘이 어떻게 차이가 나는지 이벤트의 발생시점을 입력버퍼 기준으로 설명해보자.
>레벨 트리거 방식에서는 입력버퍼에 데이터가 남아있는 동안 계속해서 이벤트가 등록된다. 반면 엣지 트리거 방식에서는 입력버퍼에 데이터가 들어올 때 이벤트가 등록된다.
6. 엣지 트리거 방식을 사용하면 데이터의 수신과 데이터의 처리시점을 분리할 수 있다고 하였다. 그 이유는 무엇이고, 이는 어떠한 장점이 있는가?
>엣지 트리거 방식을 사용하면 입력버퍼에 데이터가 수신될 때 딱 한번만 이벤트가 발생하고 입력버퍼에 데이터가 남아있는 동안에는 이벤트가 발생하지 않기 때문에 데이터가 수신된 다음에 원하느 시점에 가서 데이터를 처리할 수 있다. 그리고 이렇게 데이터의 수신과 처리의 시점을 분리하면 서버 구현에 유연성을 부여하게 된다.
7. 서버에 접속한 모든 클라이언트들 사이에서 메시지를 주고받는 형태의 채팅 서버를 레벨 트리거 방식의 epoll 기반으로, 엣지 트리거 방식의 epoll 기반으로 각각 구현해본다. 물론 서버의 실행을 위해서는 채팅 클라이언트가 필요한데, 이는 Chapter 18에서 소개하는 예제 chat_clnt.c를 그래도 활용한다.
>
[chat_clnt.c]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define BUF_SIZE 100
#define NAME_SIZE 20
void * send_msg(void * arg);
void * recv_msg(void * arg);
void error_handling(char * msg);
char name[NAME_SIZE]="[DEFAULT]";
char msg[BUF_SIZE];
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
pthread_t snd_thread, rcv_thread;
void * thread_return;
if(argc!=4) {
printf("Usage : %s <IP> <port> <name>\n", argv[0]);
exit(1);
}
sprintf(name, "[%s]", argv[3]);
sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
error_handling("connect() error");
pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
pthread_join(snd_thread, &thread_return);
pthread_join(rcv_thread, &thread_return);
close(sock);
return 0;
}
void * send_msg(void * arg) // send thread main
{
int sock=*((int*)arg);
char name_msg[NAME_SIZE+BUF_SIZE];
while(1)
{
fgets(msg, BUF_SIZE, stdin);
if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n"))
{
close(sock);
exit(0);
}
sprintf(name_msg,"%s %s", name, msg);
write(sock, name_msg, strlen(name_msg));
}
return NULL;
}
void * recv_msg(void * arg) // read thread main
{
int sock=*((int*)arg);
char name_msg[NAME_SIZE+BUF_SIZE];
int str_len;
while(1)
{
str_len=read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);
if(str_len==-1)
return (void*)-1;
name_msg[str_len]=0;
fputs(name_msg, stdout);
}
return NULL;
}
void error_handling(char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}
[레벨 트리거]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
#define MAX_CLNT 256
#define EPOLL_SIZE 50
void error_handling(char *buf);
void send_msg(char * msg, int len);
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t adr_sz;
int str_len, i;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
epfd=epoll_create(EPOLL_SIZE);
ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
event.events=EPOLLIN;
event.data.fd=serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
while(1)
{
event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(event_cnt==-1)
{
break;
}
for(i=0; i<event_cnt; i++)
{
if(ep_events[i].data.fd==serv_sock)
{
adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
event.events=EPOLLIN;
event.data.fd=clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
clnt_socks[clnt_cnt++]=clnt_sock;
printf("connected client: %d \n", clnt_sock);
}
else
{
str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
if(str_len==0)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
printf("closed client: %d \n", ep_events[i].data.fd);
for(i=0; i<clnt_cnt; i++)
{
if(clnt_sock==clnt_socks[i])
{
while(i++<clnt_cnt-1)
clnt_socks[i]=clnt_socks[i+1];
break;
}
}
clnt_cnt--;
}
else
{
send_msg(buf, str_len);
}
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void send_msg(char * msg, int len)
{
int i;
for(i=0; i<clnt_cnt; i++)
write(clnt_socks[i], msg, len);
}
void error_handling(char *buf)
{
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}
[원래는 connected clinet문 사이에 많은 'return epoll_wait'문이 발생해야하는데 발생하지 않았다]
[엣지 트리거]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
#define MAX_CLNT 256
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);
void send_msg(char * msg, int len);
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t adr_sz;
int str_len, i;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
epfd=epoll_create(EPOLL_SIZE);
ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
setnonblockingmode(serv_sock);
event.events=EPOLLIN;
event.data.fd=serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
while(1)
{
event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(event_cnt==-1)
{
puts("epoll_wait() error");
break;
}
puts("return epoll_wait");
for(i=0; i<event_cnt; i++)
{
if(ep_events[i].data.fd==serv_sock)
{
adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
setnonblockingmode(clnt_sock);
event.events=EPOLLIN|EPOLLET;
event.data.fd=clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
clnt_socks[clnt_cnt++]=clnt_sock;
printf("connected client: %d \n", clnt_sock);
}
else
{
while(1)
{
str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
if(str_len==0)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
for(i=0; i<clnt_cnt; i++)
{
if(ep_events[i].data.fd==clnt_socks[i])
{
while(i++<clnt_cnt-1)
clnt_socks[i]=clnt_socks[i+1];
break;
}
}
clnt_cnt--;
printf("closed client: %d \n", ep_events[i].data.fd);
break;
}
else if(str_len<0)
{
if(errno==EAGAIN)
break;
}
else
{
send_msg(buf, str_len);
}
}
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void send_msg(char * msg, int len)
{
int i;
for(i=0; i<clnt_cnt; i++)
write(clnt_socks[i], msg, len);
}
void setnonblockingmode(int fd)
{
int flag=fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}
[입력버퍼에 데이터가 들어올 때만 이벤트가 발생한 것을 확인할 수 있다]
[참고] TCP/IP 소켓프로그래밍 윤성우 저
'C > TCPIP 소켓 프로그래밍(윤성우 저)' 카테고리의 다른 글
TCP/IP 소켓 프로그래밍 18장 내용 확인문제 (0) | 2017.07.23 |
---|---|
TCP/IP 소켓 프로그래밍 16장 내용 확인문제 (0) | 2017.07.16 |
TCP/IP 소켓 프로그래밍 15장 내용 확인문제 (0) | 2017.07.12 |
TCP/IP 소켓 프로그래밍 10~14장 함수 복습 (0) | 2017.07.09 |
TCP/IP 소켓 프로그래밍 6~9장 함수 복습 (0) | 2017.07.08 |