// %flair:license{ // This file is part of the Flair framework distributed under the // CECILL-C License, Version 1.0. // %flair:license} // created: 2015/04/28 // filename: TcpSocket.cpp // // author: Gildas Bayard // Copyright Heudiasyc UMR UTC/CNRS 7253 // // version: $Id: $ // // purpose: Class defining a TCP socket // // /*********************************************************************/ #include "TcpSocket.h" #include #include #include #include #include #include using std::string; namespace flair { namespace core { TcpSocket::TcpSocket(const Object *parent, const std::string name, bool _blockOnSend, bool _blockOnReceive) : ConnectedSocket(parent, name), isConnected(false) { blockOnSend = _blockOnSend; blockOnReceive = _blockOnReceive; } TcpSocket::~TcpSocket() { // Info("Debug: destroying TCP socket %s", ObjectName().c_str()); close(socket); } void TcpSocket::Listen(const unsigned int port, const std::string localAddress) { socket = ::socket(AF_INET, SOCK_STREAM, 0); sockaddr_in my_addr; my_addr.sin_family = AF_INET; my_addr.sin_port = htons(port); if (localAddress == "ANY") { my_addr.sin_addr.s_addr = INADDR_ANY; } else { inet_aton(localAddress.c_str(), &(my_addr.sin_addr)); } memset(&(my_addr.sin_zero), '\0', 8); if (bind(socket, (sockaddr *)&my_addr, sizeof(my_addr)) < 0) { char errorMsg[256]; Err("TCP bind, %s\n", strerror_r(errno, errorMsg, sizeof(errorMsg))); } listen(socket, 1); } TcpSocket *TcpSocket::Accept(Time timeout) { char errorMsg[256]; TcpSocket *acceptedSocket = nullptr; struct timeval tv; if (timeout != 0) { tv.tv_sec = timeout / 1000; // timeout is in ms tv.tv_usec = (timeout % 1000) * 1000; } fd_set rset; FD_ZERO(&rset); FD_SET(socket, &rset); int ret = select(socket + 1, &rset, nullptr, nullptr, (timeout == 0) ? nullptr : &tv); // man 2 accept if (ret < 0) { Err("select: %s\n", strerror_r(errno, errorMsg, sizeof(errorMsg))); } else { if (ret == 0) { // timeout reached // Err("timeout reached\n"); } else { // our socket is readable, a new connection can be accepted acceptedSocket = new TcpSocket(this->Parent(), this->ObjectName(), blockOnSend, blockOnReceive); sockaddr_in their_addr; socklen_t namelen = sizeof(their_addr); if ((acceptedSocket->socket = accept(socket, (sockaddr *)&their_addr, &namelen)) < 0) { Err("error: %s\n", strerror_r(errno, errorMsg, sizeof(errorMsg))); delete acceptedSocket; acceptedSocket = nullptr; } } } return acceptedSocket; } bool TcpSocket::Connect(const unsigned int distantPort, const std::string distantAddress, Time timeout) { bool success = false; char errorMsg[256]; if (isConnected) { if (this->distantPort == distantPort && this->distantAddress == distantAddress) { return true; } else { close(socket); } } socket = ::socket(AF_INET, SOCK_STREAM, 0); if (socket == -1) return false; sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(short(distantPort)); if (inet_pton(AF_INET, distantAddress.c_str(), &serv_addr.sin_addr) <= 0) { printf("incorrect network address."); close(socket); return false; } memset(&(serv_addr.sin_zero), '\0', 8); // go non blocking int flags = fcntl(socket, F_GETFL); fcntl(socket, F_SETFL, flags | O_NONBLOCK); if (connect(socket, (sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { if ((errno != EINPROGRESS) && (errno != EAGAIN)) { Err("socket connect: %s\n", strerror_r(errno, errorMsg, sizeof(errorMsg))); success = false; } else { // now block with a timeout struct timeval tv; if (timeout != 0) { tv.tv_sec = timeout / 1000; // timeout is in ms tv.tv_usec = (timeout % 1000) * 1000; } fd_set wset; FD_ZERO(&wset); FD_SET(socket, &wset); int ret = select(socket + 1, NULL, &wset, NULL, (timeout == 0) ? NULL : &tv); // man 2 connect EINPROGRESS if (ret < 0) { Err("select: %s\n", strerror_r(errno, errorMsg, sizeof(errorMsg))); success = false; } else { if (ret == 0) { // timeout reached // Err("timeout reached\n"); success = false; } else { // something happened on our socket. Check if an error occured int error; socklen_t len = sizeof(error); if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, &len) != 0) { // Err("getsockopt: // %s\n",strerror_r(errno,errorMsg,sizeof(errorMsg))); success = false; } else if (error != 0) { // Err("socket error: // %d(%s)\n",error,strerror_r(error,errorMsg,sizeof(errorMsg))); success = false; } else { if (connect(socket, (sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { success = false; } else { // Info("connected indeed ^^\n"); success = true; } } } } } } else { success = true; // never reached suprisingly... } // switch back to blocking mode (default) fcntl(socket, F_SETFL, flags); if (!success) { close(socket); // Info("Debug: Connect to %s:%d failed\n", distantAddress.c_str(), // distantPort); return false; } else { isConnected = true; this->distantPort = distantPort; this->distantAddress = distantAddress; // Info("Debug: Connect to %s:%d succeeded\n", distantAddress.c_str(), // distantPort); return true; } } ssize_t TcpSocket::SendMessage(const char *buffer, size_t bufferSize, Time timeout) { int flags = 0; if (!blockOnSend) { flags |= MSG_DONTWAIT; } else { struct timeval tv; tv.tv_sec = timeout / 1000; // timeout is in ms tv.tv_usec = (timeout % 1000) * 1000; setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); } ssize_t bytesSent = send(socket, buffer, bufferSize, flags); return bytesSent; } ssize_t TcpSocket::RecvMessage(char *buffer, size_t bufferSize, Time timeout) { int flags = 0; if (!blockOnReceive) { flags |= MSG_DONTWAIT; } else { struct timeval tv; tv.tv_sec = timeout / 1000; // timeout is in ms tv.tv_usec = (timeout % 1000) * 1000; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); } ssize_t bytesRead = recv(socket, buffer, bufferSize, flags); return bytesRead; } uint16_t TcpSocket::NetworkToHost16(uint16_t data) { return ntohs(data); } uint16_t TcpSocket::HostToNetwork16(uint16_t data) { return htons(data); } uint32_t TcpSocket::NetworkToHost32(uint32_t data) { return ntohl(data); } uint32_t TcpSocket::HostToNetwork32(uint32_t data) { return htonl(data); } } // end namespace core } // end namespace flair