/* net6 - Library providing IPv4/IPv6 network access * Copyright (C) 2005, 2006 Armin Burgmeier / 0x539 dev group * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #ifdef HAVE_SYS_SELECT_H # include #endif #include "error.hpp" #include "select.hpp" namespace { inline unsigned long time() { static std::time_t begin = 0; if(begin == 0) begin = std::time(NULL); return (std::time(NULL) - begin) * 1000; } // TODO: Subsecond time resolution... unsigned long msec() { static unsigned long(*time_func)() = NULL; if(time_func == NULL) time_func = &time; return time_func(); } unsigned long time_elapsed(unsigned long since, unsigned long now) { if(since <= now) return now - since; throw std::logic_error( "net6::select.cpp::time_elapsed:\n" "Time overflow. Panic!" ); } } net6::io_condition net6::selector::get(const socket& sock) const { map_type::const_iterator iter = sock_map.find(&sock); if(iter == sock_map.end() ) return IO_NONE; return iter->second.condition; } void net6::selector::set(const socket& sock, io_condition condition) { map_type::iterator iter = sock_map.find(&sock); if(condition != IO_NONE) { if(iter == sock_map.end() ) { selected_type& type = sock_map[&sock]; type.condition = condition; type.timeout_begin = 0; type.timeout = 0; } else { iter->second.condition = condition; // Cancel running timeout if IO_TIMEOUT is not set if( (iter->second.condition & IO_TIMEOUT) == IO_NONE) { iter->second.timeout_begin = 0; iter->second.timeout = 0; } } } else { // Remove socket if(iter != sock_map.end() ) sock_map.erase(iter); } } void net6::selector::set_timeout(const socket& sock, unsigned long timeout) { selected_type* sel_type = NULL; map_type::iterator iter = sock_map.find(&sock); if(iter != sock_map.end() ) { if( (iter->second.condition & IO_TIMEOUT) == IO_TIMEOUT) sel_type = &iter->second; } if(sel_type == NULL) { throw std::logic_error( "net6::selector::set_timeout:\n" "Socket is not selected for IO_TIMEOUT" ); } sel_type->timeout_begin = msec(); sel_type->timeout = timeout; } unsigned long net6::selector::get_timeout(const socket& sock) { map_type::const_iterator iter = sock_map.find(&sock); if(iter == sock_map.end() ) return 0; // No timeout set if(iter->second.timeout == 0) return 0; unsigned long elapsed = time_elapsed( iter->second.timeout_begin, msec() ); // Timeout should already have been elapsed... if(elapsed >= iter->second.timeout) return 1; return iter->second.timeout - elapsed; } void net6::selector::select() { select_impl(NULL); } void net6::selector::select(unsigned long timeout) { timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; select_impl(&tv); } void net6::selector::run() { running = true; while(running) select(); } void net6::selector::quit() { running = false; } void net6::selector::select_impl(timeval* tv) { socket::socket_type max_fd = 0; fd_set readfs, writefs, errorfs; unsigned long now = msec(); unsigned long timeout = std::numeric_limits::max(); // Determinate the highest file descriptor number for select() and // the first timeout to be elapsed. FD_ZERO(&readfs); FD_ZERO(&writefs); FD_ZERO(&errorfs); for(map_type::const_iterator iter = sock_map.begin(); iter != sock_map.end(); ++ iter) { max_fd = std::max(iter->first->cobj(), max_fd); if(iter->second.condition & IO_INCOMING) FD_SET(iter->first->cobj(), &readfs); if(iter->second.condition & IO_OUTGOING) FD_SET(iter->first->cobj(), &writefs); if(iter->second.condition & IO_ERROR) FD_SET(iter->first->cobj(), &errorfs); // Can only be set if IO_TIMEOUT is set in conditions if( (iter->second.timeout > 0) && (timeout > 0) ) { unsigned long elapsed = time_elapsed( iter->second.timeout_begin, now ); // Should already have been elapsed if(elapsed > iter->second.timeout) timeout = 0; // Next timeout? if(timeout > iter->second.timeout - elapsed) timeout = iter->second.timeout - elapsed; } } // Given timeout if(tv != NULL) { if(timeout > (tv->tv_sec * 1000ul + tv->tv_usec / 1000ul) ) timeout = (tv->tv_sec * 1000ul + tv->tv_usec / 1000ul); } timeval val; if(timeout < std::numeric_limits::max() ) { val.tv_sec = timeout / 1000; val.tv_usec = (timeout % 1000) * 1000; tv = &val; } if(::select(max_fd + 1, &readfs, &writefs, &errorfs, tv) == -1) throw error(net6::error::SYSTEM); now = msec(); // We pack all affected sockets into another map that is used during // execution of the event handlers. This allows that event handlers may // modify the selector's map (by performing calls to add() or remove()) // without invalidating iterators of the select() routine here. std::map temp_map; for(map_type::iterator iter = sock_map.begin(); iter != sock_map.end(); ++ iter) { const socket* sock = iter->first; io_condition conds = IO_NONE; if(FD_ISSET(iter->first->cobj(), &readfs) ) conds |= IO_INCOMING; if(FD_ISSET(iter->first->cobj(), &writefs) ) conds |= IO_OUTGOING; if(FD_ISSET(iter->first->cobj(), &errorfs) ) conds |= IO_ERROR; if(iter->second.timeout > 0) { if(time_elapsed(iter->second.timeout_begin, now) >= iter->second.timeout) { conds |= IO_TIMEOUT; // Timeout has elapsed, unset iter->second.condition &= ~IO_TIMEOUT; iter->second.timeout_begin = 0; iter->second.timeout = 0; if(iter->second.condition == IO_NONE) sock_map.erase(iter); } } if(conds != IO_NONE) temp_map[sock] = conds; } for(std::map::const_iterator iter = temp_map.begin(); iter != temp_map.end(); ++ iter) { // Socket has been removed from the selector by the execution // of a previous signal handler. if(sock_map.find(iter->first) == sock_map.end() ) continue; iter->first->io_event().emit(iter->second); } }