/* * IXSocket.cpp * Author: Benjamin Sergeant * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. */ #include "IXSocket.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // Linux/Android has a special type of virtual files. select(2) will react // when reading/writing to those files, unlike closing sockets. // // https://linux.die.net/man/2/eventfd // // eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4) // is on Kernel 3.x // // cf Android/Kernel table here // https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel // #ifndef __APPLE__ # include #endif // Android needs extra headers for TCP_NODELAY and IPPROTO_TCP #ifdef ANDROID # include # include #endif namespace ix { Socket::Socket() : _sockfd(-1), _eventfd(-1) { #ifndef __APPLE__ _eventfd = eventfd(0, 0); assert(_eventfd != -1 && "Panic - eventfd not functioning on this platform"); #endif } Socket::~Socket() { close(); #ifndef __APPLE__ ::close(_eventfd); #endif } bool connectToAddress(const struct addrinfo *address, int& sockfd, std::string& errMsg) { sockfd = -1; int fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); if (fd < 0) { errMsg = "Cannot create a socket"; return false; } int maxRetries = 3; for (int i = 0; i < maxRetries; ++i) { if (connect(fd, address->ai_addr, address->ai_addrlen) != -1) { sockfd = fd; return true; } // EINTR means we've been interrupted, in which case we try again. if (errno != EINTR) break; } ::close(fd); sockfd = -1; errMsg = strerror(errno); return false; } int Socket::hostname_connect(const std::string& hostname, int port, std::string& errMsg) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; std::string sport = std::to_string(port); struct addrinfo *res = nullptr; int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res); if (getaddrinfo_result) { errMsg = gai_strerror(getaddrinfo_result); return -1; } int sockfd = -1; // iterate through the records to find a working peer struct addrinfo *address; bool success = false; for (address = res; address != nullptr; address = address->ai_next) { success = connectToAddress(address, sockfd, errMsg); if (success) { break; } } freeaddrinfo(res); return sockfd; } void Socket::configure() { int flag = 1; setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking #ifdef SO_NOSIGPIPE int value = 1; setsockopt(_sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&value, sizeof(value)); #endif } void Socket::poll(const OnPollCallback& onPollCallback) { if (_sockfd == -1) { onPollCallback(); return; } fd_set rfds; FD_ZERO(&rfds); FD_SET(_sockfd, &rfds); #ifndef __APPLE__ FD_SET(_eventfd, &rfds); #endif int sockfd = _sockfd; int nfds = std::max(sockfd, _eventfd); select(nfds + 1, &rfds, nullptr, nullptr, nullptr); onPollCallback(); } void Socket::wakeUpFromPollApple() { close(); // All OS but Linux will wake up select // when closing the file descriptor watched by select } void Socket::wakeUpFromPollLinux() { std::string str("\n"); // this will wake up the thread blocked on select const void* buf = reinterpret_cast(str.c_str()); write(_eventfd, buf, str.size()); } void Socket::wakeUpFromPoll() { #ifdef __APPLE__ wakeUpFromPollApple(); #else wakeUpFromPollLinux(); #endif } bool Socket::connect(const std::string& host, int port, std::string& errMsg) { std::lock_guard lock(_socketMutex); #ifndef __APPLE__ if (_eventfd == -1) { return false; // impossible to use this socket if eventfd is broken } #endif _sockfd = Socket::hostname_connect(host, port, errMsg); return _sockfd != -1; } void Socket::close() { std::lock_guard lock(_socketMutex); if (_sockfd == -1) return; ::close(_sockfd); _sockfd = -1; } int Socket::send(char* buffer, size_t length) { int flags = 0; #ifdef MSG_NOSIGNAL flags = MSG_NOSIGNAL; #endif return (int) ::send(_sockfd, buffer, length, flags); } int Socket::send(const std::string& buffer) { return send((char*)&buffer[0], buffer.size()); } int Socket::recv(void* buffer, size_t length) { int flags = 0; #ifdef MSG_NOSIGNAL flags = MSG_NOSIGNAL; #endif return (int) ::recv(_sockfd, buffer, length, flags); } }