199 lines
4.9 KiB
C++
199 lines
4.9 KiB
C++
/** @file
|
|
* @author Edouard DUPIN
|
|
* @copyright 2014, Edouard DUPIN, all right reserved
|
|
* @license MPL v2.0 (see license file)
|
|
*/
|
|
|
|
#include <enet/debug.hpp>
|
|
#include <enet/Tcp.hpp>
|
|
#include <sys/types.h>
|
|
#include <cerrno>
|
|
#include <unistd.h>
|
|
#include <cstring>
|
|
#include <etk/stdTools.hpp>
|
|
#include <ethread/tools.hpp>
|
|
|
|
#ifdef __TARGET_OS__Windows
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netdb.h>
|
|
#endif
|
|
|
|
bool enet::Tcp::setTCPNoDelay(bool _enabled) {
|
|
if (m_socketId >= 0) {
|
|
int flag = _enabled==true?1:0;
|
|
if(setsockopt(m_socketId, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)) == -1) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
enet::Tcp::Tcp() :
|
|
#ifdef __TARGET_OS__Windows
|
|
m_socketId(INVALID_SOCKET),
|
|
#else
|
|
m_socketId(-1),
|
|
#endif
|
|
m_name(),
|
|
m_status(status::error) {
|
|
|
|
}
|
|
|
|
#ifdef __TARGET_OS__Windows
|
|
enet::Tcp::Tcp(SOCKET _idSocket, const std::string& _name) :
|
|
#else
|
|
enet::Tcp::Tcp(int32_t _idSocket, const std::string& _name) :
|
|
#endif
|
|
m_socketId(_idSocket),
|
|
m_name(_name),
|
|
m_status(status::link) {
|
|
|
|
}
|
|
|
|
enet::Tcp::Tcp(Tcp&& _obj) :
|
|
m_socketId(_obj.m_socketId),
|
|
m_name(_obj.m_name),
|
|
m_status(_obj.m_status) {
|
|
#ifdef __TARGET_OS__Windows
|
|
_obj.m_socketId = INVALID_SOCKET;
|
|
#else
|
|
_obj.m_socketId = -1;
|
|
#endif
|
|
_obj.m_name = "";
|
|
_obj.m_status = status::error;
|
|
}
|
|
|
|
enet::Tcp::~Tcp() {
|
|
unlink();
|
|
}
|
|
|
|
enet::Tcp& enet::Tcp::operator = (enet::Tcp&& _obj) {
|
|
unlink();
|
|
m_socketId = _obj.m_socketId;
|
|
#ifdef __TARGET_OS__Windows
|
|
_obj.m_socketId = INVALID_SOCKET;
|
|
#else
|
|
_obj.m_socketId = -1;
|
|
#endif
|
|
m_name = _obj.m_name;
|
|
_obj.m_name = "";
|
|
m_status = _obj.m_status;
|
|
_obj.m_status = status::error;
|
|
return *this;
|
|
}
|
|
|
|
bool enet::Tcp::unlink() {
|
|
// prevent call while stoping ...
|
|
m_status = status::unlink;
|
|
if (m_socketId >= 0) {
|
|
ENET_INFO("Close socket (start)");
|
|
#ifdef __TARGET_OS__Windows
|
|
shutdown(m_socketId, SD_BOTH);
|
|
// Release hand of the socket to permit the Select to exit ... ==> otherwise it lock ...
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
closesocket(m_socketId);
|
|
m_socketId = INVALID_SOCKET;
|
|
#else
|
|
shutdown(m_socketId, SHUT_RDWR);
|
|
// Release hand of the socket to permit the Select to exit ... ==> otherwise it lock ...
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
close(m_socketId);
|
|
m_socketId = -1;
|
|
#endif
|
|
ENET_INFO("Close socket (done)");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int32_t enet::Tcp::read(void* _data, int32_t _maxLen) {
|
|
if (m_status != status::link) {
|
|
ENET_ERROR("Can not read on unlink connection");
|
|
return -1;
|
|
}
|
|
int32_t size = -1;
|
|
|
|
fd_set sock;
|
|
// Initialize the timeout to 3 minutes. If no activity after 3 minutes this program will end. timeout value is based on milliseconds.
|
|
struct timeval timeOutStruct;
|
|
timeOutStruct.tv_sec = (3 * 60 * 1000);
|
|
timeOutStruct.tv_usec = 0;
|
|
FD_ZERO(&sock);
|
|
FD_SET(m_socketId,&sock);
|
|
ENET_VERBOSE(" select ...");
|
|
int rc = select(m_socketId+1, &sock, NULL, NULL, &timeOutStruct);
|
|
ENET_VERBOSE(" select (done)");
|
|
// Check to see if the poll call failed.
|
|
if (rc < 0) {
|
|
ENET_ERROR(" select() failed");
|
|
return -1;
|
|
}
|
|
// Check to see if the 3 minute time out expired.
|
|
if (rc == 0) {
|
|
ENET_ERROR(" select() timed out.");
|
|
return -2;
|
|
}
|
|
if (!FD_ISSET(m_socketId, &sock)) {
|
|
ENET_ERROR(" select() id is not set...");
|
|
return -1;
|
|
}
|
|
bool closeConn = false;
|
|
// Receive all incoming data on this socket before we loop back and call poll again.
|
|
// Receive data on this connection until the recv fails with EWOULDBLOCK.
|
|
// If any other failure occurs, we will close the connection.
|
|
{
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
rc = recv(m_socketId, (char *)_data, _maxLen, 0);
|
|
}
|
|
if (rc < 0) {
|
|
if (errno != EWOULDBLOCK) {
|
|
ENET_ERROR(" recv() failed");
|
|
closeConn = true;
|
|
}
|
|
}
|
|
// Check to see if the connection has been closed by the client
|
|
if (rc == 0) {
|
|
ENET_INFO("Connection closed");
|
|
closeConn = true;
|
|
}
|
|
if (closeConn == false) {
|
|
// Data was received
|
|
size = rc;
|
|
} else {
|
|
// If the close_conn flag was turned on, we need to clean up this active connection.
|
|
// This clean up process includes removing the descriptor.
|
|
ENET_DEBUG(" Set status at remote close ...");
|
|
m_status = status::linkRemoteClose;
|
|
}
|
|
//#endif
|
|
return size;
|
|
}
|
|
|
|
|
|
int32_t enet::Tcp::write(const void* _data, int32_t _len) {
|
|
if (m_status != status::link) {
|
|
ENET_ERROR("Can not write on unlink connection");
|
|
return -1;
|
|
}
|
|
//ENET_DEBUG("write on socketid = " << m_socketId << " data@=" << int64_t(_data) << " size=" << _len );
|
|
int32_t size;
|
|
{
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
size = ::send(m_socketId, (const char *)_data, _len, 0);
|
|
}
|
|
if ( size != _len
|
|
&& errno != 0) {
|
|
ENET_ERROR("PB when writing data on the FD : request=" << _len << " have=" << size << ", erno=" << errno << "," << strerror(errno));
|
|
m_status = status::error;
|
|
return -1;
|
|
}
|
|
return size;
|
|
}
|