257 lines
6.0 KiB
C++
257 lines
6.0 KiB
C++
#include "matador/net/socket.hpp"
|
|
#include "matador/net/socket_stream.hpp"
|
|
#include "matador/net/os.hpp"
|
|
#include "matador/net/error.hpp"
|
|
#include "matador/net/ip.hpp"
|
|
|
|
#include <cstring>
|
|
|
|
namespace matador {
|
|
|
|
#define throw_logic_error(msg) \
|
|
do { \
|
|
std::stringstream str; \
|
|
str << msg; \
|
|
throw std::logic_error(str.str()); \
|
|
} while(false);
|
|
|
|
template < class P >
|
|
socket_base<P>::socket_base(const protocol_type &protocol)
|
|
{
|
|
open(protocol);
|
|
}
|
|
|
|
template < class P >
|
|
socket_base<P>::socket_base(const peer_type &peer)
|
|
{
|
|
open(peer.protocol());
|
|
}
|
|
|
|
template < class P >
|
|
socket_type socket_base<P>::open(const protocol_type &protocol)
|
|
{
|
|
return open(protocol.family(), protocol.type(), protocol.protocol());
|
|
}
|
|
|
|
template < class P >
|
|
void socket_base<P>::close()
|
|
{
|
|
if (sock_ <= 0) {
|
|
return;
|
|
}
|
|
//os::shutdown(sock_, os::shutdown_type::READ_WRITE);
|
|
os::close(sock_);
|
|
sock_ = 0;
|
|
}
|
|
|
|
template < class P >
|
|
bool socket_base<P>::is_open() const
|
|
{
|
|
return sock_ > 0;
|
|
}
|
|
|
|
template < class P >
|
|
socket_type socket_base<P>::release()
|
|
{
|
|
auto tmp_fd = sock_;
|
|
sock_ = 0;
|
|
return tmp_fd;
|
|
}
|
|
|
|
template < class P >
|
|
bool socket_base<P>::connect(const typename protocol_type::peer &p)
|
|
{
|
|
return ::connect(sock_, p.data(), static_cast<int>(p.size())) == 0;
|
|
}
|
|
|
|
template < class P >
|
|
void socket_base<P>::non_blocking(bool nb)
|
|
{
|
|
#ifdef WIN32
|
|
unsigned long nonblock = nb ? 0 : 1;
|
|
// fcntl doesn't do the right thing, but the similar ioctl does
|
|
// warning: is that still true? and does it the right thing for
|
|
// set blocking as well?
|
|
if (ioctlsocket(sock_, FIONBIO, &nonblock) != 0) {
|
|
throw std::logic_error("ioctlsocket: couldn't set flags");
|
|
}
|
|
is_nonblocking_ = nb;
|
|
#else
|
|
int val = fcntl(sock_, F_GETFL, 0);
|
|
if (val < 0) {
|
|
throw std::logic_error("fcntl: couldn't get flags");
|
|
}
|
|
|
|
int flag = (nb ? O_NONBLOCK : 0);
|
|
if (fcntl(sock_, F_SETFL, val | flag) < 0) {
|
|
std::string err(strerror(errno));
|
|
throw std::logic_error("fcntl: couldn't set flags (" + err + ")");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template < class P >
|
|
bool socket_base<P>::non_blocking() const
|
|
{
|
|
#ifdef _WIN32
|
|
return is_nonblocking_;
|
|
#else
|
|
int val = fcntl(sock_, F_GETFL, 0);
|
|
if (val < 0) {
|
|
std::string err(strerror(errno));
|
|
throw std::logic_error("fcntl: couldn't get flags (" + err + ")");
|
|
}
|
|
return (val & O_NONBLOCK) > 0;
|
|
#endif
|
|
}
|
|
|
|
template < class P >
|
|
void socket_base<P>::cloexec(bool nb)
|
|
{
|
|
#ifdef WIN32
|
|
unsigned long cloexec = 1;
|
|
// fcntl doesn't do the right thing, but the simular ioctl does
|
|
// warning: is that still true? and does it the right thing for
|
|
// set blocking as well?
|
|
if (ioctlsocket(sock_, FIONBIO, &cloexec) != 0) {
|
|
throw std::logic_error("ioctlsocket: couldn't set flags");
|
|
}
|
|
#else
|
|
int val = fcntl(sock_, F_GETFL, 0);
|
|
if (val < 0) {
|
|
throw std::logic_error("fcntl: couldn't get flags");
|
|
}
|
|
|
|
int flag = (nb ? FD_CLOEXEC : 0);
|
|
if (fcntl(sock_, F_SETFL, val | flag) < 0) {
|
|
std::string err(strerror(errno));
|
|
throw std::logic_error("fcntl: couldn't set flags (" + err + ")");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template < class P >
|
|
bool socket_base<P>::cloexec() const
|
|
{
|
|
int val = fcntl(sock_, F_GETFL, 0);
|
|
if (val < 0) {
|
|
std::string err(strerror(errno));
|
|
throw std::logic_error("fcntl: couldn't get flags (" + err + ")");
|
|
}
|
|
return (val & FD_CLOEXEC) > 0;
|
|
}
|
|
|
|
template < class P >
|
|
bool socket_base<P>::options(int name, bool value)
|
|
{
|
|
const char flag = static_cast<char>(value ? 1 : 0);
|
|
return setsockopt(sock_, IPPROTO_TCP, name, &flag, sizeof(flag)) == 0;
|
|
}
|
|
|
|
template < class P >
|
|
socket_type socket_base<P>::id() const
|
|
{
|
|
return sock_;
|
|
}
|
|
|
|
template < class P >
|
|
void socket_base<P>::assign(socket_type sock)
|
|
{
|
|
if (is_open()) {
|
|
throw std::logic_error("couldn't assign: socket already opened");
|
|
}
|
|
|
|
struct sockaddr_in addr{};
|
|
socklen_t addr_size = sizeof(struct sockaddr_in);
|
|
if (getpeername(sock, (struct sockaddr *)&addr, &addr_size) == 0) {
|
|
//char *clientip = new char[20];
|
|
char s[INET6_ADDRSTRLEN];
|
|
os::inet_ntop(addr.sin_family, &addr.sin_addr, s, sizeof s);
|
|
#ifdef _WIN32
|
|
// strcpy_s(clientip, 20, s);
|
|
#else
|
|
// strcpy(clientip, s);
|
|
#endif
|
|
}
|
|
sock_ = sock;
|
|
}
|
|
|
|
template < class P >
|
|
socket_type socket_base<P>::open(int family, int type, int protocol)
|
|
{
|
|
sock_ = ::socket(family, type, protocol);
|
|
return sock_;
|
|
}
|
|
|
|
template < class P >
|
|
socket_type connect(socket_stream<P> &stream, const char* hostname, unsigned short port)
|
|
{
|
|
char portstr[6];
|
|
sprintf(portstr, "%d", port);
|
|
// const char* portname = "daytime";
|
|
struct addrinfo hints = {};
|
|
memset(&hints,0,sizeof(hints));
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
struct addrinfo* res = nullptr;
|
|
struct addrinfo* head = nullptr;
|
|
int err = getaddrinfo(hostname, portstr, &hints, &res);
|
|
if (err != 0) {
|
|
detail::throw_logic_error_with_gai_errno("failed to resolve local socket address: %s", err);
|
|
}
|
|
|
|
head = res;
|
|
|
|
socket_type conn_fd = 0;
|
|
socket_type ret = 0;
|
|
do {
|
|
conn_fd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
if (!is_valid_socket(conn_fd)) {
|
|
// error, try the next one
|
|
continue;
|
|
}
|
|
|
|
ret = ::connect(conn_fd, res->ai_addr, res->ai_addrlen);
|
|
if (ret == 0) {
|
|
// success
|
|
stream.assign(conn_fd);
|
|
break;
|
|
// } else {
|
|
// throw_logic_error("couldn't execute: " << strerror(errno));
|
|
}
|
|
|
|
// bind error, close and try the next one
|
|
os::shutdown(conn_fd, os::shutdown_type::READ_WRITE);
|
|
} while ( (res = res->ai_next) != nullptr);
|
|
|
|
if (res == nullptr) {
|
|
throw_logic_error("couldn't execute to " << hostname << ":" << port);
|
|
}
|
|
|
|
freeaddrinfo(head);
|
|
|
|
return ret;
|
|
}
|
|
|
|
template < class P >
|
|
int connect(socket_stream<P> &stream, peer_base<P> endpoint)
|
|
{
|
|
auto pt = endpoint.protocol();
|
|
|
|
auto fd = ::socket(pt.family(), pt.type(), pt.protocol());
|
|
|
|
if (!is_valid_socket(fd)) {
|
|
return static_cast<int>(fd);
|
|
}
|
|
|
|
auto ret = ::connect(fd, endpoint.data(), static_cast<int>(endpoint.size()));
|
|
if (ret == 0) {
|
|
stream.assign(fd);
|
|
} else {
|
|
os::shutdown(fd, os::shutdown_type::READ_WRITE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
} |