Add support for unix sockets to Tairon::Net::Socket.
[tairon.git] / src / net-core / socket.cpp
blob9b7663876350c66fe2e974348fe2b67e1feb0fa3
1 /***************************************************************************
2 * *
3 * Copyright (C) 2006 David Brodsky *
4 * *
5 * This library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Library General Public *
7 * License as published by the Free Software Foundation and appearing *
8 * in the file LICENSE.LGPL included in the packaging of this file. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13 * Library General Public License for more details. *
14 * *
15 ***************************************************************************/
17 #include <sys/un.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <netdb.h>
22 #include <tairon/core/log.h>
24 #include "socket.h"
26 #include "deleter.h"
27 #include "poll.h"
28 #include "socketnotifier.h"
30 namespace Tairon
33 namespace Net
36 const size_t Socket::bufLength = 65536;
38 /* {{{ Socket::Socket(Family, Type) */
39 Socket::Socket(Family family, Type type)
41 fd = socket(family, type, 0);
42 if (fd == -1)
43 throw SocketException("Cannot create socket", errno);
45 init();
47 /* }}} */
49 /* {{{ Socket::Socket(int sd) */
50 Socket::Socket(int sd) : fd(sd)
52 if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1)
53 throw SocketException("Cannot set nonblocking socket", errno);
55 init();
57 notifier->setReadyReadFunctor(Tairon::Core::methodFunctor(this, &Socket::readyRead));
58 notifier->setReadyWriteFunctor(Tairon::Core::methodFunctor(this, &Socket::readyWrite));
60 /* }}} */
62 /* {{{ Socket::~Socket() */
63 Socket::~Socket()
65 // deleting notifier also deletes all created functors
66 delete notifier;
67 delete [] inBuffer;
69 /* }}} */
71 /* {{{ Socket::close() */
72 void Socket::close()
74 connectedSignal.disable();
75 errorSignal.disable();
76 readyReadSignal.disable();
77 readyWriteSignal.disable();
79 // this also removes socket from polling
80 ::close(fd);
82 Poll::self()->addDeleter(createDeleter(this));
84 /* }}} */
86 /* {{{ Socket::connected() */
87 void Socket::connected()
89 DEBUG((const char *) String("Socket::connected " + String::number(fd)));
91 Poll::self()->unregisterSocket(fd);
92 delete notifier->getReadyWriteFunctor();
93 notifier->setReadyWriteFunctor(Tairon::Core::methodFunctor(this, &Socket::readyWrite));
95 int err;
96 socklen_t len = sizeof(err);
97 getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
98 if (err == 0) {
99 notifier->setReadyReadFunctor(Tairon::Core::methodFunctor(this, &Socket::readyRead));
100 // don't register socket for reading, wait for the ready method
101 connectedSignal.emit(this);
102 } else
103 errorSignal.emit(this, err);
105 /* }}} */
107 /* {{{ Socket::connect(const String &) */
108 void Socket::connect(const String &path)
110 if (path.length() >= 108) // length of struct socaddr_un.sun_path field
111 throw SocketException("Path for unix socket is too long", 0);
113 if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1)
114 throw SocketException("Cannot set nonblocking socket", errno);
116 sockaddr_un addr;
117 addr.sun_family = AF_UNIX;
118 memcpy(addr.sun_path, path.data(), path.length());
119 addr.sun_path[path.length()] = 0;
121 int ret = ::connect(fd, (sockaddr *) &addr, sizeof(addr));
122 if ((ret == -1) && (errno != EINPROGRESS))
123 throw SocketException("Error while connecting", errno);
125 notifier->setReadyWriteFunctor(Tairon::Core::methodFunctor(this, &Socket::connected));
127 Poll::self()->registerSocketToWrite(fd, notifier);
129 /* }}} */
131 /* {{{ Socket::connect(const String &, uint16_t) */
132 void Socket::connect(const String &address, uint16_t port)
134 if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1)
135 throw SocketException("Cannot set nonblocking socket", errno);
137 struct hostent *host = gethostbyname(address);
138 if (!host)
139 throw SocketException("Cannot resolve host name", h_errno);
141 if (host->h_addrtype != AF_INET)
142 throw SocketException("Invalid address type", host->h_addrtype);
144 if (!(*host->h_addr_list))
145 throw SocketException("Zero length list of addresses", 0);
147 sockaddr_in addr;
148 addr.sin_family = AF_INET;
149 addr.sin_port = htons(port);
150 memcpy(&(addr.sin_addr.s_addr), *(host->h_addr_list), sizeof(addr.sin_addr.s_addr));
152 int ret = ::connect(fd, (sockaddr *) &addr, sizeof(addr));
153 if ((ret == -1) && (errno != EINPROGRESS))
154 throw SocketException("Error while connecting", errno);
156 notifier->setReadyWriteFunctor(Tairon::Core::methodFunctor(this, &Socket::connected));
158 Poll::self()->registerSocketToWrite(fd, notifier);
160 /* }}} */
162 /* {{{ Socket::error() */
163 void Socket::error()
165 int err;
166 socklen_t len = sizeof(err);
167 getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
168 Poll::self()->unregisterSocket(fd);
169 errorSignal.emit(this, err);
171 /* }}} */
173 /* {{{ Socket::init() */
174 void Socket::init()
176 inBuffer = new char[bufLength];
177 notifier = new SocketNotifier(0, 0, Tairon::Core::methodFunctor(this, &Socket::error));
179 /* }}} */
181 /* {{{ Socket::read(size_t) */
182 String Socket::read(size_t max)
184 ssize_t len = recv(fd, inBuffer, bufLength < max ? bufLength : max, MSG_DONTWAIT);
185 if (len == 0) // connection has been closed
186 throw SocketException("Connection has been closed", 0);
187 else if (len < 0)
188 if (errno != EAGAIN) {
189 throw SocketException("Error while reading from socket", errno);
190 } else
191 len = 0;
192 try {
193 Poll::self()->addSocketToRead(fd, notifier);
194 } catch (const PollException &) { // do nothing
196 return String(inBuffer, len);
198 /* }}} */
200 /* {{{ Socket::readyRead() */
201 void Socket::readyRead()
203 DEBUG((const char *) String("Socket::readyRead " + String::number((int) this)));
205 // don't make signal storm - if processing of the signals takes much time
206 // in another thread, then there can be new data and many new signals would
207 // be emitted
208 try {
209 Poll::self()->delSocketToRead(fd, notifier);
210 } catch (const PollException &) {
212 readyReadSignal.emit(this);
214 /* }}} */
216 /* {{{ Socket::readTo(void *, size_t) */
217 size_t Socket::readTo(void *buf, size_t length)
219 ssize_t len = recv(fd, buf, length, MSG_DONTWAIT);
220 if (len == 0) // connection has been closed
221 throw SocketException("Connection has been closed", 0);
222 else if (len < 0)
223 if (errno != EAGAIN)
224 throw SocketException("Error while reading from socket", errno);
225 else
226 len = 0;
227 try {
228 Poll::self()->addSocketToRead(fd, notifier);
229 } catch (const PollException &) { // do nothing
231 return len;
233 /* }}} */
235 /* {{{ Socket::ready() */
236 void Socket::ready()
238 Poll::self()->registerSocketToRead(fd, notifier);
240 /* }}} */
242 /* {{{ Socket::readyWrite() */
243 void Socket::readyWrite()
245 Poll::self()->delSocketToWrite(fd, notifier);
246 readyWriteSignal.emit(this);
248 /* }}} */
250 /* {{{ Socket::write(const String &, bool) */
251 size_t Socket::write(const String &data, bool block)
253 return write(data.data(), data.length(), block);
255 /* }}} */
257 /* {{{ Socket::write(const char *, size_t, bool) */
258 size_t Socket::write(const char *data, size_t length, bool block)
260 ssize_t sent = send(fd, data, length, block ? 0 : MSG_DONTWAIT);
261 if (sent == -1) {
262 if (errno != EAGAIN)
263 throw SocketException("Error while writing to socket", errno);
264 try {
265 Poll::self()->addSocketToWrite(fd, notifier);
266 } catch (const PollException &) { // do nothing
268 return 0;
270 return sent;
272 /* }}} */
275 /* {{{ SocketException::SocketException(const String &, int) */
276 SocketException::SocketException(const String &desc, int err) : Tairon::Core::Exception(desc), errorNumber(err)
278 DEBUG((const char *) String(desc + " (" + String::number(errorNumber) + ")"));
280 /* }}} */
286 // vim: ai sw=4 ts=4 noet fdm=marker