/*----------------------------------------------------------------------------- * socket.cpp * Copyright (C) 2004, 2005 Akito Nozaki * * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include NAMESPACE_TANOSHI { Socket::Socket(Object *parent) : ByteStream(parent), d(new SocketPrivate) { d->socket = 0; d->address.setAddress(0); d->localport = 0; d->dnslookup.setEventManager(eventManager()); d->dnslookup.resultReady.connect(sigc::mem_fun(this, &Socket::dns_done)); reset(); } Socket::~Socket() { reset(true); delete d; } void Socket::setEventManager(EventManager *e) { d->dnslookup.setEventManager(e); ByteStream::setEventManager(e); } void Socket::reset(bool clear) { if(d->socket) { if(!clear && d->state == Connected) { ByteArray block; block = read(bytesAvailable()); appendRead(block); } ::close(d->socket); d->socket = 0; setSafeToDelete(false); connectionClosed(); setSafeToDelete(true); } else { if(clear) clearReadBuffer(); } removeFromEvent(); if(d->dnslookup.isWaiting()) d->dnslookup.stop(); d->state = Idle; } void Socket::connectToHost(const std::string &host, unsigned int port) { reset(true); d->host = host; d->port = port; d->state = HostLookup; d->dnslookup.resolve(d->host); } int Socket::socket() const { return d->socket; } void Socket::setSocket(int s) { reset(true); d->state = Connected; d->socket = s; setFd(d->socket); setEvents(POLLIN); addToEvent(); } int Socket::state() const { return d->state; } bool Socket::isOpen() const { if(d->state == Connected) return true; else return false; } void Socket::close() { if(d->state == Idle) return; if(d->socket) { d->state = Closing; reset(); } else reset(); } void Socket::write(const ByteArray &a) { if(d->state != Connected) return; if(a.size()) { appendWrite(a); setEvents(POLLIN | POLLOUT); addToEvent(); } } void Socket::write(const std::string &a) { if(d->state != Connected) return; if(a.length()) { appendWrite(ByteArray(a.c_str(), a.length())); setEvents(POLLIN | POLLOUT); addToEvent(); } } HostAddress Socket::address() const { return d->address; } unsigned int Socket::port() const { if(d->socket) return d->port; else return 0; } void Socket::dns_done() { d->address = d->dnslookup.getResult(); if(d->address.isEmpty()) { error(ErrHostNotFound); return; } // ... I don't know if this should emit a signal... or if it does // it should be upto the client to connect. hostFound(); connect(); } void Socket::connect() { d->state = Connecting; int sockfd; if((sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) { reset(); error(ErrConnectionRefused); return; } d->socket = sockfd; struct sockaddr_in localaddr; memset(&(localaddr.sin_zero), 0, 8); localaddr.sin_family = AF_INET; localaddr.sin_port = htons(d->localport); localaddr.sin_addr.s_addr = htonl(d->localaddress.toIPv4Address()); if(::bind(d->socket, (struct sockaddr *)&localaddr, sizeof(localaddr)) == -1) { reset(); error(ErrConnectionRefused); return; } struct sockaddr_in peeraddr; memset(peeraddr.sin_zero, 0, 8); peeraddr.sin_family = AF_INET; peeraddr.sin_port = htons(d->port); peeraddr.sin_addr.s_addr = htonl(d->address.toIPv4Address()); int flags = fcntl(d->socket, F_GETFL, 0); fcntl(d->socket, F_SETFL, flags | O_NONBLOCK); int conn_ret = ::connect( d->socket, (struct sockaddr *)&peeraddr, sizeof(peeraddr)); if(conn_ret == -1 && errno != EINPROGRESS) { reset(); error(ErrConnectionRefused); return; } setFd(d->socket); if(conn_ret == -1) { // if its waiting to connect we only want to check for out // going connection. setEvents(POLLOUT); addToEvent(); setSleep(-1); } else if(conn_ret == 0) { setEvents(POLLIN); addToEvent(); setSleep(-1); d->state = Connected; connected(); } } void Socket::finishConnecting() { if(d->state != Connecting) return; // I'm not sure what exactly this is, but I get HUP when th // connection is refused. if((revents() & POLLHUP) && !(revents() & POLLOUT)) { reset(); error(ErrConnectionRefused); return; } if(!(revents() & POLLOUT)) return; unsigned int error; unsigned int len = sizeof(error); int ret = getsockopt(d->socket, SOL_SOCKET, SO_ERROR, &error, &len); if(!ret) { removeFromEvent(); setEvents(POLLIN); addToEvent(); d->state = Connected; connected(); } } void Socket::setHostAddress(const HostAddress &hostaddr) { d->address = hostaddr; } void Socket::setPort(unsigned int port) { d->port = port; } void Socket::setLocalAddress(const HostAddress &hostaddr) { d->localaddress = hostaddr; } HostAddress Socket::localAddress() const { return d->localaddress; } void Socket::setLocalPort(unsigned short port) { d->localport = port; } unsigned short Socket::localPort() const { return d->port; } void Socket::sn_read() { char buffer[100000]; int t = ::recv(d->socket, buffer, 100000, 0); if(t == 0) { reset(); return; } else if(t > 0) { appendRead(ByteArray(buffer, t)); readyRead(); } } void Socket::sn_write() { if(d->state != Connected && d->state != Closing) return; int written = 0; written = ::send(d->socket, readBuf().data(), readBuf().size(), 0); if(written == -1) { error(ErrSocketWrite); return; } takeWrite(written, true); if(!bytesToWrite()) { setEvents(POLLIN); addToEvent(); } bytesWritten(written); if(d->state == Closing && !bytesToWrite()) { reset(); removeFromEvent(); connectionClosed(); } } bool Socket::event(Event *e) { if(e->id() != id()) return false; setSafeToDelete(false); if(d->state == Connecting) { finishConnecting(); } else if(d->state != Idle) { if(revents() & POLLOUT) { sn_write(); } if(revents() & POLLIN) sn_read(); setSleep(-1); } setSafeToDelete(true); return true; } }