Support non-blocking connect()

This commit is contained in:
Kirigaya Kazuto 2018-09-09 22:35:18 +08:00
parent 1c4e3e5f24
commit 34ab73a7c4
2 changed files with 350 additions and 27 deletions

324
gsock.cpp
View File

@ -111,15 +111,80 @@ static inline const char* get_family_name(int family)
} }
} }
int GetNativeErrCode()
{
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
// Internal Socket Call Errcode
enum gerrno
{
UnknownError = -1,
OK,
WouldBlock,
InProgress,
Already,
IsConnected,
};
gerrno TranslateNativeErrToGErr(int native_errcode)
{
switch (native_errcode)
{
#ifdef _WIN32
case WSAEWOULDBLOCK:
return gerrno::WouldBlock;
case WSAEINPROGRESS:
return gerrno::InProgress;
case WSAEALREADY:
return gerrno::Already;
case WSAEISCONN:
return gerrno::IsConnected;
#else
case EWOULDBLOCK:
return gerrno::WouldBlock;
case EINPROGRESS:
return gerrno::InProgress;
case EALREADY:
return gerrno::Already;
case EISCONN:
return gerrno::IsConnected;
#endif
default:
myliblog("Unknown Error Code: %d\n", native_errcode);
return gerrno::UnknownError;
}
}
struct vsock::_impl struct vsock::_impl
{ {
int sfd; int sfd;
bool created; bool created;
bool nonblocking;
// Does not set "nonblocking" flag.
int doSetNonblocking()
{
u_long mode = 1;
if (ioctlsocket(sfd, FIONBIO, &mode) == 0)
{
return 0;
}
else
{
return -1;
}
}
}; };
vsock::vsock() : _vp(new _impl) vsock::vsock() : _vp(new _impl)
{ {
_vp->created=false; _vp->created=false;
_vp->nonblocking = false;
} }
vsock::vsock(vsock&& v) vsock::vsock(vsock&& v)
@ -136,6 +201,37 @@ vsock& vsock::operator = (vsock&& v)
return *this; return *this;
} }
int vsock::setNonblocking()
{
if (!_vp->nonblocking)
{
if (_vp->created)
{
if (_vp->doSetNonblocking() == 0)
{
_vp->nonblocking = true;
return 0;
}
else
{
// Failed to set non-blocking.
return -1;
}
}
else
{
// Socket is not created yet. Just mark it.
_vp->nonblocking = true;
return 0;
}
}
else
{
// Socket is already in non-blocking mode.
return 0;
}
}
vsock::~vsock() vsock::~vsock()
{ {
if(_vp) if(_vp)
@ -155,10 +251,44 @@ vsock::~vsock()
struct sock::_impl struct sock::_impl
{ {
static int create_socket(vsock::_impl* _vp, int af_protocol);
static int connect_ipv4(vsock::_impl* _vp,const std::string& IPStr, int Port); static int connect_ipv4(vsock::_impl* _vp,const std::string& IPStr, int Port);
static int connect_ipv6(vsock::_impl* _vp,const std::string& IPStr, int Port); static int connect_ipv6(vsock::_impl* _vp,const std::string& IPStr, int Port);
static int connect_real(vsock::_impl* _vp, int af_protocol, const sockaddr* paddr, int size);
}; };
// static
int sock::_impl::create_socket(vsock::_impl* _vp, int af_protocol)
{
// If socket is not created, then create it.
if (!_vp->created)
{
_vp->sfd = socket(af_protocol, SOCK_STREAM, 0);
if (_vp->sfd < 0)
{
myliblog("socket() returns %d. WSAGetLastError: %d\n", _vp->sfd, WSAGetLastError());
return GSOCK_ERROR_CREAT;
}
if (_vp->nonblocking && _vp->doSetNonblocking() != 0)
{
myliblog("Failed to set socket to nonblocking with _vp %p\n", _vp);
// close this socket to avoid fd leak.
closesocket(_vp->sfd);
return GSOCK_ERROR_SETMODE;
}
myliblog("Socket <%s> created: [%d] with _vp %p. %s\n",
(af_protocol == AF_INET ? "IPv4" : "IPv6"),
_vp->sfd, _vp, (_vp->nonblocking ? "NonBlocking" : "Blocking"));
_vp->created = true;
}
return 0;
}
// static
int sock::_impl::connect_ipv4(vsock::_impl* _vp, const std::string& IPStr, int Port) int sock::_impl::connect_ipv4(vsock::_impl* _vp, const std::string& IPStr, int Port)
{ {
struct sockaddr_in saddr; struct sockaddr_in saddr;
@ -171,21 +301,11 @@ int sock::_impl::connect_ipv4(vsock::_impl* _vp, const std::string& IPStr, int P
saddr.sin_port = htons(Port); saddr.sin_port = htons(Port);
saddr.sin_family = AF_INET; saddr.sin_family = AF_INET;
// Create socket
_vp->sfd = socket(AF_INET, SOCK_STREAM, 0);
if (_vp->sfd<0)
{
myliblog("socket() returns %d. WSAGetLastError: %d\n", _vp->sfd, WSAGetLastError());
return GSOCK_ERROR_CREAT;
}
myliblog("Socket <IPv4> created: [%d] with _vp %p\n", _vp->sfd, _vp);
_vp->created = true;
// only returns -1 or 0 // only returns -1 or 0
return ::connect(_vp->sfd, (sockaddr*)&saddr, sizeof(saddr)); return connect_real(_vp, AF_INET, (sockaddr*)&saddr, sizeof(saddr));
} }
// static
int sock::_impl::connect_ipv6(vsock::_impl* _vp, const std::string& IPStr, int Port) int sock::_impl::connect_ipv6(vsock::_impl* _vp, const std::string& IPStr, int Port)
{ {
struct sockaddr_in6 saddr; struct sockaddr_in6 saddr;
@ -198,24 +318,28 @@ int sock::_impl::connect_ipv6(vsock::_impl* _vp, const std::string& IPStr, int P
saddr.sin6_port = htons(Port); saddr.sin6_port = htons(Port);
saddr.sin6_family = AF_INET6; saddr.sin6_family = AF_INET6;
// Create socket // only returns -1 or 0
_vp->sfd = socket(AF_INET6, SOCK_STREAM, 0); return connect_real(_vp, AF_INET6, (sockaddr*)&saddr, sizeof(saddr));
if (_vp->sfd<0)
{
myliblog("socket() returns %d. WSAGetLastError: %d\n", _vp->sfd, WSAGetLastError());
return GSOCK_ERROR_CREAT;
} }
myliblog("Socket <IPv6> created: [%d] with _vp %p\n", _vp->sfd, _vp); // static
_vp->created = true; int sock::_impl::connect_real(vsock::_impl* _vp, int af_protocol, const sockaddr* paddr, int namelen)
{
return ::connect(_vp->sfd, (sockaddr*)&saddr, sizeof(saddr)); // Create socket
int ret = create_socket(_vp, af_protocol);
if (ret != 0) return ret;
return ::connect(_vp->sfd, paddr, namelen);
} }
int sock::connect(const std::string& IPStr,int Port) int sock::connect(const std::string& IPStr,int Port)
{ {
myliblog("sock::connect() %p\n",this); myliblog("sock::connect() %p\n",this);
if (_vp->nonblocking)
{
return GSOCK_MISMATCH_MODE;
}
if (_vp->created) if (_vp->created)
{ {
return GSOCK_INVALID_SOCKET; return GSOCK_INVALID_SOCKET;
@ -233,6 +357,162 @@ int sock::connect(const std::string& IPStr,int Port)
} }
} }
struct NBConnectResult::_impl
{
int sfd;
struct sockaddr_in saddr;
struct sockaddr_in6 saddr6;
bool isv4;
// 0: Not used.
// 1: running
// 2: finished, connected.
// 3: finished, failed.
int status;
int errcode;
void update();
};
void NBConnectResult::_impl::update()
{
// Already finished.
if (status > 1) return;
int ret;
if (isv4)
{
ret = connect(sfd, (sockaddr*)&saddr, sizeof(saddr));
}
else
{
ret = connect(sfd, (sockaddr*)&saddr6, sizeof(saddr6));
}
if (ret == 0)
{
status = 2;
}
else
{
gerrno err = TranslateNativeErrToGErr(GetNativeErrCode());
if (err == gerrno::InProgress || err == gerrno::WouldBlock || err == gerrno::Already)
{
status = 1;
}
else if (err == gerrno::IsConnected)
{
status = 2;
}
else
{
status = 3;
}
}
myliblog("Status updated to %d\n", status);
}
NBConnectResult::NBConnectResult() : _p(new _impl)
{
_p->status = 0;
}
bool NBConnectResult::isFinished()
{
_p->update();
return (_p->status > 1);
}
bool NBConnectResult::isConnected()
{
_p->update();
return (_p->status == 2);
}
int NBConnectResult::getErrCode()
{
return _p->errcode;
}
void NBConnectResult::wait()
{
while (!isFinished());
}
struct NBTransferResult::_impl
{
int sfd;
void* ptr;
int datasz;
int errcode;
};
NBTransferResult::NBTransferResult() : _p(new _impl)
{
}
NBConnectResult sock::connect_nb(const std::string& IPStr, int Port)
{
NBConnectResult res;
int xret;
if (IPStr.find(":") != std::string::npos)
{
// Maybe IPv6
memset(&(res._p->saddr6), 0, sizeof(res._p->saddr6));
if (inet_pton(AF_INET6, IPStr.c_str(), &(res._p->saddr6.sin6_addr)) != 1)
{
// Failed.
res._p->status = 3;
res._p->errcode = GSOCK_INVALID_IP;
return res;
}
res._p->saddr6.sin6_port = htons(Port);
res._p->saddr6.sin6_family = AF_INET6;
res._p->isv4 = false;
xret = _impl::connect_real(_vp, AF_INET6, (sockaddr*)&(res._p->saddr6), sizeof(res._p->saddr6));
res._p->sfd = _vp->sfd;
}
else
{
// Maybe IPv4
memset(&(res._p->saddr), 0, sizeof(res._p->saddr));
if (inet_pton(AF_INET, IPStr.c_str(), &(res._p->saddr.sin_addr.s_addr)) != 1)
{
// Failed.
res._p->status = 3;
res._p->errcode = GSOCK_INVALID_IP;
return res;
}
res._p->saddr.sin_port = htons(Port);
res._p->saddr.sin_family = AF_INET;
res._p->isv4 = true;
xret = _impl::connect_real(_vp, AF_INET, (sockaddr*)&(res._p->saddr), sizeof(res._p->saddr));
res._p->sfd = _vp->sfd;
}
if (xret == 0)
{
res._p->status = 2; // Socket is connected immediately! Amazing!!
}
else if (xret == -1)
{
res._p->status = 1;
}
else
{
// Failed
res._p->status = 3;
res._p->errcode = xret;
}
return res;
}
int sock::send(const void* Buffer,int Length) int sock::send(const void* Buffer,int Length)
{ {
return ::send(_vp->sfd,(const char*)Buffer,Length,0); return ::send(_vp->sfd,(const char*)Buffer,Length,0);

43
gsock.h
View File

@ -9,6 +9,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
int InitNativeSocket(); int InitNativeSocket();
@ -23,10 +24,14 @@ enum
GSOCK_ERROR_NTOP = -6, // inet_ntop failed. GSOCK_ERROR_NTOP = -6, // inet_ntop failed.
GSOCK_MISMATCH_PROTOCOL = -7, // Protocol mismatch. GSOCK_MISMATCH_PROTOCOL = -7, // Protocol mismatch.
GSOCK_BAD_PROTOCOL = -8, // Bad protocol. GSOCK_BAD_PROTOCOL = -8, // Bad protocol.
GSOCK_ERROR_SETMODE = -9, // Failed to set nonblocking
GSOCK_MISMATCH_MODE = -10, // Example: calling blocking method on a non-blocking socket.
}; };
class vsock class vsock
{ {
public:
int setNonblocking();
protected: protected:
vsock(); vsock();
vsock(const vsock&)=delete; vsock(const vsock&)=delete;
@ -48,6 +53,40 @@ protected:
#endif #endif
}; };
class NBConnectResult
{
public:
NBConnectResult();
bool isFinished();
// Wait until the connection is finished. (via while loop)
void wait();
bool isConnected();
// ErrCode is only usable when the connection is finished and failed.
int getErrCode();
private:
struct _impl;
std::shared_ptr<_impl> _p;
friend class sock;
};
class NBTransferResult
{
public:
NBTransferResult();
bool isFinished();
int getBytesDone();
int getErrCode();
private:
struct _impl;
std::shared_ptr<_impl> _p;
friend class sock;
};
class sock : public vsock class sock : public vsock
{ {
public: public:
@ -57,12 +96,16 @@ public:
// GSOCK_INVALID_SOCKET: This socket has been connected before. // GSOCK_INVALID_SOCKET: This socket has been connected before.
// GSOCK_ERROR_CREAT // GSOCK_ERROR_CREAT
// GSOCK_INVALID_IP // GSOCK_INVALID_IP
// GSOCK_ERROR_SETMODE: Failed to set socket to non-blocking.
int connect(const std::string& IPStr,int Port); int connect(const std::string& IPStr,int Port);
NBConnectResult connect_nb(const std::string& IPStr, int Port);
// Return: // Return:
// return what send() and recv() call returns. // return what send() and recv() call returns.
int send(const void* Buffer,int Length); int send(const void* Buffer,int Length);
int recv(void* Buffer, int MaxToRecv); int recv(void* Buffer, int MaxToRecv);
NBTransferResult send_nb(const void* Buffer, int Length);
NBTransferResult recv_nb(void* Buffer, int MaxToRecv);
// Return: // Return:
// GSOCK_OK // GSOCK_OK