IXWebSocket/ixwebsocket/IXSocketConnect.cpp

156 lines
4.6 KiB
C++
Raw Permalink Normal View History

/*
* IXSocketConnect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketConnect.h"
2019-09-23 19:25:23 +02:00
2018-12-15 01:28:11 +01:00
#include "IXDNSLookup.h"
2019-01-06 05:38:43 +01:00
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
2020-03-25 04:37:55 +01:00
#include "IXSocket.h"
#include "IXUniquePtr.h"
#include <fcntl.h>
2019-09-23 19:25:23 +02:00
#include <string.h>
#include <sys/types.h>
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
#ifdef ANDROID
2019-09-23 19:25:23 +02:00
#include <linux/in.h>
#include <linux/tcp.h>
#endif
namespace ix
{
2018-12-15 01:28:11 +01:00
//
// This function can be cancelled every 50 ms
// This is important so that we don't block the main UI thread when shutting down a
// connection which is already trying to reconnect, and can be blocked waiting for
// ::connect to respond.
2018-12-15 01:28:11 +01:00
//
2019-09-23 19:25:23 +02:00
int SocketConnect::connectToAddress(const struct addrinfo* address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (fd < 0)
{
errMsg = "Cannot create a socket";
return -1;
}
// Set the socket to non blocking mode, so that slow responses cannot
2018-12-15 01:28:11 +01:00
// block us for too long
SocketConnect::configure(fd);
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded())
{
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
2019-01-07 20:18:00 +01:00
return -1;
}
2019-01-03 21:53:44 +01:00
for (;;)
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
Socket::closeSocket(fd);
errMsg = "Cancelled";
2019-01-07 03:10:39 +01:00
return -1;
}
int timeoutMs = 10;
bool readyToRead = false;
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
if (pollResult == PollResultType::Timeout)
2019-01-07 20:18:00 +01:00
{
continue;
2019-01-07 20:18:00 +01:00
}
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
2019-09-23 19:25:23 +02:00
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
else if (pollResult == PollResultType::ReadyForWrite)
{
return fd;
}
else
{
Socket::closeSocket(fd);
2019-09-23 19:25:23 +02:00
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
}
}
Socket::closeSocket(fd);
2019-01-07 20:18:00 +01:00
errMsg = "connect timed out after 60 seconds";
return -1;
}
int SocketConnect::connect(const std::string& hostname,
int port,
std::string& errMsg,
2018-12-15 01:28:11 +01:00
const CancellationRequest& isCancellationRequested)
{
//
// First do DNS resolution
//
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
2019-09-23 19:25:23 +02:00
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
2018-12-15 01:28:11 +01:00
if (res == nullptr)
{
return -1;
}
int sockfd = -1;
// iterate through the records to find a working peer
2019-09-23 19:25:23 +02:00
struct addrinfo* address;
for (address = res; address != nullptr; address = address->ai_next)
{
//
// Second try to connect to the remote host
//
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
if (sockfd != -1)
{
break;
}
}
2018-12-15 01:28:11 +01:00
freeaddrinfo(res);
return sockfd;
}
2019-01-03 21:53:44 +01:00
// FIXME: configure is a terrible name
void SocketConnect::configure(int sockfd)
{
2018-12-15 01:28:11 +01:00
// 1. disable Nagle's algorithm
int flag = 1;
2018-12-15 01:28:11 +01:00
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
2018-12-15 01:28:11 +01:00
// 2. make socket non blocking
#ifdef _WIN32
unsigned long nonblocking = 1;
2019-01-05 02:28:13 +01:00
ioctlsocket(sockfd, FIONBIO, &nonblocking);
#else
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
#endif
2018-12-15 01:28:11 +01:00
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
#ifdef SO_NOSIGPIPE
int value = 1;
2019-09-23 19:25:23 +02:00
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
#endif
}
2019-09-23 19:25:23 +02:00
} // namespace ix