view C++/modules/Socket/SocketTcp.cpp @ 346:d235553e47a9

Socket: - SocketListener is now implemented as a template and use the backend as the template parameter. No more virtual implementation, - Improve backend detection and set the default backend implementation automatically, - Rename Socket::init to Socket::initialize for non-Windows systems, - Rename SocketListener::(select|selectMultiple) to wait|waitMultiple - Coming up soon: kqueue and epoll backends.
author David Demelier <markand@malikania.fr>
date Thu, 02 Apr 2015 17:32:51 +0200
parents 486767e1d165
children 5a1ec6603230
line wrap: on
line source

/*
 * SocketTcp.cpp -- portable C++ socket wrappers
 *
 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "SocketAddress.h"
#include "SocketListener.h"
#include "SocketTcp.h"

/* --------------------------------------------------------
 * SocketAbstractTcp
 * -------------------------------------------------------- */

void SocketAbstractTcp::listen(int max)
{
	if (::listen(m_handle, max) == Error) {
		throw SocketError(SocketError::System, "listen");
	}
}

Socket SocketAbstractTcp::standardAccept(SocketAddress &info)
{
	Socket::Handle handle;

	// Store the information
	sockaddr_storage address;
	socklen_t addrlen;

	addrlen = sizeof (sockaddr_storage);
	handle = ::accept(m_handle, reinterpret_cast<sockaddr *>(&address), &addrlen);

	if (handle == Invalid) {
#if defined(_WIN32)
		int error = WSAGetLastError();

		if (error == WSAEWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockRead, "accept", error);
		}

		throw SocketError(SocketError::System, "accept", error);
#else
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockRead, "accept");
		}

		throw SocketError(SocketError::System, "accept");
#endif
	}

	info = SocketAddress(address, addrlen);

	return Socket(handle);
}

void SocketAbstractTcp::standardConnect(const SocketAddress &address)
{
	if (m_state == SocketState::Connected) {
		return;
	}

	auto &sa = address.address();
	auto addrlen = address.length();

	if (::connect(m_handle, reinterpret_cast<const sockaddr *>(&sa), addrlen) == Error) {
		/*
		 * Determine if the error comes from a non-blocking connect that cannot be
		 * accomplished yet.
		 */
#if defined(_WIN32)
		int error = WSAGetLastError();

		if (error == WSAEWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockWrite, "connect", error);
		}

		throw SocketError(SocketError::System, "connect", error);
#else
		if (errno == EINPROGRESS) {
			throw SocketError(SocketError::WouldBlockWrite, "connect");
		}

		throw SocketError(SocketError::System, "connect");
#endif
	}

	m_state = SocketState::Connected;
}

/* --------------------------------------------------------
 * SocketTcp
 * -------------------------------------------------------- */

SocketTcp SocketTcp::accept()
{
	SocketAddress dummy;

	return accept(dummy);
}

SocketTcp SocketTcp::accept(SocketAddress &info)
{
	return standardAccept(info);
}

void SocketTcp::connect(const SocketAddress &address)
{
	return standardConnect(address);
}

void SocketTcp::waitConnect(const SocketAddress &address, int timeout)
{
	if (m_state == SocketState::Connected) {
		return;
	}

	// Initial try
	try {
		connect(address);
	} catch (const SocketError &ex) {
		if (ex.code() == SocketError::WouldBlockWrite) {
			SocketListener listener{{*this, SocketListener::Write}};

			listener.wait(timeout);

			// Socket is writable? Check if there is an error

			int error = get<int>(SOL_SOCKET, SO_ERROR);

			if (error) {
				throw SocketError(SocketError::System, "connect", error);
			}
		} else {
			throw;
		}
	}

	m_state = SocketState::Connected;
}

SocketTcp SocketTcp::waitAccept(int timeout)
{
	SocketAddress dummy;

	return waitAccept(dummy, timeout);
}

SocketTcp SocketTcp::waitAccept(SocketAddress &info, int timeout)
{
	SocketListener listener{{*this, SocketListener::Read}};

	listener.wait(timeout);

	return accept(info);
}

unsigned SocketTcp::recv(void *data, unsigned dataLen)
{
	int nbread;

	nbread = ::recv(m_handle, (Socket::Arg)data, dataLen, 0);
	if (nbread == Error) {
#if defined(_WIN32)
		int error = WSAGetLastError();

		if (error == WSAEWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockRead, "recv", error);
		}

		throw SocketError(SocketError::System, "recv", error);
#else
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockRead, "recv");
		}

		throw SocketError(SocketError::System, "recv");
#endif
	} else if (nbread == 0) {
		m_state = SocketState::Closed;
	}

	return (unsigned)nbread;
}

unsigned SocketTcp::waitRecv(void *data, unsigned length, int timeout)
{
	SocketListener listener{{*this, SocketListener::Read}};

	listener.wait(timeout);

	return recv(data, length);
}

unsigned SocketTcp::send(const void *data, unsigned length)
{
	int nbsent;

	nbsent = ::send(m_handle, (Socket::ConstArg)data, length, 0);
	if (nbsent == Error) {
#if defined(_WIN32)
		int error = WSAGetLastError();

		if (error == WSAEWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockWrite, "send", error);
		}

		throw SocketError(SocketError::System, "send", error);
#else
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			throw SocketError(SocketError::WouldBlockWrite, "send");
		}

		throw SocketError(SocketError::System, "send");
#endif
	}

	return (unsigned)nbsent;
}

unsigned SocketTcp::waitSend(const void *data, unsigned length, int timeout)
{
	SocketListener listener{{*this, SocketListener::Write}};

	listener.wait(timeout);

	return send(data, length);
}