tcpsocket.cc 5.41 KiB
#include "config.h"
#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>
#include <unistd.h>
#include <ext/stdio_filebuf.h>
using namespace std;
static const char *serr() {
return strerror(errno);
}
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, serr());
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) {
const char *s = hstrerror(h_errno);
fprintf(stderr, "TCPsocket(\"%s\") -- %s\n", name, s);
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, serr());
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, serr());
/* non-fatal */
}
if(bind(fd_, (struct sockaddr *)&sin_, sizeof(sin_)) < 0) {
fprintf(stderr, "TCPsocket(%d): bind: %s\n", listenport, serr());
return;
}
backlog_ = backlog && !await_first_contact;
if(listen(fd_, backlog_) < 0) {
fprintf(stderr, "TCPsocket(%d): listen: %s\n", listenport, serr());
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), serr());
state_ = INVALID;
return false;
}
close(fd_);
fd_ = newfd;
state_ = CONNECTED;
nonblock( nonblock_ );
return true;
}
bool TCPsocket::be_connected()
{
nonblock( false );
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), serr());
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;
}
#endif /*HAVE_THREADS*/