Develop
2013.04.23 13:06
[c] 다중연결 서버 만들기 #3 - poll() 사용
조회 수 6371 댓글 0
첨부 '1' |
---|
poll은 select 와 마찬가지로 다중입출력 을 구현하기 위한 방법으로 사용되며, 동시에 여러개의 클라이언트를 다루는 서버를 제작하기 위한방법으로 흔히 사용된다. select 의 경우 입출력 이벤트가 발생했을 때 넘겨주는 정보가 너무 적음으로써, 프로그래밍시 여기에 신경을 써줘야 하는데 poll 을 이용하면 이러한 제한을 극복할수 있다. select 에 대한 자세한 내용은 select 를 통한 입출력 다중화 와 다중연결서버 만들기 #2 를 참조하기 바란다. poll 다음은 poll의 함수원형이다.
우선 poll 함수의 첫번째 인자인 pollfd 구조체에 대해서 알아보도록 하겠다. poolfd 구조체만 알만 poll 의 대부분을 다 이해한것이나 마찬가지이니, 주의 깊게 읽어 보기 바란다.
revents 는 events 가 발생했을때 커널에서 이 events 에 어떻게 반응 했는지에 대한 반응 값이다. 후에 revent 값을 조사함으로써, 해당 파일지시자에 어떠한 event 가 최해지고 커널에서 그 event를 어떻게 처리했는지 (입력/출력이 제대로 이루어졌는지, 아니면 에러가 발생했는지)를 알아내서 적절한 조취(읽을 데이타가 있으면 읽거나 하는등의 일)를 취할수 있게 된다. 그럼 events 에 세팅할수 있는 events 에 대해서 알아보도록 하겠다. 이 값들은 | ||
#define POLLIN 0x0001 // 읽을 데이타가 있다. #define POLLPRI 0x0002 // 긴급한 읽을 데이타가 있다. #define POLLOUT 0x0004 // 쓰기가 봉쇄(block)가 아니다. #define POLLERR 0x0008 // 에러발생 #define POLLHUP 0x0010 // 연결이 끊겼음 #define POLLNVAL 0x0020 // 파일지시자가 열리지 않은것같은 // Invalid request (잘못된 요청) |
마지막 아규먼트인 timeout 는 select 의 time 와 같은 역할을 한다.
- 값을 지정하지 않을경우 이벤트가 발생하기 전까지 영원히 기다린다.
- 0일경우는 기다리지 않고 곧바로 다음 루틴을 진행하고자
- 0보다 큰 양의 정수일 경우에는 해당 시간만큼을 기다리게 된다. 해당 시간내에 어떤 이벤트가 발생하면 즉시 되돌려 주며, 시간을 초과하게 될경우 0을 return 한다.
위의 3가지 아규먼트를 채워넣음으로써 poll을 사용할수 있다. poll 함수의 return 값은 int 형인데, 에러일경우 -1 이 리턴되고, 그렇지 않을경우 revent 가 발생한 pollfd 구조체의 숫자를 돌려주게 된다.
이제 poll 버젼의 우편주소 프로그램의 서버를 작성해 보도록 하자.
예제 : zipcode_poll.c
#include <sys/time.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/poll.h> // 받아들일수 있는 클라이언트의 크기 #define OPEN_MAX 600 int main(int argc, char **argv) { int server_sockfd, client_sockfd, sockfd; int i, maxi; int nread; int state = 0; socklen_t clilen; struct sockaddr_in clientaddr, serveraddr; char buf[255]; char line[255]; FILE *fp; struct pollfd client[OPEN_MAX]; if (argc != 2) { printf("Usage : ./zipcode_poll [port]n"); printf("예 : ./zipcode_poll 4444n"); exit(0); } if ((fp = fopen("zipcode.txt", "r")) == NULL) { perror("file open error : "); exit(0); } if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error : "); exit(0); } bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(atoi(argv[1])); state = bind(server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if (state == -1) { perror("bind error : "); exit(0); } state = listen(server_sockfd, 5); if (state == -1) { perror("listen error : "); exit(0); } // pollfd 구조체에 // 소켓지시자를 할당한다. // 소켓에 쓰기 events (POLLIN)에 대해서 // 반응하도록 세팅한다. client[0].fd = server_sockfd; client[0].events = POLLIN; // pollfd 구조체의 모든 fd 를 -1 로 초기화 한다. // fd 가 -1 이면 파일지시자가 세팅되어있지 않다는 뜻이다. for (i = 1; i < OPEN_MAX; i++) { client[i].fd = -1; } maxi = 0; // POLLING 시작 for (;;) { nread = poll(client, maxi + i, 1000); // 만약 POLLIN 이벤트에 대해서 // 되돌려준 이벤트가(revents) POLLIN // 이라면 accept 한다. if (client[0].revents & POLLIN) { clilen=sizeof(clientaddr); client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr, &clilen); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = client_sockfd; break; } } if (i == OPEN_MAX) { perror("too many clients : "); exit(0); } client[i].events = POLLIN; if (i > maxi) { maxi = i; } if (--nread <= 0) continue; } // 현재 파일지시자의 총갯수 만큼 루프를 돌면서 // 각 파일지시자에 POLLIN revent 가 발생했는지를 조사하고 // POLLIN이 발생했다면, 해당 파일지시자에서 데이타를 읽어들이고, // 주소정보를 돌려준다. // 만약 "quit" 를 읽었다면, 소켓연결을 끊는다. for (i = 1; i <= maxi; i++) { if ((sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLIN | POLLERR)) { rewind(fp); memset(buf, 0x00, 255); if (read(sockfd, buf, 255) <= 0) { close(client[i].fd); client[i].fd = -1; } else { if (strncmp(buf, "quit", 4) == 0) { write(sockfd, "byebyen", 7); close(client[i].fd); client[i].fd = -1; break; } while(fgets(line, 255, fp) != NULL) { if (strstr(line, buf) != NULL) write(sockfd, line, 255); memset(line, 0x00, 255); } } } } } } |
select 버젼인 다중연결서버 만들기 #2와 비교해서 보기 바란다.
코딩 분위기가 select 와 매우 비슷하다는걸 알 수 있을것이다.
pollfd 에 입력된 파일지시자의 event 에 입력event 가 발생하면, 커널은 입력event 에 대한 결과를 되돌려줄것이다.
이결과는 입력 event 가 제대로 처리되었다면 POLLIN 을 되돌려 줄 것이고, 어딘가에서 에러가 발생했다면 POLLERR 을 되돌려주게 될 것이다.
그러므로 우리는 revent 를 검사함으로써, 해당 파일지시자에 읽을 데이타가 있다는걸 알게 되고, 데이타를 읽어서 적당한 행동(여기에서는 주소를 돌려주는)을 할수 있다.
위의 프로그램은 이러한 일련의 과정을 보여준다.
select 버젼과 별차이가 없으므로 select 버젼의 소스를 이해했다면 위의 소스를 이해하는데 별 어려움이 없을것이다.
poll 은 보통 select 에 비해서 해당파일지시자에 대해서 보다 많은 정보를 되돌려줌으로, 보통 select 보다 선호되는 추세이다.
select 버젼과 마찬가지로 polling 중간에 파일 I/O 가 들어갈경우, 파일 I/O 작업에서의 block 때문에 짧은시간에 다수의 메시지를 처리할경우 문제가 될 소지가 있다.
그러므로 되도록이면 polling 중간에 파일 I/O 가 일어나지 않도록 해주어야 한다.
위의 소스의 경우도 주소정보를 미리 메모리 상에 올려놓고 쓰는게 더욱 좋은 방법이 될것이다.
-
[js] 이미지 사이즈를 동적으로 조절..
-
[c] 간단한 순위 루틴.. (질문에 대한 답변)
-
[c] 격자 직사각형 넓이 구하기
-
[c] 파일(File) 관련 함수 샘플 코드
-
[c] 더블(이중) 연결리스트 예제..
-
[js] 부모창에서 자식창으로 문자열 전달..
-
[jsp] 유효성체크(Client, Server 에서)
-
[php] gd 프로그램.. htm 파일.. ㅋㅋ
-
[c++] MFC 모든 헤더와 라이브러리 설명 | [교류]Programming
-
[ios][swift] 초간단 카달로그 앱 (Catalog App)
-
[c] 암호화 알고리즘 DES 구현 ㅋㅋ
-
[c] 지폰(gphone) 소스.. 수정(암호화)