IXWebSocket/ixwebsocket/IXSocketConnect.cpp

187 lines
5.2 KiB
C++
Raw Normal View History

/*
* IXSocketConnect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketConnect.h"
2018-12-15 01:28:11 +01:00
#include "IXDNSLookup.h"
2019-01-06 05:38:43 +01:00
#include "IXNetSystem.h"
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
#ifdef ANDROID
# include <linux/in.h>
# include <linux/tcp.h>
#endif
namespace
{
void closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#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.
//
int SocketConnect::connectToAddress(const struct addrinfo *address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
2019-01-07 20:18:00 +01:00
int 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);
2019-01-07 20:18:00 +01:00
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
&& errno != EINPROGRESS)
{
2019-01-07 20:18:00 +01:00
closeSocket(fd);
errMsg = strerror(errno);
return -1;
}
2019-01-03 21:53:44 +01:00
for (;;)
{
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
closeSocket(fd);
errMsg = "Cancelled";
2019-01-07 03:10:39 +01:00
return -1;
}
2019-01-07 20:18:00 +01:00
// Use select to check the status of the new connection
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds;
fd_set efds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
FD_ZERO(&efds);
FD_SET(fd, &efds);
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(errno);
return -1;
}
// Nothing was written to the socket, wait again.
if (!FD_ISSET(fd, &wfds)) continue;
2019-01-06 02:35:50 +01:00
// Something was written to the socket. Check for errors.
2018-12-15 01:28:11 +01:00
int optval = -1;
socklen_t optlen = sizeof(optval);
2019-01-06 02:35:50 +01:00
#ifdef _WIN32
2019-01-06 05:38:43 +01:00
// On connect error, in async mode, windows will write to the exceptions fds
2019-01-06 02:35:50 +01:00
if (FD_ISSET(fd, &efds))
#else
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
2019-01-06 02:35:50 +01:00
#endif
{
closeSocket(fd);
2019-01-07 20:18:00 +01:00
errMsg = strerror(optval);
return -1;
}
else
{
// Success !
return fd;
}
}
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
//
2018-12-15 01:28:11 +01:00
DNSLookup dnsLookup(hostname, port);
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
if (res == nullptr)
{
return -1;
}
int sockfd = -1;
// iterate through the records to find a working peer
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;
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
(void *)&value, sizeof(value));
#endif
}
}