18장 Practical Network Project Database Lab 조 성 훈
목 차 Quote Service Quote Server Program 검토하기 get_tickinfo()를 통한 시황 정보 얻기 broadcast()를 통한 시황 정보 브로드케스팅 Client Program 검토하기 데모의 컴파일과 실행 Summary
Quote Service [1] 문제 같은 정보를 위해 quote server에 별개의 TCP/IP connection을 이용 대역폭 소진의 문제 해결책 한 로컬 서버 프로그램은 주식 시장 시황을 지속적으로 불러올 수 있음 관련된 정보를 로컬 네트워크로 브로드케스트
Quote Service [2] 주식 시장 시세표 얻기 http://finance.yahoo.com 검색 후 페이지
Quote Service [3] telnet으로 스프레드시트 레코드 얻기 Spreadsheet data중 한 라인을 얻어옴 Quote fetch procedure Finance.yahoo.com에 80 포트로 접속 GET과 경로명을 입력 Response로 spreadsheet data의 한 라인을 소켓을 통해 반환 소켓을 닫음
Quote Service [4] 소스 파일 리스트 Ticker 심볼들의 목록에 대해 조사되는 파일 Tickers.h 모든 소스 모듈에 공통되는 헤더 파일 Quotes.h 로컬 시황 서버 프로그램을 구현한 모듈 Qserve.c Syslog 로깅 기능에 서버 메시지를 기록하는 모듈 Msgf.c 시세를 보는 클라이언트 프로그램 Mktwatch.c Internet address convenience 기능 Mkaddr.c 잡다한 기능들이 구현되어 있음 Misc.c Tickers.rc 파일로부터 주식 시장 ticker 심볼을 로드 Load.c 원격지 인터넷 시황 서버로부터 시황 정보를 불러옴 Gettick.c 반환되는 시황 데이터에 대한 파서 Csvparse.c 원격지 인터넷 시황 서버에 연결하는 기능을 구현함 Connect.c LAN에 시황을 브로드케스팅하는 기능 구현 Bcast.c 프로젝트 make 파일 Makefile
Quote Server Program 검토하기[1] Qserve.c 소스 모듈 Quote server 자체에 main 프로그램 형성 주식 시장 시황을 얻는 역할 수행 Local Area Network에 시황 정보 브로드케스팅
Quote Server Program 검토하기[2] 순서도 시작 getopt()호출 옵션 해석 Tickers[]에 Tickers.rc 로드 load()호출 브로드케스트 어드레스 생성 mkaddr()호출 socket()호출 UDP 소켓 생성 서버 루프 소켓에 특성 부여 Ctrl+C 종료
Quote Server Program 검토하기[3] 강조 사항 옵션 –a, -b 채용 변수 cmdopt_a과 cmdopt_b에 저장됨 static char *cmdopt_a = DFLT_SERVER; //10라인 static char *cmdopt_b = DFLT_BCAST; //13라인 모니터되는 주식 시세표는 tickers[]에 유지됨 ntick: entry의 수 static TickReq tickers[MAX_TICKERS]; //18라인 static int ntick = 0; //19라인
Quote Server Program 검토하기[4] TickReq 구조체 typedef struct { char ticker[TICKLEN+1]; /* Symbol */ double last_trade; /* Last Price */ char *date; /* Date */ char *time; /* Time of Last Trade */ double change; /* +/- Change */ double open_price; /* Opening Price */ double high; /* High Price */ double low; /* Low Price */ double volume; /* Volume of Trades */ int flags; /* Server flags */ time_t next_samp; /* Time of next evt */ } TickReq;
Quote Server Program 검토하기[5] Usage() -h 옵션일 때 request에 대한 사용 정보를 제공함 //24~36라인 static void usage(void) { printf("Usage: %s [-h] [-a address:port]\n" "where:\n\t-h\t\tRequests usage info.\n" "\t-a address:port\tSpecify the server\n" "\t\t\taddress and port number.\n" "\t-b bcast:port\tSpecify the broadcast\n" "\t\t\taddress and port number.\n", command); }
Quote Server Program 검토하기[6] 서버 루프 단계(1) Next time(tn): 0으로 초기화(현재 시간: td) tn=0; time(&td); //144 ~ 145라인 Ticker[]의 처음부터 끝까지 반복 for ( x=0; x<ntick; ++x ) //151라인 tm: ticker를 갱신하는 동안 잠시 멈출 시간 표시 5초씩 5분 미만까지 증가 If(tm < (time_t) 5 * 60) tm+=5; //209 ~ 210라인 이벤트 시간이 tn에서 발견될 경우 비활성 시간 계산 후 변수 zzz에 배정 sleep()호출 If(!tn) tn =td +tm; // 218 ~ 223라인 If(tn>=td) if ((zzz=tn-td)) sleep(zzz);
Quote Server Program 검토하기[7] 서버 루프 단계(2-1) Ticker[] 엔트리들은 flag들을 멤버로 포함함 Flag FLG_UNKNOWN이 설정 ticker가 알려지지 않음 Flag FLG_ERROR가 설정 에러 발생 if ( tickers[x].flags & FLG_UNKNOWN || tickers[x].flags & FLG_ERROR ) continue; //157라인 td의 현재 시간과 ticker[x]의 다음 이벤트 시간과 비교됨 같다면, ticker에 새로운 시황을 가져올 시간 의미 if ( td >= tickers[x].next_samp ) { z = get_tickinfo(&tickers[x],cmdopt_a); Get_tickinfo() ticker 심볼에 대한 ticker 정보를 얻기 위해 호출됨
Quote Server Program 검토하기[8] 서버 루프 단계(2-2) 다음 이벤트 시간은 현재 시간과 추가한 시간 주기 tm으로 계산됨(5분을 최대로 해서 증가) time(&tickers[x].next_samp); //184 라인 tickers[x].next_samp += tm; //185 라인 시황 정보 fetch가 성공했는지 알아보는 테스트 성공했다면, 정보들을 local area network의 클라이언트에 전송함 if ( !z ) //191 ~ 195 라인 broadcast(s,&tickers[x],(struct sockaddr *)&bc_addr, bc_len); Ticker[]에 있는 모든 시황 정보 표시 심볼들이 처리될 때까지 x를 증가시켜 2-1, 2-2단계 반복
Get_tickinfo()를 통한 시황 정보 가져오기[1] Quote.h 매크로 알아보기 DFLT_SERVER Default hostname과 시황에 대한 접속 port 정의 #define DFLT_SERVER “finance.yahoo.com:80” //25 라인 DFLT_BCAST Default broadcast address 정의(loopback 허용) #define DFLT_BCAST “127.255.255.255:9777” //30 라인 Parm 구조 C 데이터 타입으로 해석한 정보를 조정하여 형 정의 Typedef struct { char type; void * parm; } Parm; //35~38 라인
Get_tickinfo()를 통한 시황 정보 가져오기[2] TIMEOUT_SECS 10초로 설정 응답 전에 시황 정보에 대한 wait time을 정의 #define TIMEOUT_SECS 10 MAX_TICKERS Qserve.c 모듈에 있는 tickers[]의 크기 정의 #define MAX_TICKERS 256 // 53 라인 TickReq 서버에 대한 ticker 심볼 entry 정의 클라이언트 프로그램에서 사용됨 FLG_UNKNOWN & FLG_ERROR Unknown과 에러에 해당하는 flag
Get_tickinfo()를 통한 시황 정보 가져오기[3] Gettick.c의 주요 컴포넌트 정의 Timeout FLAG f TRUE request가 timeout Static int f = FALSE; 신호 캐처 함수(sig_ALRM()) 시간이 만료되었을 때 SIGALRM 신호를 받음 f를 설정함 Static void sig_ALRM(int signo) { f=TRUE; }
Get_tickinfo()를 통한 시황 정보 가져오기[4] 매개변수 : TickReq 포인터와 server address 매개변수 테이블이 parms[]에 형성됨 //52 ~ 69 라인 parms[0].type = 'S'; /* String */ parms[0].parm = &tkr; /* Ticker name */ … parms[8].type = 'D'; parms[8].parm = &req->volume; SIGALRM에 대한 신호 처리 초기화 //74 ~ 77 라인 sa_new.sa_handler = sig_ALRM; sigemptyset(&sa_new.sa_mask); sa_new.sa_flags = 0; sigaction(SIGALRM,&sa_new,&sa_old);
Get_tickinfo()를 통한 시황 정보 가져오기[5] f가 FALSE로 초기화되어 타이머가 시작됨 연결 request quotation server의 주소로 connect()를 호출하여 생성 s = Connect(addr); if ( s == -1 ) goto errxit; GET request 지정 sprintf(buf,"GET /d/quotes. csv?s=%s&f=sl1d1t1c1ohgvs=%s&f=sl1d1t1c1ohgv&e=.csv\r\n“, req->ticker); // 96 ~ 100 라인 GET request가 quotation server에 입력 write(s,buf,strlen(buf));
Get_tickinfo()를 통한 시황 정보 가져오기[6] Spreadsheet 레코드를 기다리고 읽음 do { z = read(s,buf,sizeof buf); } while ( !f && z == -1 && errno == EINTR ); //108~110 Error 발생했을 경우 er 변수에 errno 값을 저장 er = errno; // 112 라인 타이머를 취소하고 소켓을 닫음 Close(s); SIGALRM 신호에 대한 신호 처리를 되돌림 sigaction(SIGALRM,&sa_old,NULL); //117 라인 Timeout이나 에러 처리 Msgf() 호출이나 syslog 로깅 기능 통해 error 기록
Get_tickinfo()를 통한 시황 정보 가져오기[7] 캐리지 리턴이나 라인피드 제거 Spreadsheet 레코드에서 제거됨 Ticker 심볼이 unknown일 때의 시퀀스 테스트 if ( strstr(buf,"N/A,N/A,N/A,N/A,N/A") ) { msgf('e',"Unknown Ticker: '%s'", req->ticker); req->flags |= FLG_UNKNOWN; errno = EBADMSG; /* For caller */ return -1; /* Failed */ }
Get_tickinfo()를 통한 시황 정보 가져오기[8] Ticker 심볼이 known일 때의 시퀀스 테스트 Spreadsheet 레코드 해석 if ( (z = extract_parms(parms,9,buf)) < 0 ) { /* Report failed parse of data */ msgf('e',"Field # %d: '%s'",z,buf); req->flags |= FLG_ERROR; errno = EBADMSG; /* For caller */ return -1; /* Failed */ } // 152 ~ 158 라인 Ticker 심볼 자체가 req->ticker 엔트리에 복사 strncpy(req->ticker,tkr,TICKLEN)[TICKLEN] = 0; //161 라인 Req 멤버가 새로운 ticker 정보로 갱신됨 의미 Return 0; // 166 라인
broadcast()를 통한 시황 정보 브로드케스팅[1] Server의 broadcasting broadcast() 관련 클라이언트에 정보 공유를 위해 호출함 Broadcast()의 기본 단계 buf[2048] 브로드케스트 데이터그램을 지정하기 위해 사용 char buf[2048]; /* Buffer */ char *cp = buf; /* Buf. ptr */ buf[]에 null로 끝나는 ticker 심볼을 복사하여 데이터그램 생성 strcpy(buf,quote->ticker); // 21 라인 Cp에 새로운 포인터 지정 cp = buf + strlen(buf) + 1; // 22 라인
broadcast()를 통한 시황 정보 브로드케스팅[2] Last_trade값 지정 sprintf(cp,"%E",quote->last_trade); //23 라인 Cp의 위치 지정 cp += strlen(cp) + 1; // 24 라인 다른 데이터 컴포넌트에 위의 단계 반복 각각의 컴포넌트는 null로 끝나는 문자열 데이터그램의 길이 계산 msglen = cp - buf; //40 라인 Sendto() 소켓 s에 데이터그램을 브로드케스트하기 위해 호출 z = sendto(s,buf,msglen,0,bc_addr,bc_len); // 45 라인
Client Program 검토하기[1] Client program Mktwatch 특성 브로드케스트 어드레스로 바인드되야만 함 브로드케스트된 주식 시황을 수신 특성 커멘드라인 옵션 해석 (81 ~ 100 라인) 브로드케스트 어드레스 생성 ( 105 ~ 118 라인) UDP 소켓 생성 (123 ~ 129 라인) SO_REUSEADDR 옵션이 enable됨(135 ~ 146 라인) 하나 이상의 클라이언트에서 같은 호스트의 정보를 수신할 경우 설정
Client Program 검토하기[2] 클라이언트의 listening 시작 뒤의 단계 브로드케스트 어드레스로 연결됨(151 ~ 160 라인) 루프에 대한 클라이언트 listening 시작 Ctrl+C를 할 때까지 반복 클라이언트의 listening 시작 뒤의 단계 도착할 데이터그램 기다림 수신 데이터그램은 qserve server 브로드케스트 메시지 Extract() 데이터그램에 포함된 모든 문자열 데이터 추출 Tkr에 변환된 데이터 저장 if ( !extract(dgram,&tkr) ) // 188 라인 종료까지 반복 수행
데모 컴파일과 실행[1] 컴파일 2개의 실행 파일 Qserve: quotation server program Mktwatch: broadcast listener client
데모 컴파일과 실행[2] Qserve quotation server 시작하기 인터페이스 브로드케스트 어드레스를 통한 실행 디폴트 루프백 파라메터를 통한 서버 프로그램 실행 127.255.255.255:9777 디폴트 브로드케스트 어드레스와 port 번호 인터페이스 브로드케스트 어드레스를 통한 실행 두 경우 모두 finance.yahoo.com에 접속 후 브로드케스팅 시작함
데모 컴파일과 실행[3] 에러 보기 /var/log/messages 로그 파일에 서버 에러 기록됨 34 ticker 심볼이 tickers.rc로부터 로드됨
데모 컴파일과 실행[4] Mktwatch client 시작하기 Qserve 디폴트 사용시 $ ./mkwatch –b 192.168.0.255:9777
데모 컴파일과 실행[5] Finance.yahoo.com 서비스 변경 시 새로운 server 어드레스를 쓰기 위해 –a 이용 $ ./qserve –a new.server.com:80 & [1] 821 Port 번호 80 : 필수
Summary