RSS구독하기:SUBSCRIBE TO RSS FEED
즐겨찾기추가:ADD FAVORITE
글쓰기:POST
관리자:ADMINISTRATOR
 세마포어(Semaphores)를 비록 IPC설비중의 하나로 분류하긴 했지만, 다른 파이프, 메시지큐, FIFO 등과는 좀다르다. 다른 IPC 설비들이 대부분 프로세스간 메시지 전송을 그 목적으로 하는데 반해서 세마포어는 프로세스간 데이타를 동기화 하고 보호하는데 그목적이 있다.

프로세스간 메시지 전송을 하거나, 혹은 공유메모리를 통해서 특정 데이타를 공유하게 될경우 발생하는 문제가, 공유된 자원에 여러개의 프로세스가 동시에 접근을 하면 안되며, 단지 한번에 하나의 프로세스만 접근 가능하도록 만들어줘야 할것이다. 이것은 쓰레드에서 메시지간 동기화를 위해서 mutex 를 사용하는것과 같은 이유이다.

하나의 데이타에 여러개의 프로세스가 관여할때 어떤 문제점이 발생할수 있는지 간단한 예를 들어보도록 하겠다.
int count=100; 
A 프로세스가 count 를 읽어들인다.     100
B 프로세스가 count 를 읽어들인다.     100
B 프로세스가 count 를 1 증가 시킨다.  101 
A 프로세스가 count 를 1 증가 시킨다.  101
count 는 공유자원(공유메모리 같은)이며 A와 B 프로그램이 여기에 대한 작업을 한다. A가 1을 증가 시키고 B가 1을 증가시키므로 최종 count 값은 102 가 되어야 할것이다. 그러나 A 가 작업을 마치기 전에 B가 작업을 하게 됨으로 엉뚱한 결과를 보여주게 되었다. 위의 문제를 해결하기 위해서는 count 에 A가 접근할때 B프로세스가 접근하지못하도록 block 시키고, A가 모든 작업을 마쳤을때 B프로세스가 작업을 할수 있도록 block 를 해제 시키면 될것이다.
우리는 세마포어를 이용해서 이러한 작업을 할수 있다. 한마디로 줄여서 세마포어는 "여러개의 프로세스에 의해서 공유된는 자원의 접근제어를 위한 도구" 이다.

세마포어의 작동원리

작동원리는 매우 간단하다. 차단을 원하는 자원에대해서 세마포어를 생성하면 해당자원을 가리키는 세마포어 값이 할당된다. 이 세마포어 값에는 현재 세마포어를 적용하고 있는 자원에 접근할수 있는 프로세스의 숫자를 나타낸다. 이 값이 0이면 이 자원에 접근할수 있는 프로세스의 숫자가 0이라는 뜻이며, 자원), 0보다 큰 정수면 해당 정수의 크기만큼의 프로세스가 자원에 접근할수 있다라는 뜻이 된다. 그러므로 우리는 접근제어를 해야하는 자원에 접근하기 전에 세마포어 값을 검사해서 값이 0이면 자원을 사용할수 있을때까지 기다리고, 0보다 더크면(1이라고 가정하자) 자원에 접근해서 세마포어 값을 1 감소 시켜서, 세마포어 값을 0으로 만들어서, 다른 프로세스가 자원에 접근할수 없도록 하고, 자원의 사용이 끝나면 세마포어 값을 다시 1증가시켜서 다른 프로세스가 자원을 사용할수 있도록 만들어주면 된다.

만약 세마포어 값을 검사했는데 세마포어 값이 0이라면 사용할수 있게 될때까지 (1이 될때까지) 기다리면 (block) 될것이다.

세마포어의 사용

세마포어의 사용은 위의 작동원리를 그대로 적용한다. 즉 1. 세마포어로 제어할 자원을 설정한다.
2. 해당 자원을 사용하기전에 세마포어 값을 확인한다.
3. 세마포어 값이 0보다 크면 자원을 사용하고, 세마포어 값을 1 감소 시킨다.
4. 세마포어 값이 0이면 값이 0보다 커질때까지 block 되며, 0보다 커지게 되면 2번 부터 시작하게 된다.

위의 작업을 위해서 Unix 는 다음과 같은 관련함수들을 제공한다.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop (int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, union semun arg);

세마포어의 관리

세마포어는 그 특성상 원자화된 연산을 필요로 한다. 이러한 원자화된 연산은 유저레벨의 함수에서는 제공하기가 힘들므로, 세마포어 정보는 커널에서 전용 구조체를 이용해서 관리하게 된다. 다음은 커널에서 세모포어 정보를 유지하기 위해서 관리하는 구조체인 semid_ds 구조체의 모습이다. semid_ds 는 /usr/include/bits/sem.h 에 선언되어 있다. (이것은 리눅스에서의 경우로 Unix 버젼에 따라서 위치와 멤버변수에 약간씩 차이가 있을수 있다)
struct semid_ds
{
    struct ipc_perm sem_perm;     
    __time_t sem_otime;           
    unsigned long int __unused1;
    __time_t sem_ctime;           
    unsigned long int __unused2;
    unsigned long int sem_nsems;  
    unsigned long int __unused3;
    unsigned long int __unused4;
};
sem_perm 은 세마포어에 대한 퍼미션으로 일반 파일퍼미션과 마찬가지의 기능을 제공한다. 즉 현재 세마포어 구조체에 접근할수 있는 사용자권한을 설정한다. sem_nsems 는 생성할수 있는 세마포어의 크기이다. sem_otime 은 마지막으로 세마포어관련 작업을 한 시간(semop 함수를 이용)이며, sem_ctim 은 마지막으로 구조체 정보가 바뀐 시간이다.

semget 을 이용해서 세마포어를 만들자.

세마포어의 생성혹은 기존에 만들어져 있는 세마포어에 접근하기 위해서 유닉스에서 는 semget(2)를 제공한다. 첫번째 아규먼트는 세마포어의 유일한 키값을 위해서 사용하는 int 형의 키값이다. 우리는 이 key 값을 이용해서 유일한 세마포어를 생성하거나 접근할수 있게 된다. 새로 생성되거나 기존의 세마포어에 접근하거나 하는것은 semflg 를 통해서 제어할수 있다. 다음은 semflg 의 아규먼트이다.

IPC_CREAT
만약 커널에 해당 key 값으로 존재하는 세마포어가 없다면, 새로 생성 한다.
IPC_EXCL
IPC_CREAT와 함께 사용하며, 해당 key 값으로 세마포어가 이미 존재한다면 실패값을 리턴한다.
semflg 를 통해서 세마포어에 대한 퍼미션을 지정할수도 있다. 퍼미션 지정은 보통의 파일에 대해서 유저/그룹/other 에 대해서 지정하는것과 같다.

만약 IPC_CREAT 만 사용할경우 해당 key 값으로 존재하는 세마포어가 없다면, 새로 생성하고, 이미 존재한다면 존재하는 세마포어의 id 를 넘겨준다. IPC_EXCL을 사용하면 key 값으로 존재하는 세마포어가 없을경우 새로 생성되고, 이미 존재한다면 존재하는 id 값을 돌려주지 않고 실패값(-1)을 되돌려주고, errno 를 설정한다.

nsems 은 세마포어가 만들어질수 있는 크기이다. 이값은 최초 세마포어를 생성하는 생성자의 경우에 크기가 필요하다(보통 1). 그외에 세마포어에 접근해서 사용하는 소비자의 경우에는 세마포어를 만들지 않고 단지 접근만 할뿐임으로 크기는 0이 된다.

이상의 내용을 정리하면 semget 은 아래와 같이 사용할수 있을것이다.
만약 최초 생성이라면
    sem_num = 1;
그렇지 않고 만들어진 세마포어에 접근하는 것이라면
    sem_num = 0; 
sem_id = semget(12345, sem_num, IPC_CREAT|0660)) == -1)
{
    perror("semget error : ");
    return -1;
}
semget 은 성공할경우 int 형의 세마포어 지사자를 되돌려주며, 모든 세마포어에 대한 접근은 이 세마포어 지시자를 사용하게 된다.

위의 코드는 key 12345 를 이용해서 세마포어를 생성하며 퍼미션은 0660으로 설정된다. 세마포어의 크기는 1로 잡혀 있다(대부분의 경우 1). 만약 기존에 key 12345 로 이미 만들어진 세마포어가 있다면 새로 생성하지 않고 기존의 세마포어에 접근할수 있는 세마포어 지시자를 되돌려주게 되고, 커널은 semget 를 통해 넘어온 정보를 이용해서 semid_ds 구조체를 세팅한다.


예제: semget.c
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 

int main()
{
    int semid;
    semid = semget((key_t)12345, 1, 0666 | IPC_CREAT);
}
이제 위의 코드를 컴파일해서 실행시키고 나서 실제로 세마포어 정보가 어떻게 바뀌였는지 확인해 보도록 하자.

커널에서 관리되는 ipc 정보를 알아보기 위해서는 ipcs(8)라는 도구를 이용하면 된다.
[root@localhost test]# ipcs -s    
------ Semaphore Arrays --------
key        semid      owner      perms      nsems      status      
0x00003039 0          root      666        1         
0x00003039 은 key 12345 의 16진수 표현이다. 퍼미션은 666으로 되어 있고 semget 를 통해서 제대로 설정되어 있음을 알수 있다.

세마포어를 이용해서 접근제어 하기

이제 semget 을 통해서 세마포어를 만들거나 접근했으니, 이제 실제로 세마포어상태를 검사해서 접근제어를 해보도록하자.

이것은 semop를 통해서 이루어진다.

semop 의 첫번째 semid 는 semget 을 통해서 얻은 세마포어 지시자이다. 2번째 아규먼트는 struct sembuf 로써, 어떤 연산을 이루어지게 할런지 결정하기 위해서 사용된다. 구조체의 내은 다음과 같으며, sys/sem.h 에 선언되어 있다.
struct sembuf
{
    short sem_num;    // 세마포어의수
    short sem_op;     // 세마포어 연산지정
    short sem_flg;    // 연산옵션(flag)
}
sem_num 멤버는 세마포어의 수로 여러개의 세마포어를 사용하지 않는다면(즉 배열이 아닐경우) 0을 사용한다. 배열의 인덱스 사이즈라고 생각하면 될것이다. 보통의 경우 하나의 세마포어를 지정해서 사용하므로 0 이 될것이다.
sem_op 를 이용해서 실질적으로 세마포어 연산을 하게 되며, 이것을 이용해서 세마포어 값을 증가시키거나 감소 시킬수 잇다. sem_op 값이 양수일 경우는 자원을 다 썼으니, 세마포어 값을 증가시키겠다는 뜻이며, 음수일 경우에는 세마포어를 사용할것을 요청한다라는 뜻이다. 음수일 경우 세마포어값이 충분하다면 세마포어를 사용할수 있으며, 커널은 세마포어의 값을 음수의 크기의 절대값만큼을 세마포어에서 빼준다. 만약 세마포어의 값이 충분하지 않다면 세번째 아규먼트인 sem_flg 에 따라서 행동이 결정되는데,
sem_flg 가 IPC_NOWAIT로 명시되어 있다면, 해당영역에서 기다리지 않고(none block) 바로 에러코드를 리턴한다. 그렇지 않다면 세마포어를 획득할수 있을때까지 block 되게 된다. sem_flg 는 IPC_NOWAIT 와 SEM_UNDO 2개의 설정할수 있는 값을가지고 있다. IPC_NOWAIT 는 none block 모드 지정을 위해서 사용되며, SEM_UNDO 는 프로세스가 세마포어를 돌려주지 않고 종료해버릴경우 커널에서 알아서 세마포어 값을 조정(증가) 할수 있도록 만들어 준다.

설명이 아마 애매모호한면이 있을것이다. 간단한 상황을 예로 들어서 설명해 보겠다.
현재 세마포어 값이 1 이라고 가정하자. 
이때 A 프로세스가 semop 를 통해서 세마포어에 접근을 시도한다. 
A는 접근을 위해서 sem_op 에 -1 을 세팅한다. 즉 세마포어 자원을 1 만큼 사용하겠다라는 
뜻이다.   
현재 준비된 세마포어 값은 1로 즉시 사용할수 있으므로, 
A는 자원을 사용하게 되며, 커널은 세마포어 값을 1 만큼 감소시킨다. 

이때 B 라는 프로세스가 세마포어 자원을 1 만큼 사용하겠다라고 요청을 한다. 
그러나 지금 세마포어 값은 0 이므로 B는 지금당장 세마포어 를 사용할수 없으며, 
기다리거나, 에러값을 리턴 받아야 한다(IPC_NOWAIT).   
B는 자원 사용가능할때까지 기다리기로 결정을 했다.  

잠수후 A는 모든 작업을 다마쳤다. 
이제 세마포어를 되돌려줘야 한다. sem_op 에 1 을 세팅하면, 
커널은 세마포어 값을 1증가시키게 된다. 

드디어 기다리던 B가 세마포어 자원을 사용할수 있는 때가 도래했다. 
이제 세마포어 값은 1이 므로 B는 세마포어를 획득하게 된다.  
커널은 세마포어 값을 1 감소 시킨다.
B는 원하는 작업을 한다.
...
...

세마포어 조작

semctl 이란 함수를 이용해서 우리는 세마포어를 조정할수 있다. semctl 은 semid_ds 구조체를 변경함으로써 세마포어의 특성을 조정한다.

첫번째 아규먼트인 semid 는 세마포어 지시자이다. semnum 은 세마포어 배열을 다룰 경우 사용되며, 보통은 0이다. cmd 는 세마포어 조작명령어 셋으로 다음과 같은 조작명령어들을 가지고 있다. 아래는 그중 중요하다고 생각되는 것들만을 설명하였다. 더 자세한 내용은 semctl 에 대한 man 페이지를 참고하기 바란다.
IPC_STAT
세마포어 상태값을 얻어오기 위해 사용되며, 상태값은 arg 에 저장된다.
IPC_RMID
세마포어 를 삭제하기 위해서 사용한다.
IPC_SET
semid_ds 의 ipc_perm 정보를 변경함으로써 세마포어에 대한 권한을 변경한다.

예제를 통한 이해

지금까지 익혔던 내용을 토대로 간단한 예제프로그램을 만들어보겠다. 예제의 상황은 하나의 파일에 2개의 프로세스가 동시에 접근하고자 하는데에서 발생한다. 파일에는 count 숫자가 들어 있으며, 프로세스는 파일을 열어서 count 숫자를 읽어들이고, 여기에 1을 더해서 다시 저장하는 작업을한다. 이것을 세마포어를 통해서 제어하지 않으면 위에서 설명한문제가 발생할것이다.

위의 문제를 해결하기 위해서는 파일을 열기전에 세마포어를 설정해서 한번에 하나의 프로세스만 접근가능하도록 하면 될것이다. 모든 파일작업을 마치게 되면, 세마포어 자원을 돌려줌으로써, 비로서 다른 프로세스가 접근가능하게 만들어야 한다.
예제: sem_test
#include <sys/types.h> 
#include <sys/sem.h> 
#include <sys/ipc.h> 
#include <stdio.h> 
#include <unistd.h> 

#define SEMKEY 2345 

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
};

static int  semid;
int main(int argc, char **argv)
{
    FILE* fp;
    char buf[11];
    char count[11];

    union semun sem_union; 

    // open 과 close 를 위한 sembuf 구조체를 정의한다. 
    struct sembuf mysem_open  = {0, -1, SEM_UNDO}; // 세마포어 얻기
    struct sembuf mysem_close = {0, 1, SEM_UNDO};  // 세마포어 돌려주기
    int sem_num;

    memset(buf, 0x00, 11);
    memset(count, 0x00, 11);

    // 아규먼트가 있으면 생성자
    // 그렇지 않으면 소비자이다.
    if (argc > 1)
        sem_num = 1;
    else 
        sem_num = 0;            

    // 세마포설정을 한다. 
    semid = semget((key_t)234, sem_num, 0660|IPC_CREAT);
    if (semid == -1)
    {
        perror("semget error ");
        exit(0);
    }    

    // counter.txt 파일을 열기 위해서 세마포어검사를한다. 
    if(semop(semid, &mysem_open, 1) == -1)
    {
        perror("semop error ");
        exit(0);
    }

    if ((fp = fopen("counter.txt", "r+")) == NULL)
    {
        perror("fopen error ");
        exit(0);
    }
    // 파일의 내용을 읽은후 파일을 처음으로 되돌린다.  
    fgets(buf, 11, fp);
    rewind(fp);

    // 개행문자를 제거한다. 
    buf[strlen(buf) - 1] = 0x00;

    sprintf(count, "%d\n", atoi(buf) + 1); 
    printf("%s", count);
    // 10초를 잠들고 난후 count 를 파일에 쓴다. 
    sleep(10);
    fputs(count,fp);

    fclose(fp);
    // 모든 작업을 마쳤다면 세마포어 자원을 되될려준다
    semop(semid, &mysem_close, 1);
    return 1;
}


코드는 매우 간단하지만, 세마포어에 대한 기본적인 이해를 충분히 할수 있을만한 코드이다. 생성자와 소비자의 분리는 프로그램에 넘겨지는 아규먼트를 이용했다. 모든 작업을 마치면 테스트를 위해서 10초를 기다린후에 세마포어를 돌려주도록 코딩되어 있다.

우선 count 를 저장할 파일 counter.txt 를 만들고 여기에는 1을 저장해 놓는다. 그다음 ./sem_test 를 실행시키는데, 최초에는 생성자를 만들어야 하므로 아규먼트를 주어서 실행시키고, 그다음에 실행시킬때는 소비자가 되므로 아규먼트 없이 실행하도록 하자. 다음은 테스트 방법이다.
[root@coco test]# ./sem_test 1&
[1] 3473
36
[root@coco test]# ./sem_test
위 코드를 실행해보면 ./sem_test 1 이 세마포어자원을 돌려주기 전까지 ./sem_test 가 해당영역에서(세마포어 요청하는 부분) 블럭되어 있음을 알수 있고, 충돌없이 count가 잘되는것을 볼수 있을것이다.

세마포어는 커널에서 관리하는데 세마포어를 사용하는 프로세스가 없다고 하더라도 semctl 을 이용해서 제거하지 않는한은 커널에 남아있게 된다. 세마포어 정보를 제거하기 위해서는 semctl 연산을 하든지, 컴퓨터를 리붓 시커거나, ipcrm(8)이란 도구를 사용해서 제거시켜 줘야 한다.

결론

세마포어는 fcntl 과 매우 비슷한 일을 수행하는데, 좀더 세밀한 조정이 가능하다라는 장점을 가지고 있지만 간단한 일을 하기엔 지나치게 복잡한 면이 없잖아 있다. 이럴경우에는 세마포어 대신 fcntl 을 사용하자.
2007/07/20 13:38 2007/07/20 13:38
이 글에는 트랙백을 보낼 수 없습니다

3. XML Parser - rssParser.js

 

먼저의 post에서 var xml = rssParser(req.responseXML); 를 보셨을 것입니다.
req.responseXML
을 받아서 JSON으로 파싱 하는 것입니다.
이부분은 rssParser.js javascript파일로 따로 구현해 보았습니다.

 

 

먼저 전체 소스를 보시죠.

 

  0: /**

  1:  *  @(#)rssParser.js    V0.1    2007/03/15

  2:  *

  3:  *  rss XML Parser extend Prototype.js v1.5

  4:  *  Copyright 2005-2007 by VRICKS, All Right Reserved.

  5:  *  http://www.vricks.com

  6:  *

  7:  *  GNU LESSER GENERAL PUBLIC LICENSE

  8:  *  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  9:  *

 10:  *  @Author Woo-Chang Yang, routine@vrick.com

 11:  */

 12:

 13: function rssParser(xml) {

 14:     var v = Try.these(

 15:         // Rss ¹öÀüÀ» °¡Á® ¿É´Ï´Ù.

 16:         function() {

 17:             return xml.getElementsByTagName("rss")[0].getAttribute("version") ? "2.0" : false;

 18:         },

 19:         function() {

 20:             return xml.getElementsByTagName("rdf:RDF")[0].getAttribute("xmlns") ? "1.0" : false;

 21:         },

 22:         function() {

 23:             return xml.getElementsByTagName("feed")[0].getAttribute("xmlns") ? "atom" : false;

 24:         }

 25:     )

 26:     switch(v) {

 27:         case "2.0"  : return new rssParser2(xml); break;

 28:         case "1.0"  : return new rssParser1(xml); break;

 29:         case "atom" : return new rssParserAtom(xml); break;

 30:         default     : return false;

 31:     }

 32: };

 33:

 34: // Rss 2.0 Calss

 35: var rssParser2 = Class.create();

 36: Object.extend(rssParser2.prototype, {

 37:     initialize    : function(xml) {

 38:         var channel = xml.getElementsByTagName("channel")[0];

 39:         this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;

 40:         this.link   = channel.getElementsByTagName("link")[0].firstChild.nodeValue;

 41:         if(channel.getElementsByTagName("image")[0]) {

 42:             var images   = channel.getElementsByTagName("image")[0];

 43:             this.image   = {

 44:                 "url"    : images.getElementsByTagName("url")[0].firstChild.nodeValue,

 45:                 "title"  : images.getElementsByTagName("title")[0].firstChild.nodeValue,

 46:                 "link"   : images.getElementsByTagName("link")[0].firstChild.nodeValue

 47:             };

 48:         }

 49:         else {

 50:             this.image = {

 51:                 "url"    : "",

 52:                 "title"  : "",

 53:                 "link"   : ""

 54:             }

 55:         }

 56:         this.description = Try.these (

 57:             function() {

 58:                 return channel.getElementsByTagName("description")[0].firstChild.nodeValue;

 59:             },

 60:             function() { return "" }

 61:         );

 62:         this.language    = Try.these (

 63:             function() {

 64:                 return channel.getElementsByTagName("language")[0].firstChild.nodeValue;

 65:             },

 66:             function() {

 67:                 return channel.getElementsByTagName("dc:language")[0].firstChild.nodeValue;

 68:             },

 69:             function() { return ""}

 70:         );

 71:         this.pubDate     = Try.these(

 72:             function() {

 73:                 return channel.getElementsByTagName("pubDate")[0].firstChild.nodeValue;

 74:             },

 75:             function() {

 76:                 return channel.getElementsByTagName("lastBuildDate")[0].firstChild.nodeValue;

 77:             },

 78:             function() { return ""; }

 79:         );

 80:         var items = new Array();

 81:         $A(channel.getElementsByTagName("item")).each(function(i){

 82:             items.push({

 83:                 "category" : Try.these(

 84:                     function() {

 85:                         return i.getElementsByTagName("category")[0].firstChild.nodeValue;

 86:                     },

 87:                     function() { return "" }

 88:                 ),

 89:                 "title" : i.getElementsByTagName("title")[0].firstChild.nodeValue,

 90:                 "link"    : i.getElementsByTagName("link")[0].firstChild.nodeValue,

 91:                 "description" : Try.these (

 92:                     function() {

 93:                         return i.getElementsByTagName("description")[0].firstChild.nodeValue;

 94:                     },

 95:                     function() { return "" }

 96:                 ),

 97:                 "pubDate" : Try.these (

 98:                     function() {

 99:                         return i.getElementsByTagName("pubDate")[0].firstChild.nodeValue;

100:                     },

101:                     function() {

102:                         return i.getElementsByTagName("dc:date")[0].firstChild.nodeValue;

103:                     },

104:                     function() { return "" }

105:                 )

106:             })

107:         })

108:         this.item = items;

109:     }

110: });

111:

112: var rssParser1 = Class.create();

113: Object.extend(rssParser1.prototype, {

114:     initialize    : function(xml) {

115:         var channel = xml.getElementsByTagName("channel")[0];

116:         var images  = xml.getElementsByTagName("image")[0];

117:         this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;

118:         this.link   = channel.getElementsByTagName("link")[0].firstChild.nodeValue;

119:         this.image  = {

120:             "url"   : images.getElementsByTagName("url")[0].firstChild.nodeValue,

121:             "title" : images.getElementsByTagName("title")[0].firstChild.nodeValue,

122:             "link"  : images.getElementsByTagName("link")[0].firstChild.nodeValue

123:         };

124:         this.description = channel.getElementsByTagName("description")[0].firstChild.nodeValue;

125:         this.language    = channel.getElementsByTagName("dc:language")[0].firstChild.nodeValue;

126:         this.pubDate     = channel.getElementsByTagName("dc:date")[0].firstChild.nodeValue;

127:         var items        = xml.getElementsByTagName("item");

128:         var itemValue    = new Array();

129:         for(var i = 0; i < items.length; i++) {

130:             itemValue.push({

131:                 "category"   : items[i].getElementsByTagName("category")[0].firstChild.nodeValue,

132:                 "title"      : items[i].getElementsByTagName("title")[0].firstChild.nodeValue,

133:                 "link"       : items[i].getElementsByTagName("link")[0].firstChild.nodeValue,

134:                 "description":

135:                               items[i].getElementsByTagName("description")[0].firstChild.nodeValue,

136:                 "pubDate"    : items[i].getElementsByTagName("dc:date")[0].firstChild.nodeValue

137:             });

138:         };

139:         this.item = itemValue;

140:     }

141: });

142:

143: var rssParserAtom = Class.create();

144: Object.extend(rssParserAtom.prototype, {

145:     initialize    : function(xml) {

146:         this.title   = xml.getElementsByTagName("title")[0].firstChild.nodeValue;

147:         this.link    = xml.getElementsByTagName("link")[0].getAttribute("href");

148:         this.image   = {

149:             "url"    : "",

150:             "title"  : "",

151:             "link"   : ""

152:         };

153:         this.description = xml.getElementsByTagName("info")[0].firstChild.nodeValue;

154:         this.language    = "";

155:         this.pubDate     = xml.getElementsByTagName("modified")[0].firstChild.nodeValue;

156:         var items        = xml.getElementsByTagName("entry");

157:         var itemValue    = new Array();

158:         for(var i = 0; i < items.length; i++) {

159:             itemValue.push({

160:                 "category"   : "",

161:                 "title"      : items[i].getElementsByTagName("title")[0].firstChild.nodeValue,

162:                 "link"       : items[i].getElementsByTagName("link")[0].getAttribute("href"),

163:                 "description":items[i].getElementsByTagName("summary")[0].firstChild.nodeValue,

164:                 "pubDate"    : items[i].getElementsByTagName("created")[0].firstChild.nodeValue

165:             });

166:         };

167:         this.item = itemValue;

168:     }

169: });

 

function rssParser(xml) {...}을 보시면 Rss의 버전을 구해서 알맞은 Class를 호출 하는 부분 입니다.
Try.these (...)
부분이 보이실 것입니다. API 보기
아래에 나열된 function()을 수행하여 먼저 성공한 하나만을 호출 합니다. 정말 멋진 생각입니다. ^^;

var rssParser2 = Class.create();
Object.extend(rssParser2.prototype, { ... }
새로운 Class를 생성하여 prototype을 확장 하였습니다.
Class.create()
로 확장을 하면 항상 initialize 를 수행 합니다.
new rssParser2(xml);
하게 되면 자동으로 initialize 부분이 수행이 된다는 의미 입니다.

var channel = xml.getElementsByTagName("channel")[0];
태그명 channel의 첫번째 element를 가져 옵니다. channel이 한개뿐인데 배열로 가져 왔습니다.
태그명으로 가져 올 때는 getElementsByTagName 여기서도 보실수 있듯이 복수형입니다.
그래서 항상 배열형태로 리턴이 됩니다. [0] 이분이 빠지면 에러가 납니다. ^^;

이제 channel의 자식 노드들을 뽑아올 차례 입니다.

this.title  = channel.getElementsByTagName("title")[0].firstChild.nodeValue;
<channel>
아래에 있는 태그명 <title>의 첫번째 노드의 value를 가져 옵니다.
DOM
에 대한 자세한 내용은
URL : http://www.ibm.com/developerworks/kr/library/wa-ajaxintro5/
을 참고 하시기 바랍니다.

이렇게 title, lilnk, descriiption을 가져옵니다.
pubDate
의 경우는 먼저 pubDate 태그를 찾은 후 없으면 lastBuildDate를 그것도 없으면 공백문자를 리턴해 줍니다.
뭐 어려운거 없습니다. 그냥 DOM으로 노드 뽑아 오듯이 하나씩 뽑아서 대입해 주면 됩니다.

<item>태그에 들어있는 각각의 post를 가져올 차례입니다.
$A(channel.getElementsByTagName("item")).each(function(i){ ... })
$A
Array의 확장판으로 Ruby틱한 배열형태를 사용할 수 있도록 해 줍니다. API 보기
<item>
을 돌면서 <item>에 포함한 <title>, <link>, <description>, <pubDate>를 가져와서 JSON형태로 파싱하고
파싱된 것들을 item이란 변수에 배열로 담아 놓습니다.
이렇게 해 놓으면 후에 item[i].title, item[i].link, item[i].description 으로 가져올 수 있습니다.


어떻게 글로 설명을 할려니 더 어려워 진거 같네요.
구현된 모습을 한번 보겠습니다.

 
 
 
얼추 비슷합니다. ^^V
이것으로 이번 post는 마치겠습니다.
 
내용도 없고 재미도 없는 글 읽어 주셔서 감사합니다.
 
 

시간이 되면 rssParser.js를 이용한 구글 개인화 페이지를 구현해 보도록 하겠습니다.
물론 prototype.js를 이용할 것이며 Scriptaculous 의 이펙트도 이용할 것입니다.
화면상으로 잠시 맛배기를 ^^;
 
 



2007/07/20 09:59 2007/07/20 09:59
이 글에는 트랙백을 보낼 수 없습니다

2. Rss XML로 가져오기

 

이제 가져올 element는 정해 졌습니다.
하지만 한가지 문제가 있습니다. 이놈을 어떻게 가져 올 것인가???

 

Ajax라고 많이 들어 보셨을 것입니다. 일명 HTML, Javascript DHTML, DOM으로 구성된 비동기 통신을 가리키는 말입니다.
Ajax
의 핵심에는 XMLHttpRequest 가 있습니다. 대충 보니 HTTP프로토콜을 이용하여 XML로 어떤 값을 받는거 같습니다
.
이놈이 사용자가 눈치채지 못하게 어떤 값을 가져오고 그것을 DOM형태로 소리 소문 없이 문서 사이에 끼워 넣으면서 여러가지 동적인 화면을 구성할 수 있습니다.


Ajax
에 대해서 더 자세히 아시고 싶으신 분은
URL :
http://www.ibm.com/developerworks/kr/library/wa-ajaxintro1.html
를 참고해 주세요.
Post의 주제가 초간단 Rss Reader를 만드는 것이기 때문에 주제에 충실 하도록 하겠습니다.

 

 

이제 보실 부분은 rssReader.htmljavascript 부분입니다.
3개의 function으로 구성되어 있습니다.

 

uriChk()
입력한 URL값이 유효한 값인지 검사 합니다. 최소한 http://XXX.XXX https://XXX.XXX 형태가 되는지 검사합니다.
여기를 통과하면 일단 유효한 URL이라고 판단하여 getRss(URL)을 호출 합니다.


getRss(URL)
이부분이 핵심입니다. 해당 URL Ajax비동기 통신을 하여 xml을 가져 옵니다.
그리고 var xml = rssParser(req.responseXML); xml JSON으로 파싱을 시킵니다
.
rssParser
rssParser.js include javascript에 들어 있습니다
.
만약 알맞게 JSON으로 파싱이 되었다면 printHTML(JSON) 을 호출 합니다.


printHTML(JSON)
이 부분은 그냥 이뿌게 꾸며서 <div id="title"></div> 여기에 제목 부분을 넣고
<div id="contents"></div>
여기에 item 리스트를 넣는 것입니다.

 

 

전체 소스를 보겠습니다.

 

 0: <?xml version="1.0" encoding="EUC-KR" ?>
 1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2: <html xmlns="http://www.w3.org/1999/xhtml">
 3: <head>
 4: <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR" />
 5: <style type="text/css">
 6:     *         {font-size:12px;}
 7:     body      {margin:0px; padding:0px;color:#666666;}
 8:     a:active  {color:#236fa5; text-decoration:none;}
 9:     a:visited {color:#236fa5; text-decoration:none;}
10:     a:link    {color:#236fa5; text-decoration:none;}
11:     a:hover   {color:#518fbb; text-decoration:underline;}
12: </style>
13: <script type="text/javascript" language="javascript" src="./prototype.js"></script>
14: <script type="text/javascript" language="javascript" src="./rssParser.js"></script>
15: <script type="text/javascript" language="javascript">
16: <!--
17:     function uriChk() {
18:         var uri = $("uri");
19:         if(uri.value == "") {
20:             alert("가져올 피드의 주소를 입력 하세요.");
21:             uri.focus();
22:             return;
23:         }
24:         if(!(/(http|https):\/\/\w+([-.]\w+).+$/).test(uri.value)) {
25:             alert("피드 주소가 유효하지 않습니다.");
26:             uri.focus();
27:             return;
28:         }
29:         getRss(uri.value);
30:     }
31: 
32:     function getRss(uri) {
33:         Element.hide("title");
34:         $("contents").innerHTML = $("loading").innerHTML;
35:         new Ajax.Request(
36:             uri,
37:             {
38:                 method : "get",
39:                 onSuccess : function(req) {
40:                     var xml = rssParser(req.responseXML);
41:                     if(!xml) { 
                            alert("Rss
포멧이 아닙니다.");
                            $("contents").innerHTML = "";
                            return;
                        }
42:                     printHtml(xml);
43:                 },
44:                 onFailure : function() {
45:                     alert("데이타를 가져오는데 실패 했습니다.");
46:                     $("contents").innerHTML = "";
47:                 }
48:             }
49:         );
50:     }
51: 
52:     function printHtml(xml) {
53:         var d = new Date(xml.pubDate);
54:         var title = '';
55:         var cont = '';
56:         title += '<a href="' + xml.link + '" target="_blank"><strong style="font-size:16px;">' 
                  + xml.title + '</strong></a>';
57:         if(d != 'NaN') {
58:             title += '<span style="padding-left:20px;">(' + d.toLocaleString() + ')</span>';
59:         }
60:         title += '<br/><br/>' + xml.description;
61:         for(var i = 0; i < xml.item.length; i++) {
62:             cont += '<a href="' + xml.item[i].link +'" target="_blank">'
                     + '<strong style="font-size:16px;">' + xml.item[i].title + '</strong></a>';
63:             cont += '<hr style="height:1px;color:518fbb"/>';
64:             if(xml.item[i].pubDate != "") {
65:                 var pd = new Date(xml.item[i].pubDate);
66:                 cont += '<span style="color:#aaaaaa;">' + pd.toLocaleString() 
                         + '</span><br/><br/>';
67:             }
68:             cont += xml.item[i].description.replace("\n", "<br/>") + '<br/><br/><br/>';
69:         };
70:         $("title").innerHTML = title;
71:         $("contents").innerHTML = cont;
72:         Element.show("title");
73:     }
74: //-->
75: </script>
76: <title>Rss Reader V 0.1 by Vricks - www.vricks.com</title>
77: </head>
78: <body>
79: <div style="width:800px;text-align:left;margin:auto;">
80:     <div style="background-color:#ff000a;padding:16px 0 50px 16px;margin:20px 0;">
81:         <strong style="font-size:14px;color:#ffffff;">
82:             Rss Reader V0.1
83:         </strong>
84:     </div>
85:     <div style="text-align:center;margin:20px;">
86:         <input type="text" id="uri" value="http://blog.rss.naver.com/jeffyang.xml" 
             style="width:360px;" />
87:         <input type="button" value="RSS 가져오기" onclick="uriChk();" />
88:     </div>
89:     <div id="title" 
             style="padding:20px;background-color:#f7f7f4;border:1px solid #aaaaaa;display:none;"
>
        </
div>
90:     <div id="contents" style="padding:20px;"></div>
91:     <div style="display:none;" id="loading"><div style="text-align:center;">
             <
img src="./loading.gif" alt=""
/>
        </
div></div>
92: </div>
93: </body>
94: </html>

$("uri") -> API 보기
document.getElementById("uri")
와 같은 말입니다. 물론 $()함수는 더 많은 기능을 제공해 줍니다.

Element.hide("title"); -> API 보기
document.getElementById("title").style.display = "";
과 같은 말입니다. 물론 Element.hide("title") 함수가 더 많은 기능을 제공합니다.

 

var myAjax = new Ajax.Request(
    url,
    {
        method: 'get',
        parameters: params,
        onSuccess: function(req) {
            something...
        },
        onFailure: function() {
            something....
        }
    }  
);

XMLHttpRequest
를 쉽게 구현해 주는 부분입니다. -> API 보기
url :
접속할 URL입니다. 뒤에 오는 method get일 경우 get방식의 prameters를 붙여서 사용할 수 있습니다.
) var url = "http://test.com/test.xml?id=1234&date=2007-03-01";
method : get/post
가 올 수 있습니다
.
parameters : get
방식의 경우 null 이 옵니다. post의 경우는 ?를 제외한 prameter를 붙여서 사용 합니다
.
) var params = "id=1234&date=2007-03-01";
onSuccess :
성공 했을때의 코드를 기술 합니다
.
onFailure :
실패 했을때의 코드를 기술 합니다.


printHTML()
JSON으로 파싱된 값들을 받아서 뿌려 주는 일을 합니다.
var xml = rssParser(req.responseXML);
로 받아서 printHTML(xml)로 보냈습니다
.
channel
title을 가져 올 때는 단순히
xml.title
item[0]
title을 가져 올 때는 xml.item[0].title 이렇게 가져 오면 됩니다
.
rssParser.js
는 다음 post에서 만나 보실 수 있습니다.

 

2007/07/20 09:58 2007/07/20 09:58
이 글에는 트랙백을 보낼 수 없습니다

언제부터인지 우리 주위에 Rss라는 단어가 많이 익숙해져 있습니다.
블로그, 뉴스, 도서 사이트 심지어 UCC까지 WEB2.0시대인 요즘은 원하는 정보를 Rss 서비스로 받아 보실 수 있습니다
.
이러한 Rss 피드를 모아서 편하게 구독하게 해 주는 것이 일명 Rss Reader입니다
.
Rss Reader
에는 웹 기반의 Rss Reader, 설치형 Rss Reader, 동기화 가능 설치형 Rss Reader이 있습니다
.
이러한 것들을 구현하기 위해서는 수집, 정렬, 검색, 동기화 등등 많은 기술들이 들어 갑니다
.
하지만 버뜨~ post의 제목이 초간단 Rss Reader입니다
.
제목에 충실히 인터넷 연결이 되어 있고 IE가 깔려 있으면(흠 이부분은 나중에 이야기 드릴께요 ㅠㅠ) 단순히 해당 피드 주소만 넣으면 읽기만 가능한 그러한 Reader를 만들어 보겠습니다
.
누군가 그랬습니다
. Simple is The Best....
사실 실력이 요까지라서 더 복잡한건 ..... OTL

 

초간단 Rss Reader를 구현하기 위한 기본적인 지식으로 Javascript, html, xml의 기본적인 지식이 필요합니다.
Javascript
의 경우 prototype.js를 기본으로 하여 이미 구현한 라이브러리를 최대한 이용하도록 하겠습니다
.
prototype.js
URL : http://www.prototypejs.org 에서 다운 받으실 수 있으며

http://blog.naver.com/jeffyang/150015619963
에 가시면(post 자랑 ^^;) 한글로 된 메뉴얼의 링크를 얻으실 수 있습니다.
Post 작성하는 순간에 버전이 1.51 RC1으로 올랐습니다
.
이번 버전에는 XML노드를 처리하기 위한 라이브러리가 보이는 군요
.
애써 외면하며 여기서는 prototype 1.5를 기준으로 하겠습니다.

 

간략한 post의 순서입니다.

 

1. Rss를 분석하여 어떤 elements를 가지고 표현할 것인지 결정 합니다.
2. Rss
XML로 받아 옵니다. 이 부분은 Javascript XMLHttpRequest객체를 가지고 구현을 합니다
.
3.
받아온 XML을 파싱해야 합니다. 이 부분 역시 Javascript JSON 포멧으로 파싱 합니다
.
4. JSON
을 받아서 화면이 html코드로 이쁘게 뿌려 주면 간단 RssReader의 완성 입니다.


Javascript, html, xml
로만 하겠다고 해 놓고 JSON이란 놈이 보이는 군요.
JSON
JavaScript Object Notation 라 불리우는 경량의 DATA 교환 형식입니다
.
Javascript
맞죠!!! 네 분명 Javascript Object라고 했습니다. JSON에 대해서 더 자세히 알고 싶으신 분은

URL : http://json.org/json-ko.html
을 참고 하세요. 한글로 잘 설명되어 있습니다.
평소 영어를 외계어로 여기는 까닭에 해석하시는 수고를 덜어 드리고자 이렇게 친절하게 한글주소로 안내를 해 드립니다.


이쯤에서 첫번째 주제 Rss를 분석해 보도록 하겠습니다.


현재 국내에서 가장 많이 사용되고 있는 것이 Rss 2.0 포멧입니다. 그리고 Rss 1.0, ATOM 포멧도 많이 보입니다.
솔직히 웬만한 사이트들은 모두 Rss 2.0을 지원해 줍니다. 하지만 국내 사이트들 중에는 무늬만 Rss 2.0이고 표준을 지키지 않은 곳들이 많이 있습니다
.
표준을 준수하지 않은 경우는 해당 Rss서비스에 맞도록 하나씩 처리를 해 줘야 합니다
.
필수 Elements가 빠진곳이나 엉뚱한 Elements가 나타나는 곳들도 종종 있었습니다
.
한국에서 구글이 힘을 못쓰는 이유는 포탈 및 대형 사이트 들의 비표준이 지대한 공헌을 했다고 하더군요. (공감 100% 입니다
.)
어찌 되었건 한국어로 된 Rss 서비스를 받고자 한다면 이 정도 수고는 해야 하는군요. ㅠㅠ

이러한 비표준을 처리하기 위해서 prototype.js Try.these 구문을 이용 했습니다.

 

1. RSS 2.0 분석

 

Rss란 무엇인가?
한국정보통신기술위원회의 문서를 빌리자면

 

RSS 는 웹 컨텐츠 신디케이션 형식으로 Really Simple Syndication 의 약자이다.
컨텐츠 신디케이션(Content Syndication)이란 사이트 컨텐츠의 일부 또는 전체를 다른 서비스에서 이용할 수 있도록 해주는 것을 말한다
.
신디케이션된 컨텐츠(또는 feed)는 컨텐츠 자체와 메타데이타 로 구성된다. 이러한 feed 에는 헤드라인 내용만 있을 수도 있고, 스토리에 대한 링크만 있을 수도 있으며, 사이트의 전체 컨텐츠가 포함될 수도 있다
.
근래에 들어 특히 1 인 미디어인 블로그(blog)를 중심으로 컨텐츠 신디케이션 표준인 RSS 의 가능성을 확인되면서, 다양한 응용들이 등장하고 있고, 차세대 웹 콘텐츠 유통 기술의 핵심 표준으로 주목받고 있다
.


뭔 소린지 잘 모르겠습니다. 그냥 구조를 한번 살펴 보죠.
대략 다음과 같은 구조로 되어 있습니다. 이중 굵게 처리된 것들은 필수 요소 입니다
.
item
태그의 title, link, description은 모두 필수는 아니고 세개 중 한개는 반드시 있어야 합니다.

 

 0: <?xml version="1.0" encoding="euc-kr" ?>
 1: <rss version="2.0">
 2:     <channel>
 3:         <title>채널의 제목</title>
 4:         <link>채널의 링크</link>
 5:         <description>채널에 대한 간략한 설명</description>
 6:         <language>채널이 작성되는 언어</language>
 7:         <copyright>채널의 저작권 통지</copyright>
 8:         <managingEditor>채널 편집 담당자의 전자우편</managingEditor>
 9:         <webMaster>채널 웹마스타의 전자우편</webMaster>
10:         <pubDate>채널 컨텐츠의 발행 시간</pubDate>
11:         <lastBuildDate>채널이 마지막으로 변경된 시간</lastBuildDate>
12:         <category>채널이 속해있는 가테고리</category>
13:         <generator>채널을 생성하는데 사용된 프로그램</generator>
14:         <images>
15:             <url>이미지의 URL</url>
16:             <title>이미지의 제목</title>
17:             <link>해당 사이트의 링크</link>
18:             <width>이미지 넓이</width>
19:             <height>이미지 높이</height>
20:         </images>
21:         <item>
22:             <title>항목의 제목</title>
23:             <link>항목의 URL</link>
24:             <description>항목의 내용</description>
25:             <author>저작자의 전자우편</author>
26:             <category>항목이 속해있는 가테고리</category>
27:             <comments>항목과 관련된 설명 페이지의 URL</comments>
28:             <guid>고유하게 항목을 식별할 수 있는 문자열</guid>
29:             <pubDate>항목 발행 시간</pubDate>
30:         </item>
31:     </channel>
32: </rss>

 

어렵습니다. ㅠㅠ.
이것만 봐서는 눈에 잘 안 들어 옵니다. 그래서 IE 7.0의 피드랑 비교해서 각 항목을 알아 보겠습니다.

 

 

<IE 7.0 피드 그림>

 

 

대충 감이 옵니다.
화면을 보아 하니 <channel>에서 시작해서 </channel>로 닫히는군요
.
<channel>
아래에 있는 <link><title><pubDate>도 대충 어떤 놈인지 알겠습니다
.
중간에 <image>도 보이구 <category>도 보입니다
.
<item>
에서 시작해서 </item>까지가 한개의 post 로 구분되어 있습니다
.
<item>
아래에는 <link><title><pubDate><description>이 보이네요..

이제 감 잡았습니다.
가져올 elements를 구분하겠습니다
.
피드의 titlelink
, pubDate(없으면 lastBuildDate)를 가져와서 있는놈들을 큰 제목에 뿌려 주고

item
을 배열로 돌리면서 item의 자식들 link, title, pubDate, desciption 중 있는놈들을 모두 뿌려 주겠습니다.


2007/07/20 09:57 2007/07/20 09:57
이 글에는 트랙백을 보낼 수 없습니다
웅쓰:웅자의 상상플러스
웅자의 상상플러스
전체 (379)
게임 (5)
영화 (2)
기타 (23)
맛집 (5)
영어 (2)
대수학 (3)
형태소 (5)
Hacking (9)
Linux (112)
HTML (48)
Application_developing (48)
Web_developing (102)
Window (11)
«   2007/07   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
  1. 2016/01 (1)
  2. 2015/12 (3)
  3. 2015/10 (3)
  4. 2015/03 (2)
  5. 2015/01 (4)