Skip to content
Snippets Groups Projects
tcpsocket.cc 6.03 KiB
Newer Older
  • Learn to ignore specific revisions
  • #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*/