#ifdef HAVE_THREADS #include "tcpsocket.h" #include <sys/time.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <arpa/inet.h> #include <netdb.h> bool TCPsocket::mksock( const char *name, bool nb ) { fd_ = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); if(fd_ < 0) { fprintf(stderr, "TCPsocket(\"%s\"): %s\n", name, strerror(errno)); return false; } nonblock( nb ); return true; } void TCPsocket::nonblock( bool nb ) { nonblock_ = nb; if(nonblock_) { fcntl( fd_, F_SETFL, fcntl( fd_, F_GETFL ) | O_NONBLOCK ); } else { fcntl( fd_, F_SETFL, fcntl( fd_, F_GETFL ) & ~O_NONBLOCK ); } } TCPsocket::~TCPsocket() { if(fd_ >= 0) close(fd_); } void TCPsocket::connectwith( const char *name, int port, bool nonblock ) { state_ = INVALID; fd_ = -1; const char *s = strchr(name, ':'); if(s == name) { listenwith( atoi(s+1), !nonblock, 1, nonblock ); return; } else if(s != NULL) { char *ts = new char[ s - name + 1 ]; memcpy( ts, s, s - name ); ts[s-name] = '\0'; connectwith( ts, atoi(s+1), nonblock ); delete ts; return; } if(port <= 0 || port >= 65536) { fprintf(stderr, "TCPsocket(): port %d out of range\n", port); return; } memset(&sin_, 0, sizeof(sin_)); sin_.sin_family = AF_INET; sin_.sin_port = htons(port); if(inet_aton( name, &sin_.sin_addr )) { /* ok */ } else { struct hostent *hp = gethostbyname( name ); if(hp == NULL) { fprintf(stderr, "TCPsocket(\"%s\") -- %s\n", name, hstrerror(h_errno)); return; } memcpy(&sin_.sin_addr, &hp->h_addr, sizeof(struct in_addr)); } if(!mksock( name, nonblock )) return; if(connect( fd_, (struct sockaddr *)&sin_, sizeof(sin_) ) == 0) { state_ = CONNECTED; } else if(errno == EAGAIN || errno == EINPROGRESS) { state_ = CONNECTING; } else { fprintf(stderr, "TCPsocket(\"%s\"[%s], %d): %s\n", name, inet_ntoa(sin_.sin_addr), port, strerror(errno)); state_ = INVALID; } } TCPsocket::TCPsocket( const char *name, int port, bool nonblock ) { connectwith( name, port, nonblock ); } TCPsocket::TCPsocket( int listenport, bool await_first_contact, int backlog, bool nonblock ) { listenwith( listenport, await_first_contact, backlog, nonblock ); } void TCPsocket::listenwith( int listenport, bool await_first_contact, int backlog, bool nonblock ) { state_ = INVALID; fd_ = -1; if(listenport <= 0 || listenport >= 65536) { fprintf(stderr, "TCPsocket(%d): port out of range\n", listenport); return; } memset(&sin_, 0, sizeof(sin_)); sin_.sin_family = AF_INET; sin_.sin_addr.s_addr = INADDR_ANY; sin_.sin_port = htons(listenport); if(!mksock( "(listen)", nonblock )) return; static int one = 1; if(setsockopt( fd_, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one) ) < 0) { fprintf(stderr, "TCPsocket(%d): setsockopt: %s\n", listenport, strerror(errno)); /* non-fatal */ } if(bind(fd_, (struct sockaddr *)&sin_, sizeof(sin_)) < 0) { fprintf(stderr, "TCPsocket(%d): bind: %s\n", listenport, strerror(errno)); return; } backlog_ = backlog && !await_first_contact; if(listen(fd_, backlog_) < 0) { fprintf(stderr, "TCPsocket(%d): listen: %s\n", listenport, strerror(errno)); return; } state_ = LISTENING; if(await_first_contact) { do_accept(); } } bool TCPsocket::do_accept() { socklen_t len = sizeof(sin_); int newfd; fcntl( fd_, F_SETFL, fcntl( fd_, F_GETFL ) & ~O_NONBLOCK ); do { newfd = accept( fd_, (struct sockaddr *)&sin_, &len ); } while(newfd < 0 && errno == EINTR); if(newfd < 0) { fprintf(stderr, "TCPsocket(%d, true): accept: %s\n", ntohs(sin_.sin_port), strerror(errno)); state_ = INVALID; return false; } close(fd_); fd_ = newfd; state_ = CONNECTED; nonblock( nonblock_ ); return true; } bool TCPsocket::be_connected() { switch(state_) { case INVALID: return false; case CONNECTED: return true; case CONNECTING: return await(); // wait until connection succeeds or fails case LISTENING: return do_accept(); default: return false; } } bool TCPsocket::ready() { if(!valid()) return false; fd_set fds; static struct timeval zero = { 0, 0 }; FD_ZERO(&fds); FD_SET( fd_, &fds ); return select( fd_+1, &fds, NULL, NULL, &zero ) > 0; } bool TCPsocket::await( int usecs ) { if(!valid()) return false; fd_set fds; FD_ZERO(&fds); FD_SET( fd_, &fds ); if(usecs < 0) { /* wait indefinitely */ return select( fd_+1, &fds, NULL, NULL, NULL ) > 0; } else { struct timeval awhile = { usecs / 1000000, usecs % 1000000 }; return select( fd_+1, &fds, NULL, NULL, &awhile ) > 0; } } TCPsocket::TCPsocket( TCPsocket & listener, bool nonblock ) { state_ = INVALID; fd_ = -1; if(!listener.valid()) return; socklen_t sl = sizeof(sin_); do { fd_ = accept( listener.fd(), (struct sockaddr *)&sin_, &sl ); } while(fd_ < 0 && errno == EINTR); if(fd_ < 0) { fprintf(stderr, "TCPsocket(listening(%d)): accept: %s\n", ntohs(listener.sin_.sin_port), strerror(errno)); return; } if(nonblock) fcntl( fd_, F_SETFL, fcntl( fd_, F_GETFL ) | O_NONBLOCK ); else fcntl( fd_, F_SETFL, fcntl( fd_, F_GETFL ) & ~O_NONBLOCK ); state_ = CONNECTED; } bool TCPsocket::open_ifstream( std::ifstream & ifs ) { if( ifs.is_open() ) { fprintf(stderr, "TCPsocket(%s)->open_ifstream: stream already open?\n", inet_ntoa(sin_.sin_addr)); return false; } if( ! be_connected() ) return false; char fdname[32]; sprintf(fdname, "/dev/fd/%d", fd()); // ugh. ifs.open( fdname, ifs.in | ifs.binary ); if(!ifs.is_open()) { fprintf(stderr, "TCPsocket(%s)->open_ifstream: can't open %s?\n", inet_ntoa(sin_.sin_addr), fdname); return false; } return true; } char *TCPsocket::fdname() { if(!be_connected()) return NULL; char fdname[32]; sprintf(fdname, "/dev/fd/%d", fd()); // ugh. char *result = new char[ strlen(fdname) + 1 ]; strcpy(result, fdname); return result; } #endif /*HAVE_THREADS*/