//////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library // Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it freely, // subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; // you must not claim that you wrote the original software. // If you use this software in a product, an acknowledgment // in the product documentation would be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, // and must not be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro #endif namespace sf { //////////////////////////////////////////////////////////// /// Default constructor //////////////////////////////////////////////////////////// SocketTCP::SocketTCP() { Create(SocketHelper::InvalidSocket()); } //////////////////////////////////////////////////////////// /// Change the blocking state of the socket //////////////////////////////////////////////////////////// void SocketTCP::SetBlocking(bool blocking) { // Make sure our socket is valid if (!IsValid()) Create(); SocketHelper::SetBlocking(mySocket, blocking); myIsBlocking = blocking; } //////////////////////////////////////////////////////////// /// Connect to another computer on a specified port //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Connect(unsigned short port, const IpAddress& host, float timeout) { // Make sure our socket is valid if (!IsValid()) Create(); // Build the host address sockaddr_in sockAddr; memset(sockAddr.sin_zero, 0, sizeof(sockAddr.sin_zero)); sockAddr.sin_addr.s_addr = inet_addr(host.ToString().c_str()); sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(port); if (timeout <= 0) { // ----- We're not using a timeout : just try to connect ----- if (connect(mySocket, reinterpret_cast(&sockAddr), sizeof(sockAddr)) == -1) { // Failed to connect return SocketHelper::GetErrorStatus(); } // Connection succeeded return Socket::Done; } else { // ----- We're using a timeout : we'll need a few tricks to make it work ----- // Save the previous blocking state bool blocking = myIsBlocking; // Switch to non-blocking to enable our connection timeout if (blocking) SetBlocking(false); // Try to connect to host if (connect(mySocket, reinterpret_cast(&sockAddr), sizeof(sockAddr)) >= 0) { // We got instantly connected! (it may no happen a lot...) return Socket::Done; } // Get the error status Socket::Status status = SocketHelper::GetErrorStatus(); // If we were in non-blocking mode, return immediatly if (!blocking) return status; // Otherwise, wait until something happens to our socket (success, timeout or error) if (status == Socket::NotReady) { // Setup the selector fd_set selector; FD_ZERO(&selector); FD_SET(mySocket, &selector); // Setup the timeout timeval time; time.tv_sec = static_cast(timeout); time.tv_usec = (static_cast(timeout * 1000) % 1000) * 1000; // Wait for something to write on our socket (which means that the connection request has returned) if (select(static_cast(mySocket + 1), NULL, &selector, NULL, &time) > 0) { // At this point the connection may have been either accepted or refused. // To know whether it's a success or a failure, we try to retrieve the name of the connected peer SocketHelper::LengthType size = sizeof(sockAddr); if (getpeername(mySocket, reinterpret_cast(&sockAddr), &size) != -1) { // Connection accepted status = Socket::Done; } else { // Connection failed status = SocketHelper::GetErrorStatus(); } } else { // Failed to connect before timeout is over status = SocketHelper::GetErrorStatus(); } } // Switch back to blocking mode SetBlocking(true); return status; } } //////////////////////////////////////////////////////////// /// Listen to a specified port for incoming data or connections //////////////////////////////////////////////////////////// bool SocketTCP::Listen(unsigned short port) { // Make sure our socket is valid if (!IsValid()) Create(); // Build the address sockaddr_in sockAddr; memset(sockAddr.sin_zero, 0, sizeof(sockAddr.sin_zero)); sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(port); // Bind the socket to the specified port if (bind(mySocket, reinterpret_cast(&sockAddr), sizeof(sockAddr)) == -1) { // Not likely to happen, but... Err() << "Failed to bind socket to port " << port << std::endl; return false; } // Listen to the bound port if (listen(mySocket, 0) == -1) { // Oops, socket is deaf Err() << "Failed to listen to port " << port << std::endl; return false; } return true; } //////////////////////////////////////////////////////////// /// Wait for a connection (must be listening to a port). /// This function will block if the socket is blocking //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Accept(SocketTCP& connected, IpAddress* address) { // Address that will be filled with client informations sockaddr_in clientAddress; SocketHelper::LengthType length = sizeof(clientAddress); // Accept a new connection connected = accept(mySocket, reinterpret_cast(&clientAddress), &length); // Check errors if (!connected.IsValid()) { if (address) *address = IpAddress(); return SocketHelper::GetErrorStatus(); } // Fill address if requested if (address) *address = IpAddress(inet_ntoa(clientAddress.sin_addr)); return Socket::Done; } //////////////////////////////////////////////////////////// /// Send an array of bytes to the host (must be connected first) //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Send(const char* data, std::size_t sizeInBytes) { // First check that socket is valid if (!IsValid()) return Socket::Error; // Check parameters if (data && sizeInBytes) { // Loop until every byte has been sent int sent = 0; int sizeToSend = static_cast(sizeInBytes); for (int length = 0; length < sizeToSend; length += sent) { // Send a chunk of data sent = send(mySocket, data + length, sizeToSend - length, 0); // Check if an error occured if (sent <= 0) return SocketHelper::GetErrorStatus(); } return Socket::Done; } else { // Error... Err() << "Cannot send data over the network (invalid parameters)" << std::endl; return Socket::Error; } } //////////////////////////////////////////////////////////// /// Receive an array of bytes from the host (must be connected first). /// This function will block if the socket is blocking //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Receive(char* data, std::size_t maxSize, std::size_t& sizeReceived) { // First clear the size received sizeReceived = 0; // Check that socket is valid if (!IsValid()) return Socket::Error; // Check parameters if (data && maxSize) { // Receive a chunk of bytes int received = recv(mySocket, data, static_cast(maxSize), 0); // Check the number of bytes received if (received > 0) { sizeReceived = static_cast(received); return Socket::Done; } else if (received == 0) { return Socket::Disconnected; } else { return SocketHelper::GetErrorStatus(); } } else { // Error... Err() << "Cannot receive data from the network (invalid parameters)" << std::endl; return Socket::Error; } } //////////////////////////////////////////////////////////// /// Send a packet of data to the host (must be connected first) //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Send(Packet& packet) { // Get the data to send from the packet std::size_t dataSize = 0; const char* data = packet.OnSend(dataSize); // Send the packet size Uint32 packetSize = htonl(static_cast(dataSize)); Send(reinterpret_cast(&packetSize), sizeof(packetSize)); // Send the packet data if (packetSize > 0) { return Send(data, dataSize); } else { return Socket::Done; } } //////////////////////////////////////////////////////////// /// Receive a packet from the host (must be connected first). /// This function will block if the socket is blocking //////////////////////////////////////////////////////////// Socket::Status SocketTCP::Receive(Packet& packet) { // We start by getting the size of the incoming packet Uint32 packetSize = 0; std::size_t received = 0; if (myPendingPacketSize < 0) { // Loop until we've received the entire size of the packet // (even a 4 bytes variable may be received in more than one call) while (myPendingHeaderSize < sizeof(myPendingHeader)) { char* data = reinterpret_cast(&myPendingHeader) + myPendingHeaderSize; Socket::Status status = Receive(data, sizeof(myPendingHeader) - myPendingHeaderSize, received); myPendingHeaderSize += received; if (status != Socket::Done) return status; } packetSize = ntohl(myPendingHeader); myPendingHeaderSize = 0; } else { // There is a pending packet : we already know its size packetSize = myPendingPacketSize; } // Then loop until we receive all the packet data char buffer[1024]; while (myPendingPacket.size() < packetSize) { // Receive a chunk of data std::size_t sizeToGet = std::min(static_cast(packetSize - myPendingPacket.size()), sizeof(buffer)); Socket::Status status = Receive(buffer, sizeToGet, received); if (status != Socket::Done) { // We must save the size of the pending packet until we can receive its content if (status == Socket::NotReady) myPendingPacketSize = packetSize; return status; } // Append it into the packet if (received > 0) { myPendingPacket.resize(myPendingPacket.size() + received); char* begin = &myPendingPacket[0] + myPendingPacket.size() - received; memcpy(begin, buffer, received); } } // We have received all the datas : we can copy it to the user packet, and clear our internal packet packet.Clear(); if (!myPendingPacket.empty()) packet.OnReceive(&myPendingPacket[0], myPendingPacket.size()); myPendingPacket.clear(); myPendingPacketSize = -1; return Socket::Done; } //////////////////////////////////////////////////////////// /// Close the socket //////////////////////////////////////////////////////////// bool SocketTCP::Close() { if (IsValid()) { if (!SocketHelper::Close(mySocket)) { Err() << "Failed to close socket" << std::endl; return false; } mySocket = SocketHelper::InvalidSocket(); } myIsBlocking = true; return true; } //////////////////////////////////////////////////////////// /// Check if the socket is in a valid state ; this function /// can be called any time to check if the socket is OK //////////////////////////////////////////////////////////// bool SocketTCP::IsValid() const { return mySocket != SocketHelper::InvalidSocket(); } //////////////////////////////////////////////////////////// /// Comparison operator == //////////////////////////////////////////////////////////// bool SocketTCP::operator ==(const SocketTCP& other) const { return mySocket == other.mySocket; } //////////////////////////////////////////////////////////// /// Comparison operator != //////////////////////////////////////////////////////////// bool SocketTCP::operator !=(const SocketTCP& other) const { return mySocket != other.mySocket; } //////////////////////////////////////////////////////////// /// Comparison operator <. /// Provided for compatibility with standard containers, as /// comparing two sockets doesn't make much sense... //////////////////////////////////////////////////////////// bool SocketTCP::operator <(const SocketTCP& other) const { return mySocket < other.mySocket; } //////////////////////////////////////////////////////////// /// Construct the socket from a socket descriptor /// (for internal use only) //////////////////////////////////////////////////////////// SocketTCP::SocketTCP(SocketHelper::SocketType descriptor) { Create(descriptor); } //////////////////////////////////////////////////////////// /// Create the socket //////////////////////////////////////////////////////////// void SocketTCP::Create(SocketHelper::SocketType descriptor) { // Use the given socket descriptor, or get a new one mySocket = descriptor ? descriptor : socket(PF_INET, SOCK_STREAM, 0); myIsBlocking = true; // Reset the pending packet myPendingHeaderSize = 0; myPendingPacket.clear(); myPendingPacketSize = -1; // Setup default options if (IsValid()) { // To avoid the "Address already in use" error message when trying to bind to the same port int yes = 1; if (setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)) == -1) { Err() << "Failed to set socket option \"SO_REUSEADDR\" ; " << "binding to a same port may fail if too fast" << std::endl; } // Disable the Nagle algorithm (ie. removes buffering of TCP packets) if (setsockopt(mySocket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), sizeof(yes)) == -1) { Err() << "Failed to set socket option \"TCP_NODELAY\" ; " << "all your TCP packets will be buffered" << std::endl; } // Set blocking by default (should always be the case anyway) SetBlocking(true); } } } // namespace sf