/** @file pollmng.c @date 2009/3/19 @author 오재경 freefrug@falinux.com FALinux.Co.,Ltd. @brief poll 을 관리한다. @modify 2009-05-07 (오재경) 파일하나만을 폴로 돌리는 함수를 추가 2009-05-20 (오재경) tag 로써 poll_obj 객체를 얻는 함수 추가 2009-10-09 (오재경) poll_do_loop() 함수의 재귀호출 회수를 제한하도록 수정 2010-01-04 (오재경) poll_obj_t 구조체에서 poll_ndx 멤버변수 제거 poll_delete() 함수에서 tlist_delete() 함수대신 tlist_remove()함수로 수정 2010-03-19 (오재경) poll_obj_t 구조체에서 on_disconnect 멤버 추가 tcp 일 경우 사용됨 2010-08-18 (장길석) mingw와 함께 사용할 수 있는 코드 추가 2014-09-03 (김민수) tty가 아닌 stdin이 POLL_IN으로 등록될 경우 항상 readable하기 때문에 CPU 점유율이 100%가 되는 문제가 있어서 stdin이 tty가 아닐 경우 이벤트를 등록하지 않는다. @todo @bug @remark @warning */ // // 저작권 에프에이리눅스(주) // 외부공개 금지 // //---------------------------------------------------------------------------- #define EMBEDDED_LINUX // 이렇게 처리하지 않으면 EClipse에서 C 영역이 회색 바탕이 됨 #ifdef MS_WIN32 #undef EMBEDDED_LINUX #endif #include #include #include #include #include #include #include #include #ifdef EMBEDDED_LINUX #include #include #else #include #include #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #include #include char desc_pollmng[] = "falinux pollmng ver 0.2.4"; /// 폴 관리 변수 static struct pollfd poll_array[POLL_MAX_COUNT]; static tlist *poll_list = NULL; static int cnt_recursive = 0; // poll_do_loop()가 완료 되기 저에 poll_do_loop()가 호축되는 카운터 static int is_need_rebuild = TRUE; // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부 static int is_loop_break = FALSE; // on_poll_xx 콜백함수에 의해 객체의 리스트가 변경되었다면 // 다음 폴이벤트를 수행하지 않고 나가기 위한 변수 // TCP 서버를 위해 필요하다. #ifdef MS_WIN32 /* Type used for the number of file descriptors. */ typedef unsigned long int nfds_t; static int poll( struct pollfd *a_fds, nfds_t a_nfds, int a_timeout){ struct pollfd *p_fds; FD_SET rset; //jwjw 이후 체크 DWORD dwMask; int cnt_rs = 0; int cnt_sock = 0; int is_socket_exists = FALSE; int ndx; // 먼저 시리얼 부터 확인한다. p_fds = a_fds; for ( ndx = 0; ndx < a_nfds; ndx++){ p_fds->revents = 0; if ( p_fds->is_serial){ DWORD dwBytesRead, dwErrorFlags; COMSTAT comstat; ClearCommError( p_fds->fd, &dwErrorFlags, &comstat); dwBytesRead = comstat.cbInQue; // input queue에 들어와 있는 데이터의 길이 if ( 0 < dwBytesRead){ p_fds->revents = POLLIN; cnt_rs++; } else { p_fds->revents = 0; } // if ( WaitCommEvent( p_fds->fd, &dwMask, NULL)){ // p_fds->revents = POLLIN; // cnt_rs++; // } else { // p_fds->revents = 0; // } } else { is_socket_exists = TRUE; } p_fds++; } if ( !is_socket_exists){ usleep( 1000); return cnt_rs; } // 소켓 쪽에 이벤트가 발생했는지 확인한다. p_fds = a_fds; FD_ZERO( &rset); for( ndx = 0; ndx < a_nfds; ndx++ ){ if ( !p_fds->is_serial){ if ( -1 != *(SOCKET *)p_fds->fd ){ FD_SET( *(SOCKET *)p_fds->fd, &rset ); } } p_fds++; } struct timeval sttTimeout; sttTimeout.tv_sec = a_timeout / 1000; sttTimeout.tv_usec = ( a_timeout % 1000 ) * 1000; cnt_sock = select( 0, &rset, NULL, NULL, &sttTimeout ); if( 0 > cnt_sock ){ // 에러 상황이므로 반환 return -1; } else { p_fds = a_fds; cnt_sock = 0; for ( ndx = 0; ndx < a_nfds; ndx++){ if ( !p_fds->is_serial){ if ( FD_ISSET( *(SOCKET *)p_fds->fd, &rset)){ p_fds->revents = POLLIN; cnt_sock++; } } p_fds++; } } return cnt_rs + cnt_sock; } #endif //------------------------------------------------------------------------------ /** @brief poll 관리 객체를 생성한다. @remark *///---------------------------------------------------------------------------- void poll_init( void ) { poll_list = tlist_create(); is_need_rebuild = TRUE; // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부 is_loop_break = FALSE; // on_poll_xx 콜백함수에 의해 객체의 리스트가 변경되었다면 #ifdef MS_WIN32 WSADATA wsaData; if ( 0 != WSAStartup( MAKEWORD( 2, 2), &wsaData)){ perror( "WSAStartup() error!!" ); exit( 1); } #endif } //------------------------------------------------------------------------------ /** @brief poll 관리 객체를 해제한다. @remark *///---------------------------------------------------------------------------- void poll_exit( void ) { poll_obj_t *obj; int ndx; for ( ndx = 0; ndx < poll_list->fcount ; ndx++ ) { obj = tlist_get( poll_list, ndx ); if ( 0 <= obj->fd ) { #ifdef EMBEDDED_LINUX close( obj->fd ); #else CloseHandle( obj->fd); #endif } free( (void *)obj ); } tlist_free( poll_list ); } //------------------------------------------------------------------------------ /** @brief 리스트로 관리되는 파일디스크립터들을 폴배열에 재구성한다. @remark 폴로 관리되는 객체의 이벤트함수를 등록한후 반드시 호출하여야 한다. *///---------------------------------------------------------------------------- void poll_rebuild( void ){ poll_obj_t *obj; int ndx; memset( poll_array, 0, sizeof(poll_array) ); for (ndx= 0; ndx < poll_list->fcount; ndx++) { int events; obj = (poll_obj_t *)tlist_get( poll_list, ndx ); if ( obj->fd == fileno(stdin) && isatty(fileno(stdin)) == 0 ) { continue; } events = 0; if ( obj->on_poll_in ) events |= POLLIN; if ( obj->on_poll_out ) events |= POLLOUT; if ( obj->on_poll_err ) events |= POLLERR; if ( obj->on_poll_hup ) events |= POLLHUP; poll_array[ndx].fd = obj->fd; poll_array[ndx].events = events; #ifdef MS_WIN32 poll_array[ndx].is_serial = obj->is_serial; #endif } is_loop_break = TRUE; is_need_rebuild = FALSE; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에 열려진 파일 디스크립터를 등록한다. @param fd 열린 파일 디스크립터 @return poll_obj_t 형식의 포인터 @remark 이벤트함수( on_poll_in, on_poll_out )를 등록한후 poll_rebuild() 함수를 반드시 호출한다. *///---------------------------------------------------------------------------- poll_obj_t *poll_add( fd_t fd ) { poll_obj_t *obj; int idx; if ( NULL == poll_list ) { printf( "error need to call poll_init()\n" ); return NULL; } obj = (poll_obj_t *)malloc( sizeof(poll_obj_t) ); memset( (void *)obj, 0, sizeof(poll_obj_t) ); obj->fd = fd; obj->type = STYP_FILE; obj->tag = 0; obj->on_poll_in = NULL; obj->on_poll_out = NULL; obj->on_poll_err = NULL; obj->on_poll_hup = NULL; obj->on_timeout = NULL; obj->on_disconnect = NULL; obj->priv = NULL; obj->user = NULL; #ifdef MS_WIN32 obj->is_serial = FALSE; #endif idx = tlist_add( poll_list, (void *)obj ); if ( 0 <= idx ) { is_need_rebuild = TRUE; return obj; } else { free( (void *)obj ); return NULL; } } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 개개의 폴구조체 포인터를 얻는다. @param idx 인덱스 @return poll_obj_t 형식의 포인터 *///---------------------------------------------------------------------------- poll_obj_t *poll_get_obj( int idx ) { if ( ( 0 <= idx ) && ( idx < poll_list->fcount ) ) { return (poll_obj_t *)tlist_get( poll_list, idx ); } return NULL; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 사용자 포인터를 얻는다. @param idx 인덱스 @return void 형식의 포인터 *///---------------------------------------------------------------------------- void *poll_get_priv( int idx ) { poll_obj_t *obj; if ( ( 0 <= idx ) && ( idx < poll_list->fcount ) ) { obj = (poll_obj_t *)tlist_get( poll_list, idx ); return obj->priv; } return NULL; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체가 관리하는 파일의 개수 @return 관리하는 파일의 개수 *///---------------------------------------------------------------------------- int poll_count( void ) { return poll_list->fcount; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 파일디스크립트로 관리하는 폴객체를 반환한다. @param fd 파일 디스크립터 @return poll_obj_t 형식의 포인터 *///---------------------------------------------------------------------------- poll_obj_t *poll_obj_byfd( fd_t fd ) { poll_obj_t *obj; int idx; for( idx=0; idxfcount; idx++ ) { obj = tlist_get( poll_list, idx ); if ( obj->fd == fd ) { return obj; } } return NULL; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 객체의 포인터로 삭제한다. @param obj poll_obj_t 형식의 포인터 @remark 관리하는 파일도 close 된다. poll_rebuild() 함수가 내부에서 호출되므로 외부에서는 호출하지 않는다. *///---------------------------------------------------------------------------- void poll_delete( poll_obj_t *obj ) { fd_t fd; fd = obj->fd; free( (void *)obj ); // 사용한 메모리를 해제한다. // 관리하는 파일을 닫는다. #ifdef EMBEDDED_LINUX close( fd ); #else CloseHandle( fd ); #endif tlist_remove( poll_list, obj ); poll_rebuild(); // 폴을 재구성한다. } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 파일디스크립터로 삭제한다. @param fd 파일 디스크립터 @remark 관리하는 파일도 close 된다. poll_rebuild() 함수가 내부에서 호출되므로 외부에서는 호출하지 않는다. *///---------------------------------------------------------------------------- void poll_delete_byfd( fd_t fd ) { poll_obj_t *obj; obj = poll_obj_byfd( fd ); if ( obj ) { poll_delete( obj ); } } //------------------------------------------------------------------------------ /** @brief poll 이벤트 메인 루프함수 @param time_out msec 단위의 타임아웃 @return POLL_ASYNC_ERR 시그널에 의해 중지되었다. POLL_TIME_OUT 대기 시간이 종료되었다. POLL_EVENTED 이벤트가 정상 처리되었다. @remark 외부 루프에서 계속호출한다. *///---------------------------------------------------------------------------- int poll_do_loop( int time_out ) { int fd_cnt, event_cnt; int ndx, rtnval = POLL_EVENTED; // 재귀호출의 회수를 제한한다. if ( POLL_RECURSIVE_COUNT < cnt_recursive ) { printf( "fatal error : poll_do_loop() recursive limit\n" ); return POLL_RECURSIVE_LIMIT_ERR; } cnt_recursive++; if ( is_need_rebuild ){ // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부 poll_rebuild(); } // 이벤트가 발생할때까지 대기상태로 놓인다. fd_cnt = poll_list->fcount; event_cnt = poll( (struct pollfd *)&poll_array, fd_cnt, time_out ); if ( 0 > event_cnt ) { rtnval = POLL_ASYNC_ERR; goto lable_poll_do_loop_end; } // timeout 이 발생하면 등록된 함수를 호출한다. if ( 0 == event_cnt ) { for ( ndx=0; ndx < fd_cnt; ndx++ ) { poll_obj_t *obj; obj = tlist_get( poll_list, ndx ); if ( obj->on_timeout ) { obj->on_timeout( obj ); } } rtnval = POLL_TIME_OUT; goto lable_poll_do_loop_end; } is_loop_break = FALSE; // 콜백함수를 호출하기 전에 변수를 초기화한다. // 콜백함수내에서 poll_rebuild() 함수가 호출되면 값이 TRUE 로 변한다. for ( ndx=0; ndxon_poll_in ) ) { obj->on_poll_in( obj ); event_cnt --; } } // 출력이벤트 -------------------------------------- if( poll_array[ndx].revents & POLLOUT ) { obj = tlist_get( poll_list, ndx ); if ( ( obj ) && ( obj->on_poll_out ) ) { obj->on_poll_out( obj ); event_cnt --; } } // HUP이벤트 -------------------------------------- if( poll_array[ndx].revents & POLLHUP ) { obj = tlist_get( poll_list, ndx ); if ( ( obj ) && ( obj->on_poll_hup ) ) { obj->on_poll_hup( obj ); event_cnt --; } } // 에러이벤트 -------------------------------------- if( poll_array[ndx].revents & POLLERR ) { obj = tlist_get( poll_list, ndx ); if ( ( obj ) && ( obj->on_poll_err ) ) { obj->on_poll_err( obj ); event_cnt --; } } if ( 0 >= event_cnt ) break; if ( is_loop_break ) break; // 이벤트 처리 중 poll에 등록한 객체에 변화가 있다면( poll_rebuild()를 실행) 분기한다. } lable_poll_do_loop_end: cnt_recursive --; return rtnval; } //------------------------------------------------------------------------------ /** @brief poll 이벤트를 하나의 파일핸들로만 돌린다. @param fd 파일핸들 @param event POLLIN, POLLOUT 값을 넣는다. @param time_out msec 단위의 타임아웃 @return POLL_ASYNC_ERR 시그널에 의해 중지되었다. POLL_TIME_OUT 대기 시간이 종료되었다. POLL_EVENTED 이벤트가 발생하였다. *///---------------------------------------------------------------------------- int poll_do_one( fd_t fd, int event, int time_out ) { int event_cnt; struct pollfd poll_one[4]; poll_one[0].fd = fd; poll_one[0].revents = 0; poll_one[0].events = event; // 이벤트가 발생할때까지 대기상태로 놓인다. event_cnt = poll( (struct pollfd *)&poll_one, 1, time_out ); if ( 0 > event_cnt ) { return POLL_ASYNC_ERR; } if ( 0 == event_cnt ) { return POLL_TIME_OUT; } return POLL_EVENTED; } //------------------------------------------------------------------------------ /** @brief poll 관리 객체에서 tag 번호로 찾아 폴객체를 반환한다. @param tag 태그번호 @return poll_obj_t 형식의 포인터 *///---------------------------------------------------------------------------- poll_obj_t *poll_obj_bytag( int tag ) { poll_obj_t *obj; int idx; for( idx=0; idxfcount; idx++ ) { obj = tlist_get( poll_list, idx ); if ( obj->tag == tag ) { return obj; } } return NULL; }