cwTcpSocket*.h/cpp : Updated to support more socket options and configurations.

This commit is contained in:
kpl 2020-01-27 17:52:53 -05:00
parent c37595578a
commit 6590d4b181
6 changed files with 429 additions and 62 deletions

View File

@ -34,6 +34,7 @@ namespace cw
typedef struct socket_str
{
int sockH;
int fdH;
unsigned flags;
unsigned recvBufByteCnt;
struct sockaddr_in sockaddr;
@ -45,6 +46,18 @@ namespace cw
rc_t _destroy( socket_t* p )
{
// close the fdH
if( p->fdH != cwSOCKET_NULL_SOCK )
{
errno = 0;
if( ::close(p->fdH) != 0 )
cwLogSysError(kOpFailRC,errno,"The socket fd close failed." );
p->fdH = cwSOCKET_NULL_SOCK;
}
// close the socket
if( p->sockH != cwSOCKET_NULL_SOCK )
{
@ -52,7 +65,7 @@ namespace cw
if( ::close(p->sockH) != 0 )
cwLogSysError(kOpFailRC,errno,"The socket close failed." );
p->sockH = cwSOCKET_NULL_SOCK;
}
@ -65,8 +78,8 @@ namespace cw
{
memset(retAddrPtr,0,sizeof(struct sockaddr_in));
if( portNumber == kInvalidPortNumber )
return cwLogError(kInvalidArgRC,"The port number %i cannot be used.",kInvalidPortNumber);
//if( portNumber == kInvalidPortNumber )
// return cwLogError(kInvalidArgRC,"The port number %i cannot be used.",kInvalidPortNumber);
if( addrStr == NULL )
retAddrPtr->sin_addr.s_addr = htonl(INADDR_ANY);
@ -80,7 +93,8 @@ namespace cw
//retAddrPtr->sin_len = sizeof(struct sockaddr_in);
retAddrPtr->sin_family = AF_INET;
retAddrPtr->sin_port = htons(portNumber);
if( portNumber != kInvalidPortNumber )
retAddrPtr->sin_port = htons(portNumber);
return kOkRC;
}
@ -104,7 +118,27 @@ namespace cw
return rc;
}
rc_t _setTimeOutMs( socket_t* p, unsigned timeOutMs )
{
rc_t rc = kOkRC;
struct timeval timeOut;
// set the socket time out
timeOut.tv_sec = timeOutMs/1000;
timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000;
if( setsockopt( p->sockH, SOL_SOCKET, SO_RCVTIMEO, &timeOut, sizeof(timeOut) ) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket timeout failed." );
goto errLabel;
}
errLabel:
return rc;
}
}
}
}
@ -124,43 +158,25 @@ cw::rc_t cw::net::socket::create(
socket_t* p = mem::allocZ<socket_t>();
p->sockH = cwSOCKET_NULL_SOCK;
p->fdH = cwSOCKET_NULL_SOCK;
int type = cwIsFlag(flags,kStreamFl) ? SOCK_STREAM : SOCK_DGRAM;
int protocol = cwIsFlag(flags,kTcpFl) ? 0 : IPPROTO_UDP;
// get a handle to the socket
if(( p->sockH = ::socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == cwSOCKET_SYS_ERR )
if(( p->sockH = ::socket( AF_INET, type, protocol ) ) == cwSOCKET_SYS_ERR )
return cwLogSysError(kOpFailRC, errno, "Socket create failed." );
// create the local address
if((rc = _initAddr(p, NULL, port, &p->sockaddr )) != kOkRC )
goto errLabel;
// bind the socket to a local address/port
if( (bind( p->sockH, (struct sockaddr*)&p->sockaddr, sizeof(p->sockaddr))) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno,"Socket bind failed." );
goto errLabel;
}
// if a remote addr was given connect this socket to it
if( remoteAddr != NULL )
if((rc = _connect(p,remoteAddr,remotePort)) != kOkRC )
goto errLabel;
// if this socket should block
if( cwIsFlag(flags,kBlockingFl) )
if( cwIsFlag(flags,kBlockingFl))
{
struct timeval timeOut;
// set the socket time out
timeOut.tv_sec = timeOutMs/1000;
timeOut.tv_usec = (timeOutMs - (timeOut.tv_sec * 1000)) * 1000;
if( setsockopt( p->sockH, SOL_SOCKET, SO_RCVTIMEO, &timeOut, sizeof(timeOut) ) == cwSOCKET_SYS_ERR )
if( timeOutMs > 0 )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket timeout failed." );
goto errLabel;
_setTimeOutMs(p,timeOutMs);
}
p->flags = cwSetFlag(p->flags,kIsBlockingFl);
}
else
@ -184,18 +200,90 @@ cw::rc_t cw::net::socket::create(
}
}
if( cwIsFlag(flags,kReuseAddrFl) )
{
unsigned int reuseaddr = 1;
if( setsockopt(p->sockH, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse address' attribute failed." );
goto errLabel;
}
}
#ifdef SO_REUSEPORT
if( cwIsFlag(flags,kReusePortFl) )
{
unsigned int reuseaddr = 1;
if(setsockopt(p->sockH, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." );
goto errLabel;
}
}
#endif
if( cwIsFlag(flags,kMultiCastTtlFl) )
{
unsigned char ttl = 1;
if( setsockopt(p->sockH, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'multicast TTL' attribute failed." );
goto errLabel;
}
}
if( cwIsFlag(flags,kMultiCastLoopFl) )
{
unsigned char loopback = 1;
if( setsockopt(p->sockH, IPPROTO_IP, IP_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket 'reuse port' attribute failed." );
goto errLabel;
}
}
// if broadcast option was requested.
if( cwIsFlag(flags,kBroadcastFl) )
{
int bcastFl = 1;
int bcastFl = 1;
if( setsockopt( p->sockH, SOL_SOCKET, SO_BROADCAST, &bcastFl, sizeof(bcastFl) ) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to set the socket broadcast attribute failed." );
goto errLabel;
}
}
// create the local address
if((rc = _initAddr(p, NULL, port, &p->sockaddr )) != kOkRC )
goto errLabel;
// bind the socket to a local address/port
if( (bind( p->sockH, (struct sockaddr*)&p->sockaddr, sizeof(p->sockaddr))) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno,"Socket bind failed." );
goto errLabel;
}
// if a remote addr was given connect this socket to it
if( remoteAddr != NULL )
if((rc = _connect(p,remoteAddr,remotePort)) != kOkRC )
goto errLabel;
// if the socket should be marked for listening
if( cwIsFlag(flags,kListenFl) )
{
if( ::listen(p->sockH, 10) != 0 )
{
rc = cwLogSysError(kOpFailRC,errno,"Socket listen() failed.");
goto errLabel;
}
}
errLabel:
if(rc != kOkRC )
_destroy(p);
@ -204,7 +292,7 @@ cw::rc_t cw::net::socket::create(
return rc;
}
cw::rc_t cw::net::socket::destroy( handle_t& hRef )
{
rc_t rc = kOkRC;
@ -220,6 +308,82 @@ cw::rc_t cw::net::socket::destroy( handle_t& hRef )
return rc;
}
cw::rc_t cw::net::socket::join_multicast_group( handle_t h, const char* addrStr )
{
rc_t rc = kOkRC;
socket_t* p = _handleToPtr(h);
struct ip_mreq req;
memset(&req, 0, sizeof(req));
if(inet_pton(AF_INET,addrStr,&req.imr_multiaddr.s_addr) == 0 )
{
rc = cwLogSysError(kOpFailRC,errno, "The network address string '%s' could not be converted to a netword address structure.",cwStringNullGuard(addrStr) );
goto errLabel;
}
req.imr_interface.s_addr = INADDR_ANY;
if(setsockopt(p->sockH, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req)) == cwSOCKET_SYS_ERR )
{
rc = cwLogSysError(kOpFailRC,errno, "Attempt to add socket to multicast group on '%s' failed.", cwStringNullGuard(addrStr) );
goto errLabel;
}
errLabel:
return rc;
}
cw::rc_t cw::net::socket::setTimeOutMs( handle_t h, unsigned timeOutMs )
{
socket_t* p = _handleToPtr(h);
return _setTimeOutMs(p,timeOutMs);
}
cw::rc_t cw::net::socket::accept( handle_t h )
{
struct sockaddr_storage remoteAddr; // connector's address information
socklen_t sin_size = sizeof(remoteAddr);
rc_t rc = kOkRC;
int fd = cwSOCKET_NULL_SOCK;
socket_t* p = _handleToPtr(h);
if((fd = accept(p->sockH, (struct sockaddr*)&remoteAddr, &sin_size)) < 0)
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
rc = kTimeOutRC;
else
{
rc = cwLogSysError(kOpFailRC,errno,"Socket accept() failed.");
goto errLabel;
}
}
else
{
char s[INET_ADDRSTRLEN+1];
addrToString( (struct sockaddr_in*)&remoteAddr, s, INET_ADDRSTRLEN );
if( p->fdH != cwSOCKET_NULL_SOCK )
{
close(p->fdH);
p->fdH = cwSOCKET_NULL_SOCK;
}
p->fdH = fd;
printf("Connect:%s\n",s);
}
errLabel:
return rc;
}
cw::rc_t cw::net::socket::connect( handle_t h, const char* remoteAddr, portNumber_t remotePort )
{
socket_t* p = _handleToPtr(h);
@ -232,7 +396,7 @@ cw::rc_t cw::net::socket::send( handle_t h, const void* data, unsigned dataByteC
errno = 0;
if( cwIsFlag(p->flags,kIsConnectedFl) == false )
return cwLogError(kInvalidOpRC,"cmUdpSend() only works with connected sockets.");
return cwLogError(kInvalidOpRC,"socket::send() only works with connected sockets.");
if( ::send( p->sockH, data, dataByteCnt, 0 ) == cwSOCKET_SYS_ERR )
return cwLogSysError(kOpFailRC,errno,"Send failed.");
@ -246,7 +410,7 @@ cw::rc_t cw::net::socket::send( handle_t h, const void* data, unsigned dataByteC
errno = 0;
if( sendto(p->sockH, data, dataByteCnt, 0, (struct sockaddr*)remoteAddr, sizeof(*remoteAddr)) == cwSOCKET_SYS_ERR )
if( ::sendto(p->sockH, data, dataByteCnt, 0, (struct sockaddr*)remoteAddr, sizeof(*remoteAddr)) == cwSOCKET_SYS_ERR )
return cwLogSysError(kOpFailRC,errno,"Send to remote addr. failed.");
return kOkRC;
@ -276,7 +440,9 @@ cw::rc_t cw::net::socket::recieve( handle_t h, char* data, unsigned dataByteCnt,
if( recvByteCntRef != NULL )
*recvByteCntRef = 0;
if((retVal = recvfrom(p->sockH, data, dataByteCnt, 0, (struct sockaddr*)fromAddr, &sizeOfRemoteAddr )) == cwSOCKET_SYS_ERR )
int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH;
if((retVal = recvfrom(fd, data, dataByteCnt, 0, (struct sockaddr*)fromAddr, &sizeOfRemoteAddr )) == cwSOCKET_SYS_ERR )
return errno == EAGAIN ? kTimeOutRC : cwLogSysError(kOpFailRC,errno,"recvfrom() failed.");
if( recvByteCntRef != NULL )
@ -344,6 +510,43 @@ cw::rc_t cw::net::socket::select_recieve(handle_t h, char* buf, unsigned bufByte
return rc;
}
cw::rc_t cw::net::socket::recv_from(handle_t h, char* buf, unsigned bufByteCnt, unsigned* recvByteCntRef, struct sockaddr_in* fromAddr )
{
rc_t rc = kOkRC;
socket_t* p = _handleToPtr(h);
socklen_t addrlen = 0;
int bytesN = 0;
if( recvByteCntRef != nullptr )
*recvByteCntRef = 0;
if( fromAddr != nullptr )
{
addrlen = sizeof(*fromAddr);
memset(fromAddr,0,sizeof(*fromAddr));
}
int fd = p->fdH != cwSOCKET_NULL_SOCK ? p->fdH : p->sockH;
if((bytesN = recvfrom(fd, buf, bufByteCnt, 0, (struct sockaddr*)fromAddr, &addrlen)) < 0 )
{
// if this is a non-blocking socket then return value -1 indicates that no data is available.
if( cwIsNotFlag( p->flags, kBlockingFl) && bytesN == -1)
bytesN = 0;
else
{
rc = cwLogSysError(kReadFailRC,errno,"recvfrom() failed.");
goto errLabel;
}
}
if( recvByteCntRef != nullptr )
*recvByteCntRef = bytesN;
errLabel:
return rc;
}
cw::rc_t cw::net::socket::initAddr( handle_t h, const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr )
{

View File

@ -14,10 +14,16 @@ namespace cw
enum
{
kNonBlockingFl = 0x00, // create a non-blocking socket
kBlockingFl = 0x01, // create a blocking socket
kTcpFl = 0x02, // create a TCP socket rather than a UDP socket
kBroadcastFl = 0x04
kNonBlockingFl = 0x000, // create a non-blocking socket
kBlockingFl = 0x001, // create a blocking socket
kTcpFl = 0x002, // create a TCP socket rather than a UDP socket
kBroadcastFl = 0x004,
kReuseAddrFl = 0x008,
kReusePortFl = 0x010,
kMultiCastTtlFl = 0x020,
kMultiCastLoopFl = 0x040,
kListenFl = 0x080,
kStreamFl = 0x100
};
enum
@ -36,6 +42,13 @@ namespace cw
rc_t destroy( handle_t& hRef );
rc_t join_multicast_group( handle_t h, const char* addr );
rc_t setTimeOutMs( handle_t h, unsigned timeOutMs );
// Listen for a connections
rc_t accept( handle_t h );
// Set a destination address for this socket. Once a destination address is set
// the caller may use send() to communicate with the specified remote socket
// without having to specify a destination address on each call.
@ -64,6 +77,8 @@ namespace cw
//
rc_t select_recieve(handle_t h, char* buf, unsigned bufByteCnt, unsigned timeOutMs, unsigned* recvByteCntRef=nullptr, struct sockaddr_in* fromAddr=nullptr );
//
rc_t recv_from(handle_t h, char* buf, unsigned bufByteCnt, unsigned* recvByteCntRef=nullptr, struct sockaddr_in* fromAddr=nullptr );
// Prepare a struct sockadddr_in for use with send()
rc_t initAddr( handle_t h, const char* addrStr, portNumber_t portNumber, struct sockaddr_in* retAddrPtr );

View File

@ -46,8 +46,17 @@ namespace cw
socksrv_t* p = static_cast<socksrv_t*>(arg);
unsigned rcvByteCnt = 0;
struct sockaddr_in fromAddr;
rc_t rc = kOkRC;
if( select_recieve(p->sockH, p->recvBuf, p->recvBufByteCnt, p->timeOutMs, &rcvByteCnt, &fromAddr ) == kOkRC )
if( p->timeOutMs == 0 )
{
rc = recv_from(p->sockH, p->recvBuf, p->recvBufByteCnt, &rcvByteCnt, &fromAddr );
sleepMs(100);
}
else
rc = select_recieve(p->sockH, p->recvBuf, p->recvBufByteCnt, p->timeOutMs, &rcvByteCnt, &fromAddr );
if( rc == kOkRC )
if( rcvByteCnt>0 && p->cbFunc != nullptr )
p->cbFunc( p->cbArg, p->recvBuf, rcvByteCnt, &fromAddr );
@ -74,7 +83,7 @@ cw::rc_t cw::net::srv::create(
socksrv_t* p = mem::allocZ<socksrv_t>();
if((rc = socket::create( p->sockH, port, socket::kNonBlockingFl, 0, remoteAddr, remotePort )) != kOkRC )
if((rc = socket::create( p->sockH, port, flags, timeOutMs, remoteAddr, remotePort )) != kOkRC )
goto errLabel;
if((rc = thread::create( p->threadH, _threadFunc, p )) != kOkRC )

View File

@ -7,7 +7,7 @@ namespace cw
{
namespace srv
{
typedef void (*cbFunc_t)( void* cbArg, const char* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr );
typedef void (*cbFunc_t)( void* cbArg, const void* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr );
typedef handle< struct socksrv_str > handle_t;
rc_t create( handle_t& hRef, //

View File

@ -24,9 +24,12 @@ namespace cw
handle_t sockH;
thread::handle_t threadH;
unsigned cbN;
bool serverFl;
bool readyFl;
bool connectedFl;
} app_t;
bool _threadFunc( void* arg )
bool _dgramThreadFunc( void* arg )
{
rc_t rc;
app_t* app = static_cast<app_t*>(arg);
@ -48,6 +51,57 @@ namespace cw
fflush(stdout);
}
return true;
}
bool _tcpStreamThreadFunc( void* arg )
{
rc_t rc;
app_t* app = static_cast<app_t*>(arg);
char buf[ app->recvBufByteN ];
unsigned recvBufByteN = 0;
if( !app->serverFl )
{
// the client node has nothing to do because it does not receive (it only sends)
sleepMs(50);
}
else
{
if( app->connectedFl == false )
{
if((rc = accept( app->sockH )) == kOkRC )
{
app->connectedFl = true;
printf("Server connected.\n");
}
}
else
{
if((rc = recieve( app->sockH, buf, app->recvBufByteN, &recvBufByteN, nullptr )) == kOkRC )
{
// if the server disconnects then recvBufByteN
if( recvBufByteN==0 )
{
app->connectedFl = false;
}
else
{
printf("%i %s\n", recvBufByteN, buf );
}
}
}
}
// count the number of callbacks
app->cbN += 1;
if( app->cbN % 10 == 0)
{
// print '+' when the server is not connected.
printf("%s", app->serverFl && app->connectedFl == false ? "+" : ".");
fflush(stdout);
}
return true;
}
}
@ -68,7 +122,7 @@ cw::rc_t cw::net::socket::test( portNumber_t localPort, const char* remoteAddr,
if((rc = create(app.sockH,localPort, kBlockingFl,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
return rc;
if((rc = thread::create( app.threadH, _threadFunc, &app )) != kOkRC )
if((rc = thread::create( app.threadH, _dgramThreadFunc, &app )) != kOkRC )
goto errLabel;
if((rc = thread::unpause( app.threadH )) != kOkRC )
@ -95,26 +149,107 @@ cw::rc_t cw::net::socket::test( portNumber_t localPort, const char* remoteAddr,
return rcSelect(rc,rc0,rc1);
}
cw::rc_t cw::net::socket::test_tcp( portNumber_t localPort, const char* remoteAddr, portNumber_t remotePort, bool dgramFl, bool serverFl )
{
rc_t rc;
unsigned timeOutMs = 100;
const unsigned sbufN = 31;
char sbuf[ sbufN+1 ];
app_t app;
bool streamFl = !dgramFl;
bool clientFl = !serverFl;
unsigned flags = kTcpFl | kBlockingFl;
app.cbN = 0;
app.recvBufByteN = sbufN+1;
app.serverFl = serverFl;
app.readyFl = false;
app.connectedFl = false;
if( serverFl && streamFl )
flags |= kListenFl;
if( streamFl )
flags |= kStreamFl;
// create the socket
if((rc = create(app.sockH,localPort, flags,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
return rc;
// create the listening thread (which is really only used by the server)
if((rc = thread::create( app.threadH, streamFl ? _tcpStreamThreadFunc : _dgramThreadFunc, &app )) != kOkRC )
goto errLabel;
// if this is the client then connect to the server (which must have already been started)
if( streamFl && clientFl )
{
if((rc = connect(app.sockH,remoteAddr,remotePort)) != kOkRC )
goto errLabel;
app.connectedFl = true;
}
printf("Starting node ....\n");
// start the thread
if((rc = thread::unpause( app.threadH )) != kOkRC )
goto errLabel;
while( true )
{
printf("? ");
if( std::fgets(sbuf,sbufN,stdin) == sbuf )
{
if( strcmp(sbuf,"quit\n") == 0)
break;
// when using streams only the client can send
if( streamFl & clientFl )
{
printf("Sending:%s",sbuf);
send(app.sockH, sbuf, strlen(sbuf)+1 );
}
// when using dgrams the dest. address is need to send
if( dgramFl )
{
printf("Sending:%s",sbuf);
send(app.sockH, sbuf, strlen(sbuf)+1, remoteAddr, remotePort);
}
}
}
errLabel:
rc_t rc0 = thread::destroy(app.threadH);
rc_t rc1 = destroy(app.sockH);
return rcSelect(rc,rc0,rc1);
}
namespace cw
{
namespace net
{
namespace srv
{
typedef struct app_str
{
handle_t srvH;
unsigned cbN;
} app_t;
typedef struct app_str
{
handle_t srvH;
unsigned cbN;
} app_t;
void srvRecieveCallback( void* arg, const char* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr )
{
app_t* p = static_cast<app_t*>(arg);
char addrBuf[ INET_ADDRSTRLEN ];
socket::addrToString( fromAddr, addrBuf, INET_ADDRSTRLEN );
p->cbN += 1;
printf("%i %s %s", p->cbN, addrBuf, data );
}
void srvRecieveCallback( void* arg, const void* data, unsigned dataByteCnt, const struct sockaddr_in* fromAddr )
{
app_t* p = static_cast<app_t*>(arg);
char addrBuf[ INET_ADDRSTRLEN ];
socket::addrToString( fromAddr, addrBuf, INET_ADDRSTRLEN );
p->cbN += 1;
printf("%i %s %s", p->cbN, addrBuf, (const char*)data );
}
}
}
}
@ -153,3 +288,6 @@ cw::rc_t cw::net::srv::test( socket::portNumber_t localPort, const char* remoteA
return rcSelect(rc,rc0);
}

View File

@ -8,11 +8,13 @@ namespace cw
namespace socket
{
rc_t test( portNumber_t localPort, const char* remoteAddr, portNumber_t remotePort );
rc_t test_tcp( portNumber_t localPort, const char* remoteAddr, portNumber_t remotePort, bool dgramFl, bool serverFl );
}
namespace srv
{
rc_t test( socket::portNumber_t localPort, const char* remoteAddr, socket::portNumber_t remotePort );
rc_t test( socket::portNumber_t localPort, const char* remoteAddr, socket::portNumber_t remotePort );
rc_t mdns_test();
}
}
}