From 390ebfd6fa1f2e99a2d922ae5969607bbb9ed379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 28 Sep 2013 04:23:59 -0300 Subject: [PATCH] Added EE::Network module. --- .hgignore | 1 + Makefile.base | 20 +- include/eepp/audio/csoundstream.hpp | 8 +- include/eepp/base/string.hpp | 85 ++-- include/eepp/ee.hpp | 9 +- include/eepp/graphics/cconsole.hpp | 6 +- include/eepp/graphics/cshader.hpp | 62 +-- include/eepp/network.hpp | 15 + include/eepp/network/base.hpp | 31 ++ include/eepp/network/cftp.hpp | 384 +++++++++++++++ include/eepp/network/chttp.hpp | 301 ++++++++++++ include/eepp/network/cipaddress.hpp | 185 +++++++ include/eepp/network/cpacket.hpp | 295 +++++++++++ include/eepp/network/csocket.hpp | 124 +++++ include/eepp/network/csocketselector.hpp | 179 +++++++ include/eepp/network/ctcplistener.hpp | 98 ++++ include/eepp/network/ctcpsocket.hpp | 190 +++++++ include/eepp/network/cudpsocket.hpp | 191 ++++++++ include/eepp/network/sockethandle.hpp | 22 + include/eepp/system/ctime.hpp | 146 +++--- premake4.lua | 15 +- projects/android-project/jni/Android.mk | 3 + projects/linux/ee.creator.user | 96 +++- projects/linux/ee.files | 29 ++ projects/mingw32/make.sh | 6 +- src/eepp/network/cftp.cpp | 463 ++++++++++++++++++ src/eepp/network/chttp.cpp | 271 ++++++++++ src/eepp/network/cipaddress.cpp | 171 +++++++ src/eepp/network/cpacket.cpp | 353 +++++++++++++ src/eepp/network/csocket.cpp | 86 ++++ src/eepp/network/csocketselector.cpp | 84 ++++ src/eepp/network/ctcplistener.cpp | 77 +++ src/eepp/network/ctcpsocket.cpp | 291 +++++++++++ src/eepp/network/cudpsocket.cpp | 137 ++++++ src/eepp/network/platform/platformimpl.hpp | 14 + .../network/platform/unix/csocketimpl.cpp | 58 +++ .../network/platform/unix/csocketimpl.hpp | 53 ++ src/eepp/network/platform/win/csocketimpl.cpp | 65 +++ src/eepp/network/platform/win/csocketimpl.hpp | 56 +++ src/examples/http_request/http_request.cpp | 28 ++ 40 files changed, 4532 insertions(+), 176 deletions(-) create mode 100644 include/eepp/network.hpp create mode 100644 include/eepp/network/base.hpp create mode 100644 include/eepp/network/cftp.hpp create mode 100644 include/eepp/network/chttp.hpp create mode 100644 include/eepp/network/cipaddress.hpp create mode 100644 include/eepp/network/cpacket.hpp create mode 100644 include/eepp/network/csocket.hpp create mode 100644 include/eepp/network/csocketselector.hpp create mode 100644 include/eepp/network/ctcplistener.hpp create mode 100644 include/eepp/network/ctcpsocket.hpp create mode 100644 include/eepp/network/cudpsocket.hpp create mode 100644 include/eepp/network/sockethandle.hpp create mode 100644 src/eepp/network/cftp.cpp create mode 100644 src/eepp/network/chttp.cpp create mode 100644 src/eepp/network/cipaddress.cpp create mode 100644 src/eepp/network/cpacket.cpp create mode 100644 src/eepp/network/csocket.cpp create mode 100644 src/eepp/network/csocketselector.cpp create mode 100644 src/eepp/network/ctcplistener.cpp create mode 100644 src/eepp/network/ctcpsocket.cpp create mode 100644 src/eepp/network/cudpsocket.cpp create mode 100644 src/eepp/network/platform/platformimpl.hpp create mode 100644 src/eepp/network/platform/unix/csocketimpl.cpp create mode 100644 src/eepp/network/platform/unix/csocketimpl.hpp create mode 100644 src/eepp/network/platform/win/csocketimpl.cpp create mode 100644 src/eepp/network/platform/win/csocketimpl.hpp create mode 100644 src/examples/http_request/http_request.cpp diff --git a/.hgignore b/.hgignore index a46447556..0f264dfbb 100644 --- a/.hgignore +++ b/.hgignore @@ -26,6 +26,7 @@ eevbo-fbo-batch* eephysics* eestrobe* eeiv* +eehttp-request* ee.tag log.log external_projects.lua diff --git a/Makefile.base b/Makefile.base index 72c1ec0ae..90ff872fd 100644 --- a/Makefile.base +++ b/Makefile.base @@ -401,7 +401,7 @@ ifeq ($(BUILD_OS), linux) DYLIBS = -lrt -lpthread -lX11 -lopenal -lGL -lXcursor $(LIBFREETYPE2) $(DYLIBS_BACKENDS) LIBS = $(DYLIBS) $(LIBSNDFILE) $(STATIC_LIBS) OTHERINC += $(INCFREETYPE2) -PLATFORMSRC = $(wildcard ./src/eepp/window/platform/x11/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/window/platform/x11/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) $(wildcard ./src/eepp/network/platform/unix/*.cpp) else @@ -409,7 +409,7 @@ ifeq ($(BUILD_OS), darwin) LIBS = -framework OpenGL -framework OpenAL -framework CoreFoundation -framework AGL $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) OTHERINC += $(INCFREETYPE2) -I/usr/local/include/freetype2 -PLATFORMSRC = $(wildcard ./src/eepp/window/platform/osx/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/window/platform/osx/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) $(wildcard ./src/eepp/network/platform/unix/*.cpp) else @@ -417,7 +417,7 @@ ifeq ($(BUILD_OS), haiku) LIBS = -lopenal -lGL $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(LIBFREETYPE2) OTHERINC += $(INCFREETYPE2) -PLATFORMSRC = $(wildcard ./src/eepp/system/platform/posix/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/system/platform/posix/*.cpp) $(wildcard ./src/eepp/network/platform/unix/*.cpp) else @@ -425,24 +425,24 @@ ifeq ($(BUILD_OS), freebsd) LIBS = -lrt -lpthread -lX11 -lopenal -lGL -lXcursor $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) OTHERINC += $(INCFREETYPE2) -PLATFORMSRC = $(wildcard ./src/eepp/window/platform/x11/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/window/platform/x11/*.cpp) $(wildcard ./src/eepp/system/platform/posix/*.cpp) $(wildcard ./src/eepp/network/platform/unix/*.cpp) else ifeq ($(BUILD_OS), mingw32) -LIBS = -lOpenAL32 -lopengl32 -lmingw32 -lglu32 -lgdi32 -static-libgcc -static-libstdc++ -mwindows $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) +LIBS = -lOpenAL32 -lopengl32 -lmingw32 -lglu32 -lgdi32 -lws2_32 -static-libgcc -static-libstdc++ -mwindows $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) OTHERINC += $(INCFREETYPE2) -PLATFORMSRC = $(wildcard ./src/eepp/window/platform/win/*.cpp) $(wildcard ./src/eepp/system/platform/win/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/window/platform/win/*.cpp) $(wildcard ./src/eepp/system/platform/win/*.cpp) $(wildcard ./src/eepp/network/platform/win/*.cpp) else #if it is cygwin ifneq (,$(findstring cygwin,$(BUILD_OS))) -LIBS = -lOpenAL32 -lmingw32 -lopengl32 -lglu32 -lgdi32 -static-libgcc -mwindows $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) +LIBS = -lOpenAL32 -lmingw32 -lopengl32 -lglu32 -lgdi32 -lws2_32 -static-libgcc -mwindows $(LIBSNDFILE) $(SDL_BACKEND_LINK) $(SDL2_BACKEND_LINK) $(SFML_BACKEND_LINK) $(LIBFREETYPE2) OTHERINC += -I./src/eepp/helper/zlib $(INCFREETYPE2) -PLATFORMSRC = $(wildcard ./src/eepp/window/platform/win/*.cpp) $(wildcard ./src/eepp/system/platform/win/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/window/platform/win/*.cpp) $(wildcard ./src/eepp/system/platform/win/*.cpp) $(wildcard ./src/eepp/network/platform/win/*.cpp) else @@ -460,7 +460,7 @@ ifeq ($(ARCH),armv7) OTHERINC += -DU_HAVE_GCC_ATOMICS=0 endif -PLATFORMSRC = $(wildcard ./src/eepp/system/platform/posix/*.cpp) +PLATFORMSRC = $(wildcard ./src/eepp/system/platform/posix/*.cpp) $(wildcard ./src/eepp/network/platform/unix/*.cpp) endif #endif ios @@ -513,7 +513,7 @@ else endif SRCHELPERS = $(SRCFREETYPE) $(SRCGLEW) $(wildcard ./src/eepp/helper/SOIL2/src/SOIL2/*.c) $(wildcard ./src/eepp/helper/stb_vorbis/*.c) $(wildcard ./src/eepp/helper/zlib/*.c) $(wildcard ./src/eepp/helper/libzip/*.c) $(wildcard ./src/eepp/helper/chipmunk/*.c) $(wildcard ./src/eepp/helper/chipmunk/constraints/*.c) -SRCMODULES = $(wildcard ./src/eepp/helper/imageresampler/*.cpp) $(wildcard ./src/eepp/helper/jpeg-compressor/*.cpp) $(wildcard ./src/eepp/helper/haikuttf/*.cpp) $(wildcard ./src/eepp/base/*.cpp) $(wildcard ./src/eepp/audio/*.cpp) $(wildcard ./src/eepp/gaming/*.cpp) $(wildcard ./src/eepp/gaming/mapeditor/*.cpp) $(wildcard ./src/eepp/graphics/*.cpp) $(wildcard ./src/eepp/graphics/renderer/*.cpp) $(wildcard ./src/eepp/math/*.cpp) $(wildcard ./src/eepp/system/*.cpp) $(wildcard ./src/eepp/ui/*.cpp) $(wildcard ./src/eepp/ui/tools/*.cpp) $(wildcard ./src/eepp/utils/*.cpp) $(wildcard ./src/eepp/window/*.cpp) $(wildcard ./src/eepp/window/backend/null/*.cpp) $(wildcard ./src/eepp/window/platform/null/*.cpp) $(SDL_BACKEND_SRC) $(SDL2_BACKEND_SRC) $(SFML_BACKEND_SRC) $(PLATFORMSRC) $(wildcard ./src/eepp/physics/*.cpp) $(wildcard ./src/eepp/physics/constraints/*.cpp) +SRCMODULES = $(wildcard ./src/eepp/helper/imageresampler/*.cpp) $(wildcard ./src/eepp/helper/jpeg-compressor/*.cpp) $(wildcard ./src/eepp/helper/haikuttf/*.cpp) $(wildcard ./src/eepp/base/*.cpp) $(wildcard ./src/eepp/audio/*.cpp) $(wildcard ./src/eepp/gaming/*.cpp) $(wildcard ./src/eepp/gaming/mapeditor/*.cpp) $(wildcard ./src/eepp/graphics/*.cpp) $(wildcard ./src/eepp/graphics/renderer/*.cpp) $(wildcard ./src/eepp/math/*.cpp) $(wildcard ./src/eepp/system/*.cpp) $(wildcard ./src/eepp/network/*.cpp) $(wildcard ./src/eepp/ui/*.cpp) $(wildcard ./src/eepp/ui/tools/*.cpp) $(wildcard ./src/eepp/utils/*.cpp) $(wildcard ./src/eepp/window/*.cpp) $(wildcard ./src/eepp/window/backend/null/*.cpp) $(wildcard ./src/eepp/window/platform/null/*.cpp) $(SDL_BACKEND_SRC) $(SDL2_BACKEND_SRC) $(SFML_BACKEND_SRC) $(PLATFORMSRC) $(wildcard ./src/eepp/physics/*.cpp) $(wildcard ./src/eepp/physics/constraints/*.cpp) OBJHELPERS = $(SRCHELPERS:.c=.o) OBJMODULES = $(SRCMODULES:.cpp=.o) diff --git a/include/eepp/audio/csoundstream.hpp b/include/eepp/audio/csoundstream.hpp index 742371c57..2ea629cc7 100755 --- a/include/eepp/audio/csoundstream.hpp +++ b/include/eepp/audio/csoundstream.hpp @@ -102,10 +102,10 @@ class EE_API cSoundStream : private cThread, private cSound { */ bool FillAndPushBuffer( const unsigned int& Buffer ); - /** Fill the buffers queue with all available buffers - * @return True if the derived class has requested to stop - */ - bool FillQueue(); + /** Fill the buffers queue with all available buffers + * @return True if the derived class has requested to stop + */ + bool FillQueue(); void ClearQueue(); diff --git a/include/eepp/base/string.hpp b/include/eepp/base/string.hpp index 683e157af..ca562aad5 100644 --- a/include/eepp/base/string.hpp +++ b/include/eepp/base/string.hpp @@ -681,45 +681,46 @@ EE_API String operator +( const String& left, const String& right ); #endif -/** @class EE::String -** EE::String is a utility string class defined mainly for -** convenience. It is a Unicode string (implemented using -** UTF-32), thus it can store any character in the world -** (european, chinese, arabic, hebrew, etc.). -** It automatically handles conversions from/to ANSI and -** wide strings, so that you can work with standard string -** classes and still be compatible with functions taking a -** EE::String. -** @code -** EE::String s; -** std::string s1 = s; // automatically converted to ANSI string -** String s2 = s; // automatically converted to wide string -** s = "hello"; // automatically converted from ANSI string -** s = L"hello"; // automatically converted from wide string -** s += 'a'; // automatically converted from ANSI string -** s += L'a'; // automatically converted from wide string -** @endcode -** Conversions involving ANSI strings use the default user locale. However -** it is possible to use a custom locale if necessary: -** @code -** std::locale locale; -** EE::String s; -** ... -** std::string s1 = s.ToAnsiString(locale); -** s = EE::String("hello", locale); -** @endcode -** -** EE::String defines the most important functions of the -** standard std::string class: removing, random access, iterating, -** appending, comparing, etc. However it is a simple class -** provided for convenience, and you may have to consider using -** a more optimized class if your program requires complex string -** handling. The automatic conversion functions will then take -** care of converting your string to EE::String whenever EE -** requires it. -** -** Please note that EE also defines a low-level, generic -** interface for Unicode handling, see the EE::Utf classes. -** -** All credits to Laurent Gomila, i just modified and expanded a little bit the implementation. -**/ +/** +@class EE::String +EE::String is a utility string class defined mainly for +convenience. It is a Unicode string (implemented using +UTF-32), thus it can store any character in the world +(european, chinese, arabic, hebrew, etc.). +It automatically handles conversions from/to ANSI and +wide strings, so that you can work with standard string +classes and still be compatible with functions taking a +EE::String. +@code +EE::String s; +std::string s1 = s; // automatically converted to ANSI string +String s2 = s; // automatically converted to wide string +s = "hello"; // automatically converted from ANSI string +s = L"hello"; // automatically converted from wide string +s += 'a'; // automatically converted from ANSI string +s += L'a'; // automatically converted from wide string +@endcode +Conversions involving ANSI strings use the default user locale. However +it is possible to use a custom locale if necessary: +@code +std::locale locale; +EE::String s; +... +std::string s1 = s.ToAnsiString(locale); +s = EE::String("hello", locale); +@endcode + +EE::String defines the most important functions of the +standard std::string class: removing, random access, iterating, +appending, comparing, etc. However it is a simple class +provided for convenience, and you may have to consider using +a more optimized class if your program requires complex string +handling. The automatic conversion functions will then take +care of converting your string to EE::String whenever EE +requires it. + +Please note that EE also defines a low-level, generic +interface for Unicode handling, see the EE::Utf classes. + +All credits to Laurent Gomila, i just modified and expanded a little bit the implementation. +*/ diff --git a/include/eepp/ee.hpp b/include/eepp/ee.hpp index 03b39895c..8c5eb2984 100755 --- a/include/eepp/ee.hpp +++ b/include/eepp/ee.hpp @@ -32,12 +32,13 @@ EE::Graphics documented. EE::Audio documented. EE::Math documented. + EE::Network documented. EE::UI Not documented at all. EE::Physics Not documented at all, chipmunk documentation should help. EE::Gaming Not documented at all. @TODO Add more commented examples, showing at least the basic usage of the engine ( 10 or more examples at least ). - STATE: 6 examples available. + STATE: 7 examples available. @TODO Improve the map editor ( add triggers, tiles selection to copy paste in other zones of the map, undo/redo actions ). STATE: Needs at least to reoffset tiles and objects for the map resizing. @@ -56,7 +57,7 @@ Middle-term plans: @TODO Add Networking support. - STATE: I'll use SFML network adapted to the engine coding style. + STATE: DONE Long-term plans: @@ -99,6 +100,10 @@ #include using namespace EE::Graphics; + // Network + #include + using namespace EE::Network; + // UI #include using namespace EE::UI; diff --git a/include/eepp/graphics/cconsole.hpp b/include/eepp/graphics/cconsole.hpp index 585df7493..2939b9dcc 100755 --- a/include/eepp/graphics/cconsole.hpp +++ b/include/eepp/graphics/cconsole.hpp @@ -209,7 +209,7 @@ class EE_API cConsole : protected iLogReader { /** Internal Callback for default command ( setvolume ) */ void CmdSetVolume( const std::vector < String >& params ); - /** Internal Callback for default command ( getgpuextensions ) */ + /** Internal Callback for default command ( getgpuextensions ) */ void CmdGetGpuExtensions( const std::vector < String >& params ); /** Internal Callback for default command ( dir and ls ) */ @@ -233,8 +233,8 @@ class EE_API cConsole : protected iLogReader { /** Add the current log to the console */ void CmdGetLog(); - /** Add the GPU Extensions supported to the console */ - void CmdGetGpuExtensions(); + /** Add the GPU Extensions supported to the console */ + void CmdGetGpuExtensions(); /** Internal Callback to Process the new line ( when return pressed ) */ void ProcessLine(); diff --git a/include/eepp/graphics/cshader.hpp b/include/eepp/graphics/cshader.hpp index 13af0c2e0..1d7072bff 100644 --- a/include/eepp/graphics/cshader.hpp +++ b/include/eepp/graphics/cshader.hpp @@ -15,55 +15,55 @@ class EE_API cShader { static bool Ensure(); /** Constructor with type of shader, next you'll need to set the source and compile it. */ - cShader( const Uint32& Type ); + cShader( const Uint32& Type ); - /** Create a type of shader and load the shader from a file, and compile it. */ - cShader( const Uint32& Type, const std::string& Filename ); + /** Create a type of shader and load the shader from a file, and compile it. */ + cShader( const Uint32& Type, const std::string& Filename ); - /** Create a type of shader from memory, and compile it. */ + /** Create a type of shader from memory, and compile it. */ cShader( const Uint32& Type, const char * Data, const Uint32& DataSize ); - /** Create a type of shader loaded from a pack file */ - cShader( const Uint32& Type, cPack * Pack, const std::string& Filename ); + /** Create a type of shader loaded from a pack file */ + cShader( const Uint32& Type, cPack * Pack, const std::string& Filename ); /** Create a type of shader from memory, and compile it. */ cShader( const Uint32& Type, const char ** Data, const Uint32& NumLines ); - virtual ~cShader(); + virtual ~cShader(); - /** Set the shader source */ - void SetSource( const std::string& Source ); + /** Set the shader source */ + void SetSource( const std::string& Source ); - /** Set the shader source */ - void SetSource( const std::vector& Source ); + /** Set the shader source */ + void SetSource( const std::vector& Source ); - /** Set the shader source */ + /** Set the shader source */ void SetSource( const char * Data, const Uint32& DataSize ); /** Set the shader source */ void SetSource( const char** Data, const Uint32& NumLines ); - /** Compile the shader */ - bool Compile(); + /** Compile the shader */ + bool Compile(); - /** @return If the shader is valid */ + /** @return If the shader is valid */ bool IsValid() const; - /** @return If the shader is compiled */ + /** @return If the shader is compiled */ bool IsCompiled() const; - /** @return The log of the compilation */ + /** @return The log of the compilation */ std::string CompileLog() const; - /** @return The Shader Type */ + /** @return The Shader Type */ Uint32 GetType() const; - /** @return The Shader Id */ + /** @return The Shader Id */ Uint32 GetId() const; - /** Reloads the Shader. */ - void Reload(); - protected: + /** Reloads the Shader. */ + void Reload(); + protected: friend class cRendererGL3; static bool sEnsure; Uint32 mGLId; @@ -71,10 +71,10 @@ class EE_API cShader { std::string mFilename; std::string mCompileLog; std::string mSource; - bool mValid; - bool mCompiled; + bool mValid; + bool mCompiled; - void Init( const Uint32& Type ); + void Init( const Uint32& Type ); std::string GetName(); @@ -84,20 +84,20 @@ class EE_API cShader { /** @brief Prebuild Vertex Shader class */ class EE_API cVertexShader : public cShader { public: - cVertexShader(); - cVertexShader( const std::string& Filename ); + cVertexShader(); + cVertexShader( const std::string& Filename ); cVertexShader( const char * Data, const Uint32& DataSize ); - cVertexShader( cPack * Pack, const std::string& Filename ); + cVertexShader( cPack * Pack, const std::string& Filename ); cVertexShader( const char ** Data, const Uint32& NumLines ); }; /** @brief Prebuild Fragment Shader class */ class EE_API cFragmentShader : public cShader { public: - cFragmentShader(); - cFragmentShader( const std::string& Filename ); + cFragmentShader(); + cFragmentShader( const std::string& Filename ); cFragmentShader( const char * Data, const Uint32& DataSize ); - cFragmentShader( cPack * Pack, const std::string& Filename ); + cFragmentShader( cPack * Pack, const std::string& Filename ); cFragmentShader( const char ** Data, const Uint32& NumLines ); }; diff --git a/include/eepp/network.hpp b/include/eepp/network.hpp new file mode 100644 index 000000000..99becf109 --- /dev/null +++ b/include/eepp/network.hpp @@ -0,0 +1,15 @@ +#ifndef EEPP_NETWORK_HPP +#define EEPP_NETWORK_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/eepp/network/base.hpp b/include/eepp/network/base.hpp new file mode 100644 index 000000000..c17671716 --- /dev/null +++ b/include/eepp/network/base.hpp @@ -0,0 +1,31 @@ +#ifndef EE_NETWORK_BASE +#define EE_NETWORK_BASE + +#include + +/** +This module is based on the sfml-network module. + +SFML - Copyright (c) 2007-2013 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. +*/ +#endif diff --git a/include/eepp/network/cftp.hpp b/include/eepp/network/cftp.hpp new file mode 100644 index 000000000..b3a2ff548 --- /dev/null +++ b/include/eepp/network/cftp.hpp @@ -0,0 +1,384 @@ +#ifndef EE_NETWORKCFTP_HPP +#define EE_NETWORKCFTP_HPP + +#include +#include +#include +#include +#include +#include + +using namespace EE::System; + +namespace EE { namespace Network { + +class cIpAddress; + +/** @brief A FTP client */ +class EE_API cFtp : NonCopyable { +public: + /** @brief Enumeration of transfer modes */ + enum TransferMode + { + Binary, ///< Binary mode (file is transfered as a sequence of bytes) + Ascii, ///< Text mode using ASCII encoding + Ebcdic ///< Text mode using EBCDIC encoding + }; + + /** @brief Define a FTP response */ + class EE_API Response + { + public: + /** @brief Status codes possibly returned by a FTP response */ + enum Status + { + // 1xx: the requested action is being initiated, + // expect another reply before proceeding with a new command + RestartMarkerReply = 110, ///< Restart marker reply + ServiceReadySoon = 120, ///< Service ready in N minutes + DataConnectionAlreadyOpened = 125, ///< Data connection already opened, transfer starting + OpeningDataConnection = 150, ///< File status ok, about to open data connection + + // 2xx: the requested action has been successfully completed + Ok = 200, ///< Command ok + PointlessCommand = 202, ///< Command not implemented + SystemStatus = 211, ///< System status, or system help reply + DirectoryStatus = 212, ///< Directory status + FileStatus = 213, ///< File status + HelpMessage = 214, ///< Help message + SystemType = 215, ///< NAME system type, where NAME is an official system name from the list in the Assigned Numbers document + ServiceReady = 220, ///< Service ready for new user + ClosingConnection = 221, ///< Service closing control connection + DataConnectionOpened = 225, ///< Data connection open, no transfer in progress + ClosingDataConnection = 226, ///< Closing data connection, requested file action successful + EnteringPassiveMode = 227, ///< Entering passive mode + LoggedIn = 230, ///< User logged in, proceed. Logged out if appropriate + FileActionOk = 250, ///< Requested file action ok + DirectoryOk = 257, ///< PATHNAME created + + // 3xx: the command has been accepted, but the requested action + // is dormant, pending receipt of further information + NeedPassword = 331, ///< User name ok, need password + NeedAccountToLogIn = 332, ///< Need account for login + NeedInformation = 350, ///< Requested file action pending further information + + // 4xx: the command was not accepted and the requested action did not take place, + // but the error condition is temporary and the action may be requested again + ServiceUnavailable = 421, ///< Service not available, closing control connection + DataConnectionUnavailable = 425, ///< Can't open data connection + TransferAborted = 426, ///< Connection closed, transfer aborted + FileActionAborted = 450, ///< Requested file action not taken + LocalError = 451, ///< Requested action aborted, local error in processing + InsufficientStorageSpace = 452, ///< Requested action not taken; insufficient storage space in system, file unavailable + + // 5xx: the command was not accepted and + // the requested action did not take place + CommandUnknown = 500, ///< Syntax error, command unrecognized + ParametersUnknown = 501, ///< Syntax error in parameters or arguments + CommandNotImplemented = 502, ///< Command not implemented + BadCommandSequence = 503, ///< Bad sequence of commands + ParameterNotImplemented = 504, ///< Command not implemented for that parameter + NotLoggedIn = 530, ///< Not logged in + NeedAccountToStore = 532, ///< Need account for storing files + FileUnavailable = 550, ///< Requested action not taken, file unavailable + PageTypeUnknown = 551, ///< Requested action aborted, page type unknown + NotEnoughMemory = 552, ///< Requested file action aborted, exceeded storage allocation + FilenameNotAllowed = 553, ///< Requested action not taken, file name not allowed + + // 10xx: Custom codes + InvalidResponse = 1000, ///< Response is not a valid FTP one + ConnectionFailed = 1001, ///< Connection with server failed + ConnectionClosed = 1002, ///< Connection with server closed + InvalidFile = 1003 ///< Invalid file to upload / download + }; + + /** @brief Default constructor + ** + ** This constructor is used by the FTP client to build + ** the response. + ** + ** @param code Response status code + ** @param message Response message */ + explicit Response(Status code = InvalidResponse, const std::string& message = ""); + + + /** @brief Check if the status code means a success + ** + ** This function is defined for convenience, it is + ** equivalent to testing if the status code is < 400. + ** + ** @return True if the status is a success, false if it is a failure */ + bool IsOk() const; + + + /** @brief Get the status code of the response + ** + ** @return Status code */ + Status GetStatus() const; + + /** @brief Get the full message contained in the response + ** @return The response message */ + const std::string& GetMessage() const; + private: + // Member data + Status mStatus; ///< Status code returned from the server + std::string mMessage; ///< Last message received from the server + }; + + /** @brief Specialization of FTP response returning a directory */ + class EE_API DirectoryResponse : public Response { + public: + /** @brief Default constructor + ** @param response Source response */ + DirectoryResponse(const Response& response); + + + /** @brief Get the directory returned in the response + ** @return Directory name */ + const std::string& GetDirectory() const; + private: + // Member data + std::string mDirectory; ///< Directory extracted from the response message + }; + + /** @brief Specialization of FTP response returning a filename lisiting */ + class EE_API ListingResponse : public Response + { + public: + /** @brief Default constructor + ** + ** @param response Source response + ** @param data Data containing the raw listing */ + ListingResponse(const Response& response, const std::vector& data); + + + /** @brief Return the array of directory/file names + ** + ** @return Array containing the requested listing */ + const std::vector& getListing() const; + private: + // Member data + std::vector mListing; ///< Directory/file names extracted from the data + }; + + /** @brief Destructor + ** Automatically closes the connection with the server if + ** it is still opened. */ + ~cFtp(); + + + /** @brief Connect to the specified FTP server + ** The port has a default value of 21, which is the standard + ** port used by the FTP protocol. You shouldn't use a different + ** value, unless you really know what you do. + ** This function tries to connect to the server so it may take + ** a while to complete, especially if the server is not + ** reachable. To avoid blocking your application for too long, + ** you can use a timeout. The default value, cTime::Zero, means that the + ** system timeout will be used (which is usually pretty long). + ** @param server Name or address of the FTP server to connect to + ** @param port Port used for the connection + ** @param timeout Maximum time to wait + ** @return Server response to the request + ** @see Disconnect */ + Response Connect(const cIpAddress& server, unsigned short port = 21, cTime timeout = cTime::Zero); + + /** @brief Close the connection with the server + ** @return Server response to the request + ** @see Connect */ + Response Disconnect(); + + /** @brief Log in using an anonymous account + ** Logging in is mandatory after connecting to the server. + ** Users that are not logged in cannot perform any operation. + ** @return Server response to the request */ + Response Login(); + + + /** @brief Log in using a username and a password + ** Logging in is mandatory after connecting to the server. + ** Users that are not logged in cannot perform any operation. + ** @param name User name + ** @param password Password + ** @return Server response to the request */ + Response Login(const std::string& name, const std::string& password); + + + /** @brief Send a null command to keep the connection alive + ** This command is useful because the server may close the + ** connection automatically if no command is sent. + ** @return Server response to the request */ + Response KeepAlive(); + + /** @brief Get the current working directory + ** The working directory is the root path for subsequent + ** operations involving directories and/or filenames. + ** @return Server response to the request + ** @see GetDirectoryListing, ChangeDirectory, ParentDirectory */ + DirectoryResponse GetWorkingDirectory(); + + /** @brief Get the contents of the given directory + ** This function retrieves the sub-directories and files + ** contained in the given directory. It is not recursive. + ** The @a directory parameter is relative to the current + ** working directory. + ** @param directory Directory to list + ** @return Server response to the request + ** @see GetWorkingDirectory, ChangeDirectory, ParentDirectory */ + ListingResponse GetDirectoryListing(const std::string& directory = ""); + + /** @brief Change the current working directory + ** The new directory must be relative to the current one. + ** @param directory New working directory + ** @return Server response to the request + ** @see GetWorkingDirectory, GetDirectoryListing, ParentDirectory */ + Response ChangeDirectory(const std::string& directory); + + + /** @brief Go to the parent directory of the current one + ** @return Server response to the request + ** @see GetWorkingDirectory, GetDirectoryListing, ChangeDirectory */ + Response ParentDirectory(); + + /** @brief Create a new directory + ** The new directory is created as a child of the current + ** working directory. + ** @param name Name of the directory to create + ** @return Server response to the request + ** @see DeleteDirectory */ + Response CreateDirectory(const std::string& name); + + + /** @brief Remove an existing directory + ** The directory to remove must be relative to the + ** current working directory. + ** Use this function with caution, the directory will + ** be removed permanently! + ** @param name Name of the directory to remove + ** @return Server response to the request + ** @see CreateDirectory */ + Response DeleteDirectory(const std::string& name); + + /** @brief Rename an existing file + ** The filenames must be relative to the current working + ** directory. + ** @param file File to rename + ** @param newName New name of the file + ** @return Server response to the request + ** @see DeleteFile */ + Response RenameFile(const std::string& file, const std::string& newName); + + /** @brief Remove an existing file + ** The file name must be relative to the current working + ** directory. + ** Use this function with caution, the file will be + ** removed permanently! + ** @param name File to remove + ** @return Server response to the request + ** @see RenameFile */ + Response DeleteFile(const std::string& name); + + /** @brief Download a file from the server + ** The filename of the distant file is relative to the + ** current working directory of the server, and the local + ** destination path is relative to the current directory + ** of your application. + ** @param remoteFile Filename of the distant file to download + ** @param localPath Where to put to file on the local computer + ** @param mode Transfer mode + ** @return Server response to the request + ** @see Upload */ + Response Download(const std::string& remoteFile, const std::string& localPath, TransferMode mode = Binary); + + /** @brief Upload a file to the server + ** The name of the local file is relative to the current + ** working directory of your application, and the + ** remote path is relative to the current directory of the + ** FTP server. + ** @param localFile Path of the local file to upload + ** @param remotePath Where to put to file on the server + ** @param mode Transfer mode + ** @return Server response to the request + ** @see Download */ + Response Upload(const std::string& localFile, const std::string& remotePath, TransferMode mode = Binary); +private : + /** @brief Send a command to the FTP server + ** @param command Command to send + ** @param parameter Command parameter + ** @return Server response to the request */ + Response SendCommand(const std::string& command, const std::string& parameter = ""); + + /** @brief Receive a response from the server + ** This function must be called after each call to + ** SendCommand that expects a response. + ** @return Server response to the request */ + Response GetResponse(); + + /** @brief Utility class for exchanging datas with the server on the data channel */ + class DataChannel; + friend class DataChannel; + + // Member data + cTcpSocket mCommandSocket; ///< Socket holding the control connection with the server +}; + +}} + +#endif // EE_NETWORKCFTP_HPP + +/** +@class cFtp +@ingroup Network + +cFtp is a very simple FTP client that allows you +to communicate with a FTP server. The FTP protocol allows +you to manipulate a remote file system (list files, +upload, download, create, remove, ...). +Using the FTP client consists of 4 parts: +@li Connecting to the FTP server +@li Logging in (either as a registered user or anonymously) +@li Sending commands to the server +@li Disconnecting (this part can be done implicitely by the destructor) +Every command returns a FTP response, which contains the +status code as well as a message from the server. Some +commands such as GetWorkingDirectory and GetDirectoryListing +return additional data, and use a class derived from +cFtp::Response to provide this data. +All commands, especially upload and download, may take some +time to complete. This is important to know if you don't want +to block your application while the server is completing +the task. +Usage example: +@code +// Create a new FTP client +cFtp ftp; + +// Connect to the server +cFtp::Response response = ftp.Connect("ftp://ftp.myserver.com"); +if (response.IsOk()) + std::cout << "Connected" << std::endl; + +// Log in +response = ftp.Login("laurent", "dF6Zm89D"); +if (response.IsOk()) + std::cout << "Logged in" << std::endl; + +// Print the working directory +cFtp::DirectoryResponse directory = ftp.GetWorkingDirectory(); +if (directory.IsOk()) + std::cout << "Working directory: " << directory.GetDirectory() << std::endl; + +// Create a new directory +response = ftp.CreateDirectory("files"); +if (response.IsOk()) + std::cout << "Created new directory" << std::endl; + +// Upload a file to this new directory +response = ftp.Upload("local-path/file.txt", "files", cFtp::Ascii); +if (response.IsOk()) + std::cout << "File uploaded" << std::endl; + +// Disconnect from the server (optional) +ftp.Disconnect(); +@endcode +*/ diff --git a/include/eepp/network/chttp.hpp b/include/eepp/network/chttp.hpp new file mode 100644 index 000000000..f0d3d77d4 --- /dev/null +++ b/include/eepp/network/chttp.hpp @@ -0,0 +1,301 @@ +#ifndef EE_NETWORKCHTTP_HPP +#define EE_NETWORKCHTTP_HPP + +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::System; + +namespace EE { namespace Network { + +/** @brief A HTTP client */ +class EE_API cHttp : NonCopyable { + public : + /** @brief Define a HTTP request */ + class EE_API Request { + public : + /** @brief Enumerate the available HTTP methods for a request */ + enum Method { + Get, ///< Request in get mode, standard method to retrieve a page + Post, ///< Request in post mode, usually to send data to a page + Head ///< Request a page's header only + }; + + /** @brief Default constructor + ** This constructor creates a GET request, with the root + ** URI ("/") and an empty body. + ** @param uri Target URI + ** @param method Method to use for the request + ** @param body Content of the request's body */ + Request(const std::string& uri = "/", Method method = Get, const std::string& body = ""); + + /** @brief Set the value of a field + ** The field is created if it doesn't exist. The name of + ** the field is case insensitive. + ** By default, a request doesn't contain any field (but the + ** mandatory fields are added later by the HTTP client when + ** sending the request). + /// + ** @param field Name of the field to set + ** @param value Value of the field */ + void SetField(const std::string& field, const std::string& value); + + /** @brief Set the request method + ** See the Method enumeration for a complete list of all + ** the availale methods. + ** The method is cHttp::Request::Get by default. + ** @param method Method to use for the request */ + void SetMethod(Method method); + + /** @brief Set the requested URI + ** The URI is the resource (usually a web page or a file) + ** that you want to get or post. + ** The URI is "/" (the root page) by default. + ** @param uri URI to request, relative to the host */ + void SetUri(const std::string& uri); + + /** @brief Set the HTTP version for the request + ** The HTTP version is 1.0 by default. + ** @param major Major HTTP version number + ** @param minor Minor HTTP version number */ + void SetHttpVersion(unsigned int major, unsigned int minor); + + /** @brief Set the body of the request + ** The body of a request is optional and only makes sense + ** for POST requests. It is ignored for all other methods. + ** The body is empty by default. + ** @param body Content of the body */ + void SetBody(const std::string& body); + private: + friend class cHttp; + + /** @brief Prepare the final request to send to the server + ** This is used internally by cHttp before sending the + ** request to the web server. + ** @return String containing the request, ready to be sent */ + std::string Prepare() const; + + /** @brief Check if the request defines a field + ** This function uses case-insensitive comparisons. + ** @param field Name of the field to test + ** @return True if the field exists, false otherwise */ + bool HasField(const std::string& field) const; + + // Types + typedef std::map FieldTable; + + // Member data + FieldTable mFields; ///< Fields of the header associated to their value + Method mMethod; ///< Method to use for the request + std::string mUri; ///< Target URI of the request + unsigned int mMajorVersion; ///< Major HTTP version + unsigned int mMinorVersion; ///< Minor HTTP version + std::string mBody; ///< Body of the request + }; + + /** @brief Define a HTTP response */ + class EE_API Response { + public: + + /** @brief Enumerate all the valid status codes for a response */ + enum Status { + // 2xx: success + Ok = 200, ///< Most common code returned when operation was successful + Created = 201, ///< The resource has successfully been created + Accepted = 202, ///< The request has been accepted, but will be processed later by the server + NoContent = 204, ///< The server didn't send any data in return + ResetContent = 205, ///< The server informs the client that it should clear the view (form) that caused the request to be sent + PartialContent = 206, ///< The server has sent a part of the resource, as a response to a partial GET request + + // 3xx: redirection + MultipleChoices = 300, ///< The requested page can be accessed from several locations + MovedPermanently = 301, ///< The requested page has permanently moved to a new location + MovedTemporarily = 302, ///< The requested page has temporarily moved to a new location + NotModified = 304, ///< For conditionnal requests, means the requested page hasn't changed and doesn't need to be refreshed + + // 4xx: client error + BadRequest = 400, ///< The server couldn't understand the request (syntax error) + Unauthorized = 401, ///< The requested page needs an authentification to be accessed + Forbidden = 403, ///< The requested page cannot be accessed at all, even with authentification + NotFound = 404, ///< The requested page doesn't exist + RangeNotSatisfiable = 407, ///< The server can't satisfy the partial GET request (with a "Range" header field) + + // 5xx: server error + InternalServerError = 500, ///< The server encountered an unexpected error + NotImplemented = 501, ///< The server doesn't implement a requested feature + BadGateway = 502, ///< The gateway server has received an error from the source server + ServiceNotAvailable = 503, ///< The server is temporarily unavailable (overloaded, in maintenance, ...) + GatewayTimeout = 504, ///< The gateway server couldn't receive a response from the source server + VersionNotSupported = 505, ///< The server doesn't support the requested HTTP version + + // 10xx: Custom codes + InvalidResponse = 1000, ///< Response is not a valid HTTP one + ConnectionFailed = 1001 ///< Connection with server failed + }; + + /** @brief Default constructor + ** Constructs an empty response. */ + Response(); + + /** @brief Get the value of a field + ** If the field @a field is not found in the response header, + ** the empty string is returned. This function uses + ** case-insensitive comparisons. + ** @param field Name of the field to get + ** @return Value of the field, or empty string if not found */ + const std::string& GetField(const std::string& field) const; + + /** @brief Get the response status code + ** The status code should be the first thing to be checked + ** after receiving a response, it defines whether it is a + ** success, a failure or anything else (see the Status + ** enumeration). + ** @return Status code of the response */ + Status GetStatus() const; + + /** @brief Get the major HTTP version number of the response + ** @return Major HTTP version number + ** @see GetMinorHttpVersion */ + unsigned int GetMajorHttpVersion() const; + + /** @brief Get the minor HTTP version number of the response + ** @return Minor HTTP version number + ** @see GetMajorHttpVersion */ + unsigned int GetMinorHttpVersion() const; + + /** @brief Get the body of the response + ** The body of a response may contain: + ** @li the requested page (for GET requests) + ** @li a response from the server (for POST requests) + ** @li nothing (for HEAD requests) + ** @li an error message (in case of an error) + ** @return The response body */ + const std::string& GetBody() const; + private : + friend class cHttp; + + /** @brief Construct the header from a response string + ** This function is used by cHttp to build the response + ** of a request. + ** @param data Content of the response to parse */ + void Parse(const std::string& data); + + // Types + typedef std::map FieldTable; + + // Member data + FieldTable mFields; ///< Fields of the header + Status mStatus; ///< Status code + unsigned int mMajorVersion; ///< Major HTTP version + unsigned int mMinorVersion; ///< Minor HTTP version + std::string mBody; ///< Body of the response + }; + + /** @brief Default constructor */ + cHttp(); + + /** @brief Construct the HTTP client with the target host + ** This is equivalent to calling SetHost(host, port). + ** The port has a default value of 0, which means that the + ** HTTP client will use the right port according to the + ** protocol used (80 for HTTP, 443 for HTTPS). You should + ** leave it like this unless you really need a port other + ** than the standard one, or use an unknown protocol. + ** @param host Web server to connect to + ** @param port Port to use for connection */ + cHttp(const std::string& host, unsigned short port = 0); + + /** @brief Set the target host + ** This function just stores the host address and port, it + ** doesn't actually connect to it until you send a request. + ** The port has a default value of 0, which means that the + ** HTTP client will use the right port according to the + ** protocol used (80 for HTTP, 443 for HTTPS). You should + ** leave it like this unless you really need a port other + ** than the standard one, or use an unknown protocol. + ** @param host Web server to connect to + ** @param port Port to use for connection */ + void SetHost(const std::string& host, unsigned short port = 0); + + + /** @brief Send a HTTP request and return the server's response. + ** You must have a valid host before sending a request (see SetHost). + ** Any missing mandatory header field in the request will be added + ** with an appropriate value. + ** Warning: this function waits for the server's response and may + ** not return instantly; use a thread if you don't want to block your + ** application, or use a timeout to limit the time to wait. A value + ** of cTime::Zero means that the client will use the system defaut timeout + ** (which is usually pretty long). + ** @param request Request to send + ** @param timeout Maximum time to wait + ** @return Server's response */ + Response SendRequest(const Request& request, cTime timeout = cTime::Zero); + private: + // Member data + cTcpSocket mConnection; ///< Connection to the host + cIpAddress mHost; ///< Web host address + std::string mHostName; ///< Web host name + unsigned short mPort; ///< Port used for connection with host +}; + +}} + +#endif // EE_NETWORKCHTTP_HPP + +/** +@class cHttp +@ingroup Network +cHttp is a very simple HTTP client that allows you +to communicate with a web server. You can retrieve +web pages, send data to an interactive resource, +download a remote file, etc. +The HTTP client is split into 3 classes: +@li cHttp::Request +@li cHttp::Response +@li cHttp +cHttp::Request builds the request that will be +sent to the server. A request is made of: +@li a method (what you want to do) +@li a target URI (usually the name of the web page or file) +@li one or more header fields (options that you can pass to the server) +@li an optional body (for POST requests) +cHttp::Response parse the response from the web server +and provides getters to read them. The response contains: +@li a status code +@li header fields (that may be answers to the ones that you requested) +@li a body, which contains the contents of the requested resource +cHttp provides a simple function, SendRequest, to send a +cHttp::Request and return the corresponding cHttp::Response +from the server. +Usage example: +@code +// Create a new HTTP client +cHttp http; + +// We'll work on http://www.google.com +http.SetHost("http://www.google.com"); + +// Prepare a request to get the 'features.php' page +cHttp::Request request("features.php"); + +// Send the request +cHttp::Response response = http.SendRequest(request); + +// Check the status code and display the result +cHttp::Response::Status status = response.GetStatus(); +if (status == cHttp::Response::Ok) +{ + std::cout << response.GetBody() << std::endl; +} +else +{ + std::cout << "Error " << status << std::endl; +} +@endcode +*/ diff --git a/include/eepp/network/cipaddress.hpp b/include/eepp/network/cipaddress.hpp new file mode 100644 index 000000000..2013b3a2c --- /dev/null +++ b/include/eepp/network/cipaddress.hpp @@ -0,0 +1,185 @@ +#ifndef EE_NETWORKCIPADDRESS_HPP +#define EE_NETWORKCIPADDRESS_HPP + +#include +#include +using namespace EE::System; + +#include +#include +#include + +namespace EE { namespace Network { + +/** @brief Encapsulate an IPv4 network address */ +class EE_API cIpAddress +{ + public: + /** @brief Default constructor + ** This constructor creates an empty (invalid) address */ + cIpAddress(); + + /** @brief Construct the address from a string + ** Here @a address can be either a decimal address + ** (ex: "192.168.1.56") or a network name (ex: "localhost"). + ** @param address IP address or network name */ + cIpAddress(const std::string& address); + + /** @brief Construct the address from a string + ** Here @a address can be either a decimal address + ** (ex: "192.168.1.56") or a network name (ex: "localhost"). + ** This is equivalent to the constructor taking a std::string + ** parameter, it is defined for convenience so that the + ** implicit conversions from literal strings to cIpAddress work. + ** @param address IP address or network name */ + cIpAddress(const char* address); + + /** @brief Construct the address from 4 bytes + ** Calling cIpAddress(a, b, c, d) is equivalent to calling + ** cIpAddress("a.b.c.d"), but safer as it doesn't have to + ** parse a string to get the address components. + ** @param byte0 First byte of the address + ** @param byte1 Second byte of the address + ** @param byte2 Third byte of the address + ** @param byte3 Fourth byte of the address */ + cIpAddress(Uint8 byte0, Uint8 byte1, Uint8 byte2, Uint8 byte3); + + /** @brief Construct the address from a 32-bits integer + ** This constructor uses the internal representation of + ** the address directly. It should be used for optimization + ** purposes, and only if you got that representation from + ** cIpAddress::ToInteger(). + ** @param address 4 bytes of the address packed into a 32-bits integer + ** @see ToInteger */ + explicit cIpAddress(Uint32 address); + + /** @brief Get a string representation of the address + ** The returned string is the decimal representation of the + ** IP address (like "192.168.1.56"), even if it was constructed + ** from a host name. + ** @return String representation of the address + ** @see ToInteger */ + std::string ToString() const; + + /** @brief Get an integer representation of the address + ** The returned number is the internal representation of the + ** address, and should be used for optimization purposes only + ** (like sending the address through a socket). + ** The integer produced by this function can then be converted + ** back to a cIpAddress with the proper constructor. + ** @return 32-bits unsigned integer representation of the address + ** @see ToString */ + Uint32 ToInteger() const; + + /** @brief Get the computer's local address + ** The local address is the address of the computer from the + ** LAN point of view, i.e. something like 192.168.1.56. It is + ** meaningful only for communications over the local network. + ** Unlike GetPublicAddress, this function is fast and may be + ** used safely anywhere. + ** @return Local IP address of the computer + ** @see GetPublicAddress */ + static cIpAddress GetLocalAddress(); + + /** @brief Get the computer's public address + ** The public address is the address of the computer from the + ** internet point of view, i.e. something like 89.54.1.169. + ** It is necessary for communications over the world wide web. + ** The only way to get a public address is to ask it to a + ** distant website; as a consequence, this function depends on + ** both your network connection and the server, and may be + ** very slow. You should use it as few as possible. Because + ** this function depends on the network connection and on a distant + ** server, you may use a time limit if you don't want your program + ** to be possibly stuck waiting in case there is a problem; this + ** limit is deactivated by default. + ** @param timeout Maximum time to wait + ** @return Public IP address of the computer + ** @see GetLocalAddress */ + static cIpAddress GetPublicAddress(cTime timeout = cTime::Zero); + + // Static member data + static const cIpAddress None; ///< Value representing an empty/invalid address + static const cIpAddress LocalHost; ///< The "localhost" address (for connecting a computer to itself locally) + static const cIpAddress Broadcast; ///< The "broadcast" address (for sending UDP messages to everyone on a local network) + private : + // Member data + Uint32 mAddress; ///< Address stored as an unsigned 32 bits integer +}; + +/** @brief Overload of == operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if both addresses are equal */ +EE_API bool operator ==(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of != operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if both addresses are different */ +EE_API bool operator !=(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of < operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is lesser than @a right */ +EE_API bool operator <(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of > operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is greater than @a right */ +EE_API bool operator >(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of <= operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is lesser or equal than @a right */ +EE_API bool operator <=(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of >= operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is greater or equal than @a right */ +EE_API bool operator >=(const cIpAddress& left, const cIpAddress& right); + +/** @brief Overload of >> operator to extract an IP address from an input stream +** @param stream Input stream +** @param address IP address to extract +** @return Reference to the input stream */ +EE_API std::istream& operator >>(std::istream& stream, cIpAddress& address); + +/** @brief Overload of << operator to print an IP address to an output stream +** @param stream Output stream +** @param address IP address to print +** @return Reference to the output stream */ +EE_API std::ostream& operator <<(std::ostream& stream, const cIpAddress& address); + +}} + +#endif // EE_NETWORKCIPADDRESS_HPP + +/** +@class cIpAddress +@ingroup Network +cIpAddress is a utility class for manipulating network +addresses. It provides a set a implicit constructors and +conversion functions to easily build or transform an IP +address from/to various representations. + +Usage example: +@code +cIpAddress a0; // an invalid address +cIpAddress a1 = cIpAddress::None; // an invalid address (same as a0) +cIpAddress a2("127.0.0.1"); // the local host address +cIpAddress a3 = cIpAddress::Broadcast; // the broadcast address +cIpAddress a4(192, 168, 1, 56); // a local address +cIpAddress a5("my_computer"); // a local address created from a network name +cIpAddress a6("89.54.1.169"); // a distant address +cIpAddress a7("www.google.com"); // a distant address created from a network name +cIpAddress a8 = cIpAddress::GetLocalAddress(); // my address on the local network +cIpAddress a9 = cIpAddress::GetPublicAddress(); // my address on the internet +@endcode +Note that cIpAddress currently doesn't support IPv6 +nor other types of network addresses. +*/ diff --git a/include/eepp/network/cpacket.hpp b/include/eepp/network/cpacket.hpp new file mode 100644 index 000000000..9a987bc7b --- /dev/null +++ b/include/eepp/network/cpacket.hpp @@ -0,0 +1,295 @@ +#ifndef EE_NETWORKCPACKET_HPP +#define EE_NETWORKCPACKET_HPP + +#include +#include +#include + +namespace EE { namespace Network { + +class cTcpSocket; +class cUdpSocket; + +/** @brief Utility class to build blocks of data to transfer over the network */ +class EE_API cPacket { + // A bool-like type that cannot be converted to integer or pointer types + typedef bool (cPacket::*BoolType)(std::size_t); + public: + + /** @brief Default constructor + ** Creates an empty packet. */ + cPacket(); + + /** @brief Virtual destructor */ + virtual ~cPacket(); + + /** @brief Append data to the end of the packet + ** @param data Pointer to the sequence of bytes to append + ** @param sizeInBytes Number of bytes to append + ** @see Clear */ + void Append(const void* data, std::size_t sizeInBytes); + + /** @brief Clear the packet + ** After calling Clear, the packet is empty. + ** @see Append */ + void Clear(); + + /** @brief Get a pointer to the data contained in the packet + ** Warning: the returned pointer may become invalid after + ** you append data to the packet, therefore it should never + ** be stored. + ** The return pointer is NULL if the packet is empty. + ** @return Pointer to the data + ** @see GetDataSize */ + const void* GetData() const; + + /** @brief Get the size of the data contained in the packet + ** This function returns the number of bytes pointed to by + ** what GetData returns. + ** @return Data size, in bytes + ** @see GetData */ + std::size_t GetDataSize() const; + + /** @brief Tell if the reading position has reached the + /// end of the packet + ** This function is useful to know if there is some data + ** left to be read, without actually reading it. + ** @return True if all data was read, false otherwise + ** @see operator bool */ + bool EndOfcPacket() const; + + /** @brief Test the validity of the packet, for reading + ** This operator allows to test the packet as a boolean + ** variable, to check if a reading operation was successful. + ** A packet will be in an invalid state if it has no more + ** data to read. + ** This behaviour is the same as standard C++ streams. + ** Usage example: + ** @code + ** float x; + ** packet >> x; + ** if (packet) + ** { + /// // ok, x was extracted successfully + ** } + /// + ** // -- or -- + /// + ** float x; + ** if (packet >> x) + ** { + /// // ok, x was extracted successfully + ** } + ** @endcode + /// + ** Don't focus on the return type, it's equivalent to bool but + ** it disallows unwanted implicit conversions to integer or + ** pointer types. + /// + ** @return True if last data extraction from packet was successful + /// + ** @see EndOfcPacket */ + operator BoolType() const; + + /** Overloads of operator >> to read data from the packet */ + cPacket& operator >>(bool& data); + cPacket& operator >>(Int8& data); + cPacket& operator >>(Uint8& data); + cPacket& operator >>(Int16& data); + cPacket& operator >>(Uint16& data); + cPacket& operator >>(Int32& data); + cPacket& operator >>(Uint32& data); + cPacket& operator >>(float& data); + cPacket& operator >>(double& data); + cPacket& operator >>(char* data); + cPacket& operator >>(std::string& data); + #ifndef EE_NO_WIDECHAR + cPacket& operator >>(wchar_t* data); + cPacket& operator >>(std::wstring& data); + #endif + cPacket& operator >>(String& data); + + /** Overloads of operator << to write data into the packet */ + cPacket& operator <<(bool data); + cPacket& operator <<(Int8 data); + cPacket& operator <<(Uint8 data); + cPacket& operator <<(Int16 data); + cPacket& operator <<(Uint16 data); + cPacket& operator <<(Int32 data); + cPacket& operator <<(Uint32 data); + cPacket& operator <<(float data); + cPacket& operator <<(double data); + cPacket& operator <<(const char* data); + cPacket& operator <<(const std::string& data); + #ifndef EE_NO_WIDECHAR + cPacket& operator <<(const wchar_t* data); + cPacket& operator <<(const std::wstring& data); + #endif + cPacket& operator <<(const String& data); +protected: + friend class cTcpSocket; + friend class cUdpSocket; + + /** @brief Called before the packet is sent over the network + ** This function can be defined by derived classes to + ** transform the data before it is sent; this can be + ** used for compression, encryption, etc. + ** The function must return a pointer to the modified data, + ** as well as the number of bytes pointed. + ** The default implementation provides the packet's data + ** without transforming it. + ** @param size Variable to fill with the size of data to send + ** @return Pointer to the array of bytes to send + ** @see OnReceive */ + virtual const void* OnSend(std::size_t& size); + + /** @brief Called after the packet is received over the network + ** This function can be defined by derived classes to + ** transform the data after it is received; this can be + ** used for uncompression, decryption, etc. + ** The function receives a pointer to the received data, + ** and must fill the packet with the transformed bytes. + ** The default implementation fills the packet directly + ** without transforming the data. + ** @param data Pointer to the received bytes + ** @param size Number of bytes + ** @see OnSend */ + virtual void OnReceive(const void* data, std::size_t size); +private: + /** Disallow comparisons between packets */ + bool operator ==(const cPacket& right) const; + bool operator !=(const cPacket& right) const; + + /** @brief Check if the packet can extract a given number of bytes + ** This function updates accordingly the state of the packet. + ** @param size Size to check + ** @return True if @a size bytes can be read from the packet */ + bool CheckSize(std::size_t size); + + // Member data + std::vector mData; ///< Data stored in the packet + std::size_t mReadPos; ///< Current reading position in the packet + bool mIsValid; ///< Reading state of the packet +}; + +}} + +#endif // EE_NETWORKCPACKET_HPP + +/** +@class cPacket +@ingroup Network + +cPackets provide a safe and easy way to serialize data, +in order to send it over the network using sockets +(cTcpSocket, cUdpSocket). + +cPackets solve 2 fundamental problems that arise when +transfering data over the network: +@li data is interpreted correctly according to the endianness +@li the bounds of the packet are preserved (one send == one receive) + +The cPacket class provides both input and output modes. +It is designed to follow the behaviour of standard C++ streams, +using operators >> and << to extract and insert data. + +It is recommended to use only fixed-size types (like Int32, etc.), +to avoid possible differences between the sender and the receiver. +Indeed, the native C++ types may have different sizes on two platforms +and your data may be corrupted if that happens. + +Usage example: +@code +Uint32 x = 24; +std::string s = "hello"; +double d = 5.89; + +// Group the variables to send into a packet +cPacket packet; +packet << x << s << d; + +// Send it over the network (socket is a valid cTcpSocket) +socket.Send(packet); + +----------------------------------------------------------------- + +// Receive the packet at the other end +cPacket packet; +socket.Receive(packet); + +// Extract the variables contained in the packet +Uint32 x; +std::string s; +double d; +if (packet >> x >> s >> d) +{ + // Data extracted successfully... +} +@endcode + +cPackets have built-in operator >> and << overloads for +standard types: +@li bool +@li fixed-size integer types (Int8/16/32, Uint8/16/32) +@li floating point numbers (float, double) +@li string types (char*, wchar_t*, std::string, std::wstring, String) + +Like standard streams, it is also possible to define your own +overloads of operators >> and << in order to handle your +custom types. + +@code +struct MyStruct +{ + float number; + Int8 integer; + std::string str; +}; + +cPacket& operator <<(cPacket& packet, const MyStruct& m) +{ + return packet << m.number << m.integer << m.str; +} + +cPacket& operator >>(cPacket& packet, MyStruct& m) +{ + return packet >> m.number >> m.integer >> m.str; +} +@endcode + +cPackets also provide an extra feature that allows to apply +custom transformations to the data before it is sent, +and after it is received. This is typically used to +handle automatic compression or encryption of the data. +This is achieved by inheriting from cPacket, and overriding +the OnSend and OnReceive functions. + +Here is an example: +@code +class ZipcPacket : public cPacket +{ + virtual const void* OnSend(std::size_t& size) + { + const void* srcData = GetData(); + std::size_t srcSize = GetDataSize(); + + return MySuperZipFunction(srcData, srcSize, &size); + } + + virtual void OnReceive(const void* data, std::size_t size) + { + std::size_t dstSize; + const void* dstData = MySuperUnzipFunction(data, size, &dstSize); + + append(dstData, dstSize); + } +}; + +// Use like regular packets: +ZipcPacket packet; +packet << x << s << d; +... +@endcode + +@see cTcpSocket, cUdpSocket +*/ diff --git a/include/eepp/network/csocket.hpp b/include/eepp/network/csocket.hpp new file mode 100644 index 000000000..7ab19f5d2 --- /dev/null +++ b/include/eepp/network/csocket.hpp @@ -0,0 +1,124 @@ +#ifndef EE_NETWORKCSOCKET_HPP +#define EE_NETWORKCSOCKET_HPP + +#include +#include +#include +#include + +namespace EE { namespace Network { +class cSocketSelector; + +/** @brief Base class for all the socket types */ +class EE_API cSocket : NonCopyable { + public: + /** @brief Status codes that may be returned by socket functions */ + enum Status { + Done, ///< The socket has sent / received the data + NotReady, ///< The socket is not ready to send / receive data yet + Disconnected, ///< The TCP socket has been disconnected + Error ///< An unexpected error happened + }; + + /** @brief Some special values used by sockets */ + enum { + AnyPort = 0 ///< Special value that tells the system to pick any available port + }; + + /** @brief Destructor */ + virtual ~cSocket(); + + /** @brief Set the blocking state of the socket + ** In blocking mode, calls will not return until they have + ** completed their task. For example, a call to Receive in + ** blocking mode won't return until some data was actually + ** received. + ** In non-blocking mode, calls will always return immediately, + ** using the return code to signal whether there was data + ** available or not. + ** By default, all sockets are blocking. + ** @param blocking True to set the socket as blocking, false for non-blocking + ** @see IsBlocking */ + void SetBlocking(bool blocking); + + /** @brief Tell whether the socket is in blocking or non-blocking mode + ** @return True if the socket is blocking, false otherwise + ** @see SetBlocking */ + bool IsBlocking() const; +protected : + /** @brief Types of protocols that the socket can use */ + enum Type + { + Tcp, ///< TCP protocol + Udp ///< UDP protocol + }; + + /** @brief Default constructor + ** This constructor can only be accessed by derived classes. + ** @param type Type of the socket (TCP or UDP) */ + cSocket(Type type); + + /** @brief Return the internal handle of the socket + ** The returned handle may be invalid if the socket + ** was not created yet (or already destroyed). + ** This function can only be accessed by derived classes. + ** @return The internal (OS-specific) handle of the socket */ + SocketHandle GetHandle() const; + + /** @brief Create the internal representation of the socket + /// + ** This function can only be accessed by derived classes. */ + void Create(); + + + /** @brief Create the internal representation of the socket from a socket handle + ** This function can only be accessed by derived classes. + ** @param handle OS-specific handle of the socket to wrap */ + void Create(SocketHandle handle); + + /** @brief Close the socket gracefully + ** This function can only be accessed by derived classes. */ + void Close(); +private : + friend class cSocketSelector; + // Member data + Type mType; ///< Type of the socket (TCP or UDP) + SocketHandle mSocket; ///< Socket descriptor + bool mIsBlocking; ///< Current blocking mode of the socket +}; + +}} + +#endif // EE_NETWORKCSOCKET_HPP + +/** +@class cSocket +@ingroup Network +This class mainly defines internal stuff to be used by +derived classes. + +The only public features that it defines, and which +is therefore common to all the socket classes, is the +blocking state. All sockets can be set as blocking or +non-blocking. + +In blocking mode, socket functions will hang until +the operation completes, which means that the entire +program (well, in fact the current thread if you use +multiple ones) will be stuck waiting for your socket +operation to complete. + +In non-blocking mode, all the socket functions will +return immediately. If the socket is not ready to complete +the requested operation, the function simply returns +the proper status code (cSocket::NotReady). +The default mode, which is blocking, is the one that is +generally used, in combination with threads or selectors. + +The non-blocking mode is rather used in real-time +applications that run an endless loop that can poll +the socket often enough, and cannot afford blocking +this loop. + +@see cTcpListener, cTcpSocket, cUdpSocket +*/ diff --git a/include/eepp/network/csocketselector.hpp b/include/eepp/network/csocketselector.hpp new file mode 100644 index 000000000..25d7625c8 --- /dev/null +++ b/include/eepp/network/csocketselector.hpp @@ -0,0 +1,179 @@ +#ifndef EE_NETWORKCSOCKETSELECTOR_HPP +#define EE_NETWORKCSOCKETSELECTOR_HPP + +#include +#include +using namespace EE::System; + +namespace EE { namespace Network { + +class cSocket; + +/** @brief Multiplexer that allows to read from multiple sockets */ +class EE_API cSocketSelector +{ + public : + /** @brief Default constructor */ + cSocketSelector(); + + /** @brief Copy constructor + ** @param copy Instance to copy */ + cSocketSelector(const cSocketSelector& copy); + + /** @brief Destructor */ + ~cSocketSelector(); + + /** @brief Add a new socket to the selector + ** This function keeps a weak reference to the socket, + ** so you have to make sure that the socket is not destroyed + ** while it is stored in the selector. + ** This function does nothing if the socket is not valid. + ** @param socket Reference to the socket to add + ** @see Remove, Clear */ + void Add(cSocket& socket); + + /** @brief Remove a socket from the selector + ** This function doesn't destroy the socket, it simply + ** removes the reference that the selector has to it. + ** @param socket Reference to the socket to remove + ** @see Add, Clear */ + void Remove(cSocket& socket); + + /** @brief Remove all the sockets stored in the selector + ** This function doesn't destroy any instance, it simply + ** removes all the references that the selector has to + ** external sockets. + ** @see Add, Remove */ + void Clear(); + + /** @brief Wait until one or more sockets are ready to receive + ** This function returns as soon as at least one socket has + ** some data available to be received. To know which sockets are + ** ready, use the isReady function. + ** If you use a timeout and no socket is ready before the timeout + ** is over, the function returns false. + ** @param timeout Maximum time to wait, (use cTime::Zero for infinity) + ** @return True if there are sockets ready, false otherwise + ** @see IsReady */ + bool Wait(cTime timeout = cTime::Zero); + + /** @brief Test a socket to know if it is ready to receive data + ** This function must be used after a call to Wait, to know + ** which sockets are ready to receive data. If a socket is + ** ready, a call to receive will never block because we know + ** that there is data available to read. + ** Note that if this function returns true for a cTcpListener, + ** this means that it is ready to accept a new connection. + ** @param socket cSocket to test + ** @return True if the socket is ready to read, false otherwise + ** @see IsReady */ + bool IsReady(cSocket& socket) const; + + /** @brief Overload of assignment operator + ** @param right Instance to assign + ** @return Reference to self */ + cSocketSelector& operator =(const cSocketSelector& right); + private : + struct cSocketSelectorImpl; + + // Member data + cSocketSelectorImpl* mImpl; ///< Opaque pointer to the implementation (which requires OS-specific types) +}; + +}} + +#endif // EE_NETWORKCSOCKETSELECTOR_HPP + +/** +@class cSocketSelector +@ingroup Network + +cSocket selectors provide a way to wait until some data is +available on a set of sockets, instead of just one. This +is convenient when you have multiple sockets that may +possibly receive data, but you don't know which one will +be ready first. In particular, it avoids to use a thread +for each socket; with selectors, a single thread can handle +all the sockets. + +All types of sockets can be used in a selector: +@li cTcpListener +@li cTcpSocket +@li cUdpSocket + +A selector doesn't store its own copies of the sockets +(socket classes are not copyable anyway), it simply keeps +a reference to the original sockets that you pass to the +"add" function. Therefore, you can't use the selector as a +socket container, you must store them oustide and make sure +that they are alive as long as they are used in the selector. + +Using a selector is simple: +@li populate the selector with all the sockets that you want to observe +@li make it wait until there is data available on any of the sockets +@li test each socket to find out which ones are ready + +Usage example: +@code +// Create a socket to listen to new connections +cTcpListener listener; +listener.Listen(55001); + +// Create a list to store the future clients +std::list clients; + +// Create a selector +cSocketSelector selector; + +// Add the listener to the selector +selector.Add(listener); + +// Endless loop that waits for new connections +while (running) +{ + // Make the selector wait for data on any socket + if (selector.Wait()) + { + // Test the listener + if (selector.IsReady(listener)) + { + // The listener is ready: there is a pending connection + cTcpSocket* client = new cTcpSocket; + if (listener.Accept(*client) == cSocket::Done) + { + // Add the new client to the clients list + clients.push_back(client); + + // Add the new client to the selector so that we will + // be notified when he sends something + selector.Add(*client); + } + else + { + // Error, we won't get a new connection, delete the socket + delete client; + } + } + else + { + // The listener socket is not ready, test all other sockets (the clients) + for (std::list::iterator it = clients.begin(); it != clients.end(); ++it) + { + cTcpSocket& client = **it; + if (selector.IsReady(client)) + { + // The client has sent some data, we can receive it + cPacket packet; + if (client.Receive(packet) == cSocket::Done) + { + ... + } + } + } + } + } +} +@endcode + +@see cSocket +*/ diff --git a/include/eepp/network/ctcplistener.hpp b/include/eepp/network/ctcplistener.hpp new file mode 100644 index 000000000..3e63ea34c --- /dev/null +++ b/include/eepp/network/ctcplistener.hpp @@ -0,0 +1,98 @@ +#ifndef EE_NETWORKCTCPLISTENER_HPP +#define EE_NETWORKCTCPLISTENER_HPP + +#include + +namespace EE { namespace Network { + +class cTcpSocket; + +/** @brief Socket that listens to new TCP connections */ +class EE_API cTcpListener : public cSocket +{ +public : + + /** @brief Default constructor */ + cTcpListener(); + + /** @brief Get the port to which the socket is bound locally + ** If the socket is not listening to a port, this function + ** returns 0. + ** @return Port to which the socket is bound + ** @see Listen */ + unsigned short GetLocalPort() const; + + /** @brief Start listening for connections + ** This functions makes the socket listen to the specified + ** port, waiting for new connections. + ** If the socket was previously listening to another port, + ** it will be stopped first and bound to the new port. + ** @param port Port to listen for new connections + ** @return Status code + ** @see Accept, Close */ + Status Listen(unsigned short port); + + /** @brief Stop listening and close the socket + ** This function gracefully stops the listener. If the + ** socket is not listening, this function has no effect. + ** @see Listen */ + void Close(); + + /** @brief Accept a new connection + ** If the socket is in blocking mode, this function will + ** not return until a connection is actually received. + ** @param socket Socket that will hold the new connection + ** @return Status code + ** @see Listen */ + Status Accept(cTcpSocket& socket); +}; + +}} + +#endif // EE_NETWORKCTCPLISTENER_HPP + +/** +@class cTcpListener +@ingroup Network + +A listener socket is a special type of socket that listens to +a given port and waits for connections on that port. +This is all it can do. + +When a new connection is received, you must call accept and +the listener returns a new instance of cTcpSocket that +is properly initialized and can be used to communicate with +the new client. + +Listener sockets are specific to the TCP protocol, +UDP sockets are connectionless and can therefore communicate +directly. As a consequence, a listener socket will always +return the new connections as cTcpSocket instances. + +A listener is automatically closed on destruction, like all +other types of socket. However if you want to stop listening +before the socket is destroyed, you can call its close() +function. + +Usage example: +@code +// Create a listener socket and make it wait for new +// connections on port 55001 +cTcpListener listener; +listener.Listen(55001); + +// Endless loop that waits for new connections +while (running) +{ + cTcpSocket client; + if (listener.Accept(client) == cSocket::Done) + { + // A new client just connected! + std::cout << "New connection received from " << client.GetRemoteAddress() << std::endl; + doSomethingWith(client); + } +} +@endcode + +@see cTcpSocket, cSocket +*/ diff --git a/include/eepp/network/ctcpsocket.hpp b/include/eepp/network/ctcpsocket.hpp new file mode 100644 index 000000000..67c3aa351 --- /dev/null +++ b/include/eepp/network/ctcpsocket.hpp @@ -0,0 +1,190 @@ +#ifndef EE_NETWORKCTCPSOCKET_HPP +#define EE_NETWORKCTCPSOCKET_HPP + +#include +#include +using namespace EE::System; + +namespace EE { namespace Network { + +class cTcpListener; +class cIpAddress; +class cPacket; + +/** @brief Specialized socket using the TCP protocol */ +class EE_API cTcpSocket : public cSocket { + public: + + /** @brief Default constructor */ + cTcpSocket(); + + /** @brief Get the port to which the socket is bound locally + ** If the socket is not connected, this function returns 0. + ** @return Port to which the socket is bound + ** @see Connect, GetRemotePort */ + unsigned short GetLocalPort() const; + + /** @brief Get the address of the connected peer + ** It the socket is not connected, this function returns + ** cIpAddress::None. + ** @return Address of the remote peer + ** @see GetRemotePort */ + cIpAddress GetRemoteAddress() const; + + /** @brief Get the port of the connected peer to which + the socket is connected + ** If the socket is not connected, this function returns 0. + ** @return Remote port to which the socket is connected + ** @see GetRemoteAddress */ + unsigned short GetRemotePort() const; + + /** @brief Connect the socket to a remote peer + ** In blocking mode, this function may take a while, especially + ** if the remote peer is not reachable. The last parameter allows + ** you to stop trying to connect after a given timeout. + ** If the socket was previously connected, it is first disconnected. + ** @param remoteAddress Address of the remote peer + ** @param remotePort Port of the remote peer + ** @param timeout Optional maximum time to wait + ** @return Status code + ** @see Disconnect */ + Status Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout = cTime::Zero); + + /** @brief Disconnect the socket from its remote peer + ** This function gracefully closes the connection. If the + ** socket is not connected, this function has no effect. + ** @see Connect */ + void Disconnect(); + + /** @brief Send raw data to the remote peer + ** This function will fail if the socket is not connected. + ** @param data Pointer to the sequence of bytes to send + ** @param size Number of bytes to send + ** @return Status code + ** @see Receive */ + Status Send(const void* data, std::size_t size); + + /** @brief Receive raw data from the remote peer + ** In blocking mode, this function will wait until some + ** bytes are actually received. + ** This function will fail if the socket is not connected. + ** @param data Pointer to the array to fill with the received bytes + ** @param size Maximum number of bytes that can be received + ** @param received This variable is filled with the actual number of bytes received + ** @return Status code + ** @see Send */ + Status Receive(void* data, std::size_t size, std::size_t& received); + + /** @brief Send a formatted packet of data to the remote peer + ** This function will fail if the socket is not connected. + ** @param packet cPacket to send + ** @return Status code + ** @see Receive */ + Status Send(cPacket& packet); + + /** @brief Receive a formatted packet of data from the remote peer + ** In blocking mode, this function will wait until the whole packet + ** has been received. + ** This function will fail if the socket is not connected. + ** @param packet cPacket to fill with the received data + ** @return Status code + ** @see Send */ + Status Receive(cPacket& packet); + + private: + + friend class cTcpListener; + + /** @brief Structure holding the data of a pending packet */ + struct PendingPacket { + PendingPacket(); + + Uint32 Size; ///< Data of packet size + std::size_t SizeReceived; ///< Number of size bytes received so far + std::vector Data; ///< Data of the packet + }; + + // Member data + PendingPacket mPendingPacket; ///< Temporary data of the packet currently being received +}; + +}} + +#endif // EE_NETWORKCTCPSOCKET_HPP + +/** +@class cTcpSocket +@ingroup Network + +TCP is a connected protocol, which means that a TCP +socket can only communicate with the host it is connected +to. It can't send or receive anything if it is not connected. + +The TCP protocol is reliable but adds a slight overhead. +It ensures that your data will always be received in order +and without errors (no data corrupted, lost or duplicated). + +When a socket is connected to a remote host, you can +retrieve informations about this host with the +GetRemoteAddress and GetRemotePort functions. You can +also get the local port to which the socket is bound +(which is automatically chosen when the socket is connected), +with the GetLocalPort function. + +Sending and receiving data can use either the low-level +or the high-level functions. The low-level functions +process a raw sequence of bytes, and cannot ensure that +one call to Send will exactly match one call to Receive +at the other end of the socket. + +The high-level interface uses packets (see cPacket), +which are easier to use and provide more safety regarding +the data that is exchanged. You can look at the cPacket +class to get more details about how they work. + +The socket is automatically disconnected when it is destroyed, +but if you want to explicitely close the connection while +the socket instance is still alive, you can call disconnect. + +Usage example: +@code +// ----- The client ----- + +// Create a socket and connect it to 192.168.1.50 on port 55001 +cTcpSocket socket; +socket.Connect("192.168.1.50", 55001); + +// Send a message to the connected host +std::string message = "Hi, I am a client"; +socket.Send(message.c_str(), message.size() + 1); + +// Receive an answer from the server +char buffer[1024]; +std::size_t received = 0; +socket.Receive(buffer, sizeof(buffer), received); +std::cout << "The server said: " << buffer << std::endl; + +// ----- The server ----- + +// Create a listener to wait for incoming connections on port 55001 +cTcpListener listener; +listener.Listen(55001); + +// Wait for a connection +cTcpSocket socket; +listener.Accept(socket); +std::cout << "New client connected: " << socket.GetRemoteAddress() << std::endl; + +// Receive a message from the client +char buffer[1024]; +std::size_t received = 0; +socket.Receive(buffer, sizeof(buffer), received); +std::cout << "The client said: " << buffer << std::endl; + +// Send an answer +std::string message = "Welcome, client"; +socket.Send(message.c_str(), message.size() + 1); +@endcode + +@see cSocket, cUdpSocket, cPacket +*/ diff --git a/include/eepp/network/cudpsocket.hpp b/include/eepp/network/cudpsocket.hpp new file mode 100644 index 000000000..93dc929ee --- /dev/null +++ b/include/eepp/network/cudpsocket.hpp @@ -0,0 +1,191 @@ +#ifndef EE_NETWORKCUDPSOCKET_HPP +#define EE_NETWORKCUDPSOCKET_HPP + +#include +#include + +namespace EE { namespace Network { + +class cIpAddress; +class cPacket; + +/** @brief Specialized socket using the UDP protocol */ +class EE_API cUdpSocket : public cSocket { + public: + + // Constants + enum { + MaxDatagramSize = 65507 ///< The maximum number of bytes that can be sent in a single UDP datagram + }; + + /** @brief Default constructor */ + cUdpSocket(); + + /** @brief Get the port to which the socket is bound locally + ** If the socket is not bound to a port, this function + ** returns 0. + ** @return Port to which the socket is bound + ** @see Bind */ + unsigned short GetLocalPort() const; + + /** @brief Bind the socket to a specific port + ** Binding the socket to a port is necessary for being + ** able to receive data on that port. + ** You can use the special value cSocket::AnyPort to tell the + ** system to automatically pick an available port, and then + ** call GetLocalPort to retrieve the chosen port. + ** @param port Port to Bind the socket to + ** @return Status code + ** @see Unbind, GetLocalPort */ + Status Bind(unsigned short port); + + /** @brief Unbind the socket from the local port to which it is bound + ** The port that the socket was previously using is immediately + ** available after this function is called. If the + ** socket is not bound to a port, this function has no effect. + ** @see Bind */ + void Unbind(); + + /** @brief Send raw data to a remote peer + ** Make sure that @a size is not greater than + ** cUdpSocket::MaxDatagramSize, otherwise this function will + ** fail and no data will be sent. + ** @param data Pointer to the sequence of bytes to send + ** @param size Number of bytes to send + ** @param remoteAddress Address of the receiver + ** @param remotePort Port of the receiver to send the data to + ** @return Status code + ** @see Receive */ + Status Send(const void* data, std::size_t size, const cIpAddress& remoteAddress, unsigned short remotePort); + + /** @brief Receive raw data from a remote peer + ** In blocking mode, this function will wait until some + ** bytes are actually received. + ** Be careful to use a buffer which is large enough for + ** the data that you intend to receive, if it is too small + ** then an error will be returned and *all* the data will + ** be lost. + ** @param data Pointer to the array to fill with the received bytes + ** @param size Maximum number of bytes that can be received + ** @param received This variable is filled with the actual number of bytes received + ** @param remoteAddress Address of the peer that sent the data + ** @param remotePort Port of the peer that sent the data + ** @return Status code + ** @see Send */ + Status Receive(void* data, std::size_t size, std::size_t& received, cIpAddress& remoteAddress, unsigned short& remotePort); + + /** @brief Send a formatted packet of data to a remote peer + ** Make sure that the packet size is not greater than + ** cUdpSocket::MaxDatagramSize, otherwise this function will + ** fail and no data will be sent. + ** @param packet cPacket to send + ** @param remoteAddress Address of the receiver + ** @param remotePort Port of the receiver to send the data to + ** @return Status code + ** @see Receive */ + Status Send(cPacket& packet, const cIpAddress& remoteAddress, unsigned short remotePort); + + /** @brief Receive a formatted packet of data from a remote peer + ** In blocking mode, this function will wait until the whole packet + ** has been received. + ** @param packet cPacket to fill with the received data + ** @param remoteAddress Address of the peer that sent the data + ** @param remotePort Port of the peer that sent the data + ** @return Status code + ** @see Send */ + Status Receive(cPacket& packet, cIpAddress& remoteAddress, unsigned short& remotePort); +private: + // Member data + std::vector mBuffer; ///< Temporary buffer holding the received data in Receive(cPacket) +}; + +}} + +#endif // EE_NETWORKCUDPSOCKET_HPP + +/** +@class cUdpSocket +@ingroup Network + +A UDP socket is a connectionless socket. Instead of +connecting once to a remote host, like TCP sockets, +it can send to and receive from any host at any time. + +It is a datagram protocol: bounded blocks of data (datagrams) +are transfered over the network rather than a continuous +stream of data (TCP). Therefore, one call to send will always +match one call to receive (if the datagram is not lost), +with the same data that was sent. + +The UDP protocol is lightweight but unreliable. Unreliable +means that datagrams may be duplicated, be lost or +arrive reordered. However, if a datagram arrives, its +data is guaranteed to be valid. + +UDP is generally used for real-time communication +(audio or video streaming, real-time games, etc.) where +speed is crucial and lost data doesn't matter much. + +Sending and receiving data can use either the low-level +or the high-level functions. The low-level functions +process a raw sequence of bytes, whereas the high-level +interface uses packets (see cPacket), which are easier +to use and provide more safety regarding the data that is +exchanged. You can look at the cPacket class to get +more details about how they work. + +It is important to note that cUdpSocket is unable to send +datagrams bigger than MaxDatagramSize. In this case, it +returns an error and doesn't send anything. This applies +to both raw data and packets. Indeed, even packets are +unable to split and recompose data, due to the unreliability +of the protocol (dropped, mixed or duplicated datagrams may +lead to a big mess when trying to recompose a packet). + +If the socket is bound to a port, it is automatically +unbound from it when the socket is destroyed. However, +you can unbind the socket explicitely with the Unbind +function if necessary, to stop receiving messages or +make the port available for other sockets. + +Usage example: +@code +// ----- The client ----- + +// Create a socket and bind it to the port 55001 +cUdpSocket socket; +socket.Bind(55001); + +// Send a message to 192.168.1.50 on port 55002 +std::string message = "Hi, I am " + cIpAddress::GetLocalAddress().ToString(); +socket.Send(message.c_str(), message.size() + 1, "192.168.1.50", 55002); + +// Receive an answer (most likely from 192.168.1.50, but could be anyone else) +char buffer[1024]; +std::size_t received = 0; +cIpAddress sender; +unsigned short port; +socket.Receive(buffer, sizeof(buffer), received, sender, port); +std::cout << sender.ToString() << " said: " << buffer << std::endl; + +// ----- The server ----- + +// Create a socket and bind it to the port 55002 +cUdpSocket socket; +socket.Bind(55002); + +// Receive a message from anyone +char buffer[1024]; +std::size_t received = 0; +cIpAddress sender; +unsigned short port; +socket.Receive(buffer, sizeof(buffer), received, sender, port); +std::cout << sender.ToString() << " said: " << buffer << std::endl; + +// Send an answer +std::string message = "Welcome " + sender.ToString(); +socket.Send(message.c_str(), message.size() + 1, sender, port); +@endcode + +@see cSocket, cTcpSocket, cPacket +*/ diff --git a/include/eepp/network/sockethandle.hpp b/include/eepp/network/sockethandle.hpp new file mode 100644 index 000000000..de39a3fb3 --- /dev/null +++ b/include/eepp/network/sockethandle.hpp @@ -0,0 +1,22 @@ +#ifndef EE_NETWORKSOCKETHANDLE_HPP +#define EE_NETWORKSOCKETHANDLE_HPP + +#include + +#if EE_PLATFORM == EE_PLATFORM_WIN + #include +#endif + +namespace EE { namespace Network { + +/** Define the low-level socket handle type, specific to each platform */ + +#if EE_PLATFORM == EE_PLATFORM_WIN + typedef UINT_PTR SocketHandle; +#else + typedef int SocketHandle; +#endif + +}} + +#endif // EE_NETWORKSOCKETHANDLE_HPP diff --git a/include/eepp/system/ctime.hpp b/include/eepp/system/ctime.hpp index 113aa0f2c..9ae3cbe3c 100644 --- a/include/eepp/system/ctime.hpp +++ b/include/eepp/system/ctime.hpp @@ -86,28 +86,28 @@ EE_API bool operator !=(cTime left, cTime right); /// @brief Overload of < operator to compare two time values /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return True if \a left is lesser than \a right +/// @return True if @a left is lesser than @a right EE_API bool operator <(cTime left, cTime right); /// @relates cTime /// @brief Overload of > operator to compare two time values /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return True if \a left is greater than \a right +/// @return True if @a left is greater than @a right EE_API bool operator >(cTime left, cTime right); /// @relates cTime /// @brief Overload of <= operator to compare two time values /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return True if \a left is lesser or equal than \a right +/// @return True if @a left is lesser or equal than @a right EE_API bool operator <=(cTime left, cTime right); /// @relates cTime /// @brief Overload of >= operator to compare two time values /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return True if \a left is greater or equal than \a right +/// @return True if @a left is greater or equal than @a right EE_API bool operator >=(cTime left, cTime right); /// @relates cTime @@ -148,154 +148,152 @@ EE_API cTime& operator -=(cTime& left, cTime right); /// @brief Overload of binary * operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime operator *(cTime left, cTime right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary * operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime operator *(cTime left, eeDouble right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary * operator to scale a time value /// @param left Left operand (a number) /// @param right Right operand (a time) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime operator *(eeDouble left, cTime right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary * operator to scale a time value /// @param left Left operand (a number) /// @param right Right operand (a time) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime operator *(Int64 left, cTime right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary * operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime operator *(cTime left, Int64 right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary *= operator to scale/assign a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime& operator *=(cTime& left, eeDouble right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary *= operator to scale/assign a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime& operator *=(cTime& left, Int64 right); /// @relates cTime /// @brief Overload of binary *= operator to scale/assign a time value /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return \a left multiplied by \a right +/// @return @a left multiplied by @a right EE_API cTime& operator *=(cTime& left, cTime right); /// @relates cTime /// @brief Overload of binary / operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return \a left divided by \a right +/// @return @a left divided by @a right EE_API cTime operator /(cTime left, cTime right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary / operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left divided by \a right +/// @return @a left divided by @a right EE_API cTime operator /(cTime left, eeDouble right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary / operator to scale a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left divided by \a right +/// @return @a left divided by @a right EE_API cTime operator /(cTime left, Int64 right); -/// @relates Time +/// @relates cTime /// @brief Overload of binary /= operator to scale/assign a time value /// @param left Left operand (a time) /// @param right Right operand (a number) -/// @return \a left divided by \a right +/// @return @a left divided by @a right EE_API cTime& operator /=(cTime& left, Int64 right); /// @relates cTime /// @brief Overload of binary /= operator to scale/assign a time value /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return \a left divided by \a right +/// @return @a left divided by @a right EE_API cTime& operator /=(cTime& left, cTime right); /// @relates cTime /// @brief Overload of binary % operator to compute remainder of a time value /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return \a left modulo \a right +/// @return @a left modulo @a right EE_API cTime operator %(cTime left, cTime right); /// @relates cTime /// @brief Overload of binary %= operator to compute/assign remainder of a time value /// @param left Left operand (a time) /// @param right Right operand (a time) -/// @return \a left modulo \a right +/// @return @a left modulo @a right EE_API cTime& operator %=(cTime& left, cTime right); }} #endif -//////////////////////////////////////////////////////////// -/// \class EE::Systen::cTime -/// \ingroup System -/// -/// EE::Systen::cTime encapsulates a time value in a flexible way. -/// It allows to define a time value either as a number of -/// seconds, milliseconds or microseconds. It also works the -/// other way round: you can read a time value as either -/// a number of seconds, milliseconds or microseconds. -/// -/// By using such a flexible interface, the API doesn't -/// impose any fixed type or resolution for time values, -/// and let the user choose its own favorite representation. -/// -/// cTime values support the usual mathematical operations: -/// you can add or subtract two times, multiply or divide -/// a time by a number, compare two times, etc. -/// -/// Since they represent a time span and not an absolute time -/// value, times can also be negative. -/// -/// Usage example: -/// @code -/// cTime t1 = Seconds(0.1f); -/// eeDouble milli = t1.AsMilliseconds(); // 100 -/// -/// cTime t2 = Milliseconds(30); -/// Int64 micro = t2.AsMicroseconds(); // 30000 -/// -/// cTime t3 = Microseconds(-800000); -/// eeDouble sec = t3.AsSeconds(); // -0.8 -/// @endcode -/// -/// @code -/// void update(cTime elapsed) -/// { -/// position += speed * elapsed; -/// } -/// -/// update(Milliseconds(100)); -/// @endcode -/// -/// @see cClock -/// -//////////////////////////////////////////////////////////// - +/** +@class cTime +@ingroup System + +EE::Systen::cTime encapsulates a time value in a flexible way. +It allows to define a time value either as a number of +seconds, milliseconds or microseconds. It also works the +other way round: you can read a time value as either +a number of seconds, milliseconds or microseconds. + +By using such a flexible interface, the API doesn't +impose any fixed type or resolution for time values, +and let the user choose its own favorite representation. + +cTime values support the usual mathematical operations: +you can add or subtract two times, multiply or divide +a time by a number, compare two times, etc. + +Since they represent a time span and not an absolute time +value, times can also be negative. + +Usage example: +@code +cTime t1 = Seconds(0.1f); +eeDouble milli = t1.AsMilliseconds(); // 100 + +cTime t2 = Milliseconds(30); +Int64 micro = t2.AsMicroseconds(); // 30000 + +cTime t3 = Microseconds(-800000); +eeDouble sec = t3.AsSeconds(); // -0.8 +@endcode + +@code +void update(cTime elapsed) +{ + position += speed * elapsed; +} + +update(Milliseconds(100)); +@endcode + +@see cClock +*/ diff --git a/premake4.lua b/premake4.lua index 4fcd07ff2..f56d66f3b 100644 --- a/premake4.lua +++ b/premake4.lua @@ -293,9 +293,9 @@ function generate_os_links() table.insert( os_links, "dl" ) end elseif os.is_real("windows") then - multiple_insert( os_links, { "OpenAL32", "opengl32", "glu32", "gdi32" } ) + multiple_insert( os_links, { "OpenAL32", "opengl32", "glu32", "gdi32", "ws2_32" } ) elseif os.is_real("mingw32") then - multiple_insert( os_links, { "OpenAL32", "opengl32", "glu32", "gdi32" } ) + multiple_insert( os_links, { "OpenAL32", "opengl32", "glu32", "gdi32", "ws2_32" } ) elseif os.is_real("macosx") then multiple_insert( os_links, { "OpenGL.framework", "OpenAL.framework", "CoreFoundation.framework", "AGL.framework" } ) elseif os.is_real("freebsd") then @@ -491,8 +491,10 @@ function build_eepp( build_name ) if os.is("windows") then files { "src/eepp/system/platform/win/*.cpp" } + files { "src/eepp/network/platform/win/*.cpp" } else files { "src/eepp/system/platform/posix/*.cpp" } + files { "src/eepp/network/platform/unix/*.cpp" } end files { "src/eepp/base/*.cpp", @@ -503,6 +505,7 @@ function build_eepp( build_name ) "src/eepp/graphics/renderer/*.cpp", "src/eepp/window/*.cpp", "src/eepp/window/platform/null/*.cpp", + "src/eepp/network/*.cpp", "src/eepp/ui/*.cpp", "src/eepp/ui/tools/*.cpp", "src/eepp/physics/*.cpp", @@ -691,7 +694,7 @@ solution "eepp" build_link_configuration( "eeew", true ) project "eepp-sound" - kind "WindowedApp" + kind "ConsoleApp" language "C++" files { "src/examples/sound/*.cpp" } build_link_configuration( "eesound", true ) @@ -720,6 +723,12 @@ solution "eepp" files { "src/examples/physics/*.cpp" } build_link_configuration( "eephysics", true ) + project "eepp-http-request" + kind "ConsoleApp" + language "C++" + files { "src/examples/http_request/*.cpp" } + build_link_configuration( "eehttp-request", true ) + if os.isfile("external_projects.lua") then dofile("external_projects.lua") end diff --git a/projects/android-project/jni/Android.mk b/projects/android-project/jni/Android.mk index 3be30d1fb..c735a7284 100644 --- a/projects/android-project/jni/Android.mk +++ b/projects/android-project/jni/Android.mk @@ -48,9 +48,12 @@ CODE_SRCS := \ helper/zlib/*.c \ helper/libzip/*.c \ helper/jpeg-compressor/*.cpp \ + helper/imageresampler/*.cpp \ helper/haikuttf/*.cpp \ system/*.cpp \ system/platform/posix/*.cpp \ + network/*.cpp \ + network/platform/unix/*.cpp \ base/*.cpp \ math/*.cpp \ audio/*.cpp \ diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 44b6e2c20..4640f35a8 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + ProjectExplorer.Project.ActiveTarget @@ -50,7 +50,7 @@ {388e5431-b31b-42b3-b9ad-9002d279d75d} 10 0 - 0 + 11 /home/programming/eepp/make/linux @@ -518,6 +518,46 @@ debug-physics GenericProjectManager.GenericBuildConfiguration + + /home/programming/eepp/make/linux + + + + false + -j4 eepp-http-request + make + true + Make + + GenericProjectManager.GenericMakeStep + + 1 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + clean + make + %{buildDir} + Custom Process Step + + ProjectExplorer.ProcessStep + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + debug-test + debug-http-request + GenericProjectManager.GenericBuildConfiguration + /home/programming/eepp/make/linux @@ -847,7 +887,7 @@ release-es GenericProjectManager.GenericBuildConfiguration - 19 + 20 0 @@ -1006,6 +1046,54 @@ false true + + true + + false + false + false + false + true + 0.01 + 10 + true + 25 + + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + + /home/programming/eepp/eehttp-request-debug + false + %{buildDir} + Run /home/programming/eepp/eehttp-request-debug + eehttp-request-debug + ProjectExplorer.CustomExecutableRunConfiguration + 3768 + true + false + false + false + true + true @@ -1390,7 +1478,7 @@ false true - 11 + 12 diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 81072fe6a..08f355175 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -624,3 +624,32 @@ ../../include/eepp/system/ctime.hpp ../../src/eepp/graphics/ctexturesaver.hpp ../../src/eepp/graphics/ctexturesaver.cpp +../../include/eepp/network/cudpsocket.hpp +../../include/eepp/network/ctcpsocket.hpp +../../include/eepp/network/ctcplistener.hpp +../../include/eepp/network/base.hpp +../../include/eepp/network/csocketselector.hpp +../../include/eepp/network/sockethandle.hpp +../../include/eepp/network/csocket.hpp +../../include/eepp/network/cpacket.hpp +../../include/eepp/network/cipaddress.hpp +../../include/eepp/network/chttp.hpp +../../include/eepp/network/cftp.hpp +../../include/eepp/network.hpp +../../src/eepp/network/cudpsocket.cpp +../../src/eepp/network/ctcpsocket.cpp +../../src/eepp/network/ctcplistener.cpp +../../src/eepp/network/csocketselector.cpp +../../src/eepp/network/csocket.cpp +../../src/eepp/network/cpacket.cpp +../../src/eepp/network/cipaddress.cpp +../../src/eepp/network/chttp.cpp +../../src/eepp/network/cftp.cpp +../../src/eepp/network/platform/unix/csocketimpl.hpp +../../src/eepp/network/platform/unix/csocketimpl.cpp +../../src/eepp/network/platform/win/csocketimpl.hpp +../../src/eepp/network/platform/win/csocketimpl.cpp +../../src/eepp/network/platform/platformimpl.hpp +../../Makefile.base +../../Makefile +../../src/examples/http_request/http_request.cpp diff --git a/projects/mingw32/make.sh b/projects/mingw32/make.sh index 15d9b0a14..45f316d1f 100755 --- a/projects/mingw32/make.sh +++ b/projects/mingw32/make.sh @@ -2,15 +2,15 @@ cd $(dirname "$0") premake4 --file=../../premake4.lua --os=windows --platform=mingw32 --with-static-freetype gmake cd ../../make/mingw32/ -make $@ -e verbose=yes +make $@ cd ../../libs/mingw32/ if [ -f eepp.dll ]; then - mv eepp.dll ../../ + cp -f eepp.dll ../../eepp.dll fi if [ -f eepp-debug.dll ]; then - mv eepp-debug.dll ../../ + cp -f eepp-debug.dll ../../eepp-debug.dll fi diff --git a/src/eepp/network/cftp.cpp b/src/eepp/network/cftp.cpp new file mode 100644 index 000000000..c45b243ae --- /dev/null +++ b/src/eepp/network/cftp.cpp @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include + +namespace EE { namespace Network { + +class cFtp::DataChannel : NonCopyable { + public : + + DataChannel(cFtp& owner); + + cFtp::Response Open(cFtp::TransferMode mode); + + void Send(const std::vector& data); + + void Receive(std::vector& data); + private: + // Member data + cFtp& mFtp; ///< Reference to the owner cFtp instance + cTcpSocket mDataSocket; ///< Socket used for data transfers +}; + +cFtp::Response::Response(Status code, const std::string& message) : + mStatus (code), + mMessage(message) +{ +} + +bool cFtp::Response::IsOk() const { + return mStatus < 400; +} + +cFtp::Response::Status cFtp::Response::GetStatus() const { + return mStatus; +} + +const std::string& cFtp::Response::GetMessage() const { + return mMessage; +} + +cFtp::DirectoryResponse::DirectoryResponse(const cFtp::Response& response) : + cFtp::Response(response) +{ + if ( IsOk() ) { + // Extract the directory from the server response + std::string::size_type begin = GetMessage().find('"', 0); + std::string::size_type end = GetMessage().find('"', begin + 1); + mDirectory = GetMessage().substr(begin + 1, end - begin - 1); + } +} + +const std::string& cFtp::DirectoryResponse::GetDirectory() const { + return mDirectory; +} + +cFtp::ListingResponse::ListingResponse(const cFtp::Response& response, const std::vector& data) : + cFtp::Response(response) +{ + if ( IsOk() ) { + // Fill the array of strings + std::string paths(data.begin(), data.end()); + std::string::size_type lastPos = 0; + + for (std::string::size_type pos = paths.find("\r\n"); pos != std::string::npos; pos = paths.find("\r\n", lastPos)) { + mListing.push_back(paths.substr(lastPos, pos - lastPos)); + lastPos = pos + 2; + } + } +} + +const std::vector& cFtp::ListingResponse::getListing() const { + return mListing; +} + +cFtp::~cFtp() { + Disconnect(); +} + +cFtp::Response cFtp::Connect(const cIpAddress& server, unsigned short port, cTime timeout) { + // Connect to the server + if (mCommandSocket.Connect(server, port, timeout) != cSocket::Done) + return Response(Response::ConnectionFailed); + + // Get the response to the connection + return GetResponse(); +} + +cFtp::Response cFtp::Login() { + return Login("anonymous", "user@eepp.com.ar"); +} + +cFtp::Response cFtp::Login(const std::string& name, const std::string& password) { + Response response = SendCommand("USER", name); + if (response.IsOk()) + response = SendCommand("PASS", password); + + return response; +} + +cFtp::Response cFtp::Disconnect() { + // Send the exit command + Response response = SendCommand("QUIT"); + if (response.IsOk()) + mCommandSocket.Disconnect(); + + return response; +} + +cFtp::Response cFtp::KeepAlive() { + return SendCommand("NOOP"); +} + +cFtp::DirectoryResponse cFtp::GetWorkingDirectory() { + return DirectoryResponse(SendCommand("PWD")); +} + +cFtp::ListingResponse cFtp::GetDirectoryListing(const std::string& directory) { + // Open a data channel on default port (20) using ASCII transfer mode + std::vector directoryData; + DataChannel data(*this); + Response response = data.Open(Ascii); + + if (response.IsOk()) { + // Tell the server to send us the listing + response = SendCommand("NLST", directory); + if (response.IsOk()) { + // Receive the listing + data.Receive(directoryData); + + // Get the response from the server + response = GetResponse(); + } + } + + return ListingResponse(response, directoryData); +} + +cFtp::Response cFtp::ChangeDirectory(const std::string& directory) { + return SendCommand("CWD", directory); +} + +cFtp::Response cFtp::ParentDirectory() { + return SendCommand("CDUP"); +} + +cFtp::Response cFtp::CreateDirectory(const std::string& name) { + return SendCommand("MKD", name); +} + +cFtp::Response cFtp::DeleteDirectory(const std::string& name) { + return SendCommand("RMD", name); +} + +cFtp::Response cFtp::RenameFile(const std::string& file, const std::string& newName) { + Response response = SendCommand("RNFR", file); + if (response.IsOk()) + response = SendCommand("RNTO", newName); + + return response; +} + +cFtp::Response cFtp::DeleteFile(const std::string& name) { + return SendCommand("DELE", name); +} + +cFtp::Response cFtp::Download(const std::string& remoteFile, const std::string& localPath, TransferMode mode) { + // Open a data channel using the given transfer mode + DataChannel data(*this); + Response response = data.Open(mode); + + if (response.IsOk()) { + // Tell the server to start the transfer + response = SendCommand("RETR", remoteFile); + + if (response.IsOk()) { + // Receive the file data + std::vector fileData; + data.Receive(fileData); + + // Get the response from the server + response = GetResponse(); + if (response.IsOk()) { + // Extract the filename from the file path + std::string filename = remoteFile; + std::string::size_type pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + filename = filename.substr(pos + 1); + + // Make sure the destination path ends with a slash + std::string path = localPath; + if (!path.empty() && (path[path.size() - 1] != '\\') && (path[path.size() - 1] != '/')) + path += "/"; + + // Create the file and copy the received data into it + std::ofstream file((path + filename).c_str(), std::ios_base::binary); + if (!file) + return Response(Response::InvalidFile); + + if (!fileData.empty()) + file.write(&fileData[0], static_cast(fileData.size())); + } + } + } + + return response; +} + +cFtp::Response cFtp::Upload(const std::string& localFile, const std::string& remotePath, TransferMode mode) { + // Get the contents of the file to send + std::ifstream file(localFile.c_str(), std::ios_base::binary); + if (!file) + return Response(Response::InvalidFile); + + file.seekg(0, std::ios::end); + std::size_t length = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + std::vector fileData(length); + if (length > 0) + file.read(&fileData[0], static_cast(length)); + + // Extract the filename from the file path + std::string filename = localFile; + std::string::size_type pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) + filename = filename.substr(pos + 1); + + // Make sure the destination path ends with a slash + std::string path = remotePath; + if (!path.empty() && (path[path.size() - 1] != '\\') && (path[path.size() - 1] != '/')) + path += "/"; + + // Open a data channel using the given transfer mode + DataChannel data(*this); + Response response = data.Open(mode); + if (response.IsOk()) { + // Tell the server to start the transfer + response = SendCommand("STOR", path + filename); + if (response.IsOk()) { + // Send the file data + data.Send(fileData); + + // Get the response from the server + response = GetResponse(); + } + } + + return response; +} + +cFtp::Response cFtp::SendCommand(const std::string& command, const std::string& parameter) { + // Build the command string + std::string commandStr; + if (parameter != "") + commandStr = command + " " + parameter + "\r\n"; + else + commandStr = command + "\r\n"; + + // Send it to the server + if (mCommandSocket.Send(commandStr.c_str(), commandStr.length()) != cSocket::Done) + return Response(Response::ConnectionClosed); + + // Get the response + return GetResponse(); +} + +cFtp::Response cFtp::GetResponse() { + // We'll use a variable to keep track of the last valid code. + // It is useful in case of multi-lines responses, because the end of such a response + // will start by the same code + unsigned int lastCode = 0; + bool isInsideMultiline = false; + std::string message; + + for (;;) { + // Receive the response from the server + char buffer[1024]; + std::size_t length; + if (mCommandSocket.Receive(buffer, sizeof(buffer), length) != cSocket::Done) + return Response(Response::ConnectionClosed); + + // There can be several lines inside the received buffer, extract them all + std::istringstream in(std::string(buffer, length), std::ios_base::binary); + while (in) { + // Try to extract the code + unsigned int code; + + if (in >> code) { + // Extract the separator + char separator = 0; + in.get(separator); + + // The '-' character means a multiline response + if ((separator == '-') && !isInsideMultiline) { + // Set the multiline flag + isInsideMultiline = true; + + // Keep track of the code + if (lastCode == 0) + lastCode = code; + + // Extract the line + std::getline(in, message); + + // Remove the ending '\r' (all lines are terminated by "\r\n") + message.erase(message.length() - 1); + message = separator + message + "\n"; + } else { + // We must make sure that the code is the same, otherwise it means + // we haven't reached the end of the multiline response + if ((separator != '-') && ((code == lastCode) || (lastCode == 0))) { + // Clear the multiline flag + isInsideMultiline = false; + + // Extract the line + std::string line; + std::getline(in, line); + + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the message + if (code == lastCode) { + std::ostringstream out; + out << code << separator << line; + message += out.str(); + } else { + message = separator + line; + } + + // Return the response code and message + return Response(static_cast(code), message); + } else { + // The line we just read was actually not a response, + // only a new part of the current multiline response + + // Extract the line + std::string line; + std::getline(in, line); + + if (!line.empty()) { + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the current message + std::ostringstream out; + out << code << separator << line << "\n"; + message += out.str(); + } + } + } + } else if (lastCode != 0) { + // It seems we are in the middle of a multiline response + + // Clear the error bits of the stream + in.clear(); + + // Extract the line + std::string line; + std::getline(in, line); + + if (!line.empty()) { + // Remove the ending '\r' (all lines are terminated by "\r\n") + line.erase(line.length() - 1); + + // Append it to the current message + message += line + "\n"; + } + } else { + // Error : cannot extract the code, and we are not in a multiline response + return Response(Response::InvalidResponse); + } + } + } + + // We never reach there +} + +cFtp::DataChannel::DataChannel(cFtp& owner) : + mFtp(owner) +{ +} + +cFtp::Response cFtp::DataChannel::Open(cFtp::TransferMode mode) { + // Open a data connection in active mode (we connect to the server) + cFtp::Response response = mFtp.SendCommand("PASV"); + + if (response.IsOk()) { + // Extract the connection address and port from the response + std::string::size_type begin = response.GetMessage().find_first_of("0123456789"); + + if (begin != std::string::npos) { + Uint8 data[6] = {0, 0, 0, 0, 0, 0}; + std::string str = response.GetMessage().substr(begin); + std::size_t index = 0; + std::locale loc; + + for (int i = 0; i < 6; ++i) { + // Extract the current number + while (std::isdigit(str[index],loc)) { + data[i] = data[i] * 10 + (str[index] - '0'); + index++; + } + + // Skip separator + index++; + } + + // Reconstruct connection port and address + unsigned short port = data[4] * 256 + data[5]; + cIpAddress address(static_cast(data[0]), + static_cast(data[1]), + static_cast(data[2]), + static_cast(data[3])); + + // Connect the data channel to the server + if (mDataSocket.Connect(address, port) == cSocket::Done) { + // Translate the transfer mode to the corresponding FTP parameter + std::string modeStr; + switch (mode) { + case cFtp::Binary : modeStr = "I"; break; + case cFtp::Ascii : modeStr = "A"; break; + case cFtp::Ebcdic : modeStr = "E"; break; + } + + // Set the transfer mode + response = mFtp.SendCommand("TYPE", modeStr); + } else { + // Failed to connect to the server + response = cFtp::Response(cFtp::Response::ConnectionFailed); + } + } + } + + return response; +} + +void cFtp::DataChannel::Receive(std::vector& data) { + // Receive data + data.clear(); + char buffer[1024]; + std::size_t received; + + while (mDataSocket.Receive(buffer, sizeof(buffer), received) == cSocket::Done) { + std::copy(buffer, buffer + received, std::back_inserter(data)); + } + + // Close the data socket + mDataSocket.Disconnect(); +} + +void cFtp::DataChannel::Send(const std::vector& data) { + // Send data + if (!data.empty()) + mDataSocket.Send(&data[0], data.size()); + + // Close the data socket + mDataSocket.Disconnect(); +} + +}} diff --git a/src/eepp/network/chttp.cpp b/src/eepp/network/chttp.cpp new file mode 100644 index 000000000..68f9a0c29 --- /dev/null +++ b/src/eepp/network/chttp.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include + +namespace { + // Convert a string to lower case + std::string toLower(std::string str) { + for (std::string::iterator i = str.begin(); i != str.end(); ++i) + *i = static_cast(std::tolower(*i)); + return str; + } +} + +namespace EE { namespace Network { + +cHttp::Request::Request(const std::string& uri, Method method, const std::string& body) { + SetMethod(method); + SetUri(uri); + SetHttpVersion(1, 0); + SetBody(body); +} + +void cHttp::Request::SetField(const std::string& field, const std::string& value) { + mFields[toLower(field)] = value; +} + +void cHttp::Request::SetMethod(cHttp::Request::Method method) { + mMethod = method; +} + +void cHttp::Request::SetUri(const std::string& uri) { + mUri = uri; + + // Make sure it starts with a '/' + if (mUri.empty() || (mUri[0] != '/')) + mUri.insert(0, "/"); +} + +void cHttp::Request::SetHttpVersion(unsigned int major, unsigned int minor) { + mMajorVersion = major; + mMinorVersion = minor; +} + +void cHttp::Request::SetBody(const std::string& body) { + mBody = body; +} + +std::string cHttp::Request::Prepare() const { + std::ostringstream out; + + // Convert the method to its string representation + std::string method; + switch (mMethod) { + default : + case Get : method = "GET"; break; + case Post : method = "POST"; break; + case Head : method = "HEAD"; break; + } + + // Write the first line containing the request type + out << method << " " << mUri << " "; + out << "HTTP/" << mMajorVersion << "." << mMinorVersion << "\r\n"; + + // Write fields + for (FieldTable::const_iterator i = mFields.begin(); i != mFields.end(); ++i) { + out << i->first << ": " << i->second << "\r\n"; + } + + // Use an extra \r\n to separate the header from the body + out << "\r\n"; + + // Add the body + out << mBody; + + return out.str(); +} + +bool cHttp::Request::HasField(const std::string& field) const { + return mFields.find(toLower(field)) != mFields.end(); +} + +cHttp::Response::Response() : + mStatus (ConnectionFailed), + mMajorVersion(0), + mMinorVersion(0) +{ +} + +const std::string& cHttp::Response::GetField(const std::string& field) const { + FieldTable::const_iterator it = mFields.find(toLower(field)); + if (it != mFields.end()) { + return it->second; + } else { + static const std::string empty = ""; + return empty; + } +} + +cHttp::Response::Status cHttp::Response::GetStatus() const { + return mStatus; +} + +unsigned int cHttp::Response::GetMajorHttpVersion() const { + return mMajorVersion; +} + +unsigned int cHttp::Response::GetMinorHttpVersion() const { + return mMinorVersion; +} + +const std::string& cHttp::Response::GetBody() const { + return mBody; +} + +void cHttp::Response::Parse(const std::string& data) { + std::istringstream in(data); + + // Extract the HTTP version from the first line + std::string version; + + if (in >> version) { + std::locale loc; + if ((version.size() >= 8) && (version[6] == '.') && + (toLower(version.substr(0, 5)) == "http/") && + std::isdigit(version[5],loc) && std::isdigit(version[7],loc)) { + mMajorVersion = version[5] - '0'; + mMinorVersion = version[7] - '0'; + } else { + // Invalid HTTP version + mStatus = InvalidResponse; + return; + } + } + + // Extract the status code from the first line + int status; + + if (in >> status) { + mStatus = static_cast(status); + } else { + // Invalid status code + mStatus = InvalidResponse; + return; + } + + // Ignore the end of the first line + in.ignore(10000, '\n'); + + // Parse the other lines, which contain fields, one by one + std::string line; + while (std::getline(in, line) && (line.size() > 2)) { + std::string::size_type pos = line.find(": "); + + if (pos != std::string::npos) { + // Extract the field name and its value + std::string field = line.substr(0, pos); + std::string value = line.substr(pos + 2); + + // Remove any trailing \r + if (!value.empty() && (*value.rbegin() == '\r')) + value.erase(value.size() - 1); + + // Add the field + mFields[toLower(field)] = value; + } + } + + // Finally extract the body + mBody.clear(); + std::copy(std::istreambuf_iterator(in), std::istreambuf_iterator(), std::back_inserter(mBody)); +} + +cHttp::cHttp() : + mHost(), + mPort(0) +{ +} + +cHttp::cHttp(const std::string& host, unsigned short port) { + SetHost(host, port); +} + +void cHttp::SetHost(const std::string& host, unsigned short port) { + // Check the protocol + if (toLower(host.substr(0, 7)) == "http://") { + // HTTP protocol + mHostName = host.substr(7); + mPort = (port != 0 ? port : 80); + } else if (toLower(host.substr(0, 8)) == "https://") { + // HTTPS protocol -- unsupported (requires encryption and certificates and stuff...) + //err() << "HTTPS protocol is not supported by cHttp" << std::endl; + mHostName = ""; + mPort = 0; + } else { + // Undefined protocol - use HTTP + mHostName = host; + mPort = (port != 0 ? port : 80); + } + + // Remove any trailing '/' from the host name + if (!mHostName.empty() && (*mHostName.rbegin() == '/')) + mHostName.erase(mHostName.size() - 1); + + mHost = cIpAddress(mHostName); +} + +cHttp::Response cHttp::SendRequest(const cHttp::Request& request, cTime timeout) { + // First make sure that the request is valid -- add missing mandatory fields + Request toSend(request); + + if (!toSend.HasField("From")) { + toSend.SetField("From", "user@eepp.com.ar"); + } + + if (!toSend.HasField("User-Agent")) { + toSend.SetField("User-Agent", "eepp-network"); + } + + if (!toSend.HasField("Host")) { + toSend.SetField("Host", mHostName); + } + + if (!toSend.HasField("Content-Length")) { + std::ostringstream out; + out << toSend.mBody.size(); + toSend.SetField("Content-Length", out.str()); + } + + if ((toSend.mMethod == Request::Post) && !toSend.HasField("Content-Type")) { + toSend.SetField("Content-Type", "application/x-www-form-urlencoded"); + } + + if ((toSend.mMajorVersion * 10 + toSend.mMinorVersion >= 11) && !toSend.HasField("Connection")) { + toSend.SetField("Connection", "close"); + } + + // Prepare the response + Response received; + + // Connect the socket to the host + if (mConnection.Connect(mHost, mPort, timeout) == cSocket::Done) { + // Convert the request to string and send it through the connected socket + std::string requestStr = toSend.Prepare(); + + if (!requestStr.empty()) { + // Send it through the socket + if (mConnection.Send(requestStr.c_str(), requestStr.size()) == cSocket::Done) { + // Wait for the server's response + std::string receivedStr; + std::size_t size = 0; + char buffer[1024]; + + while (mConnection.Receive(buffer, sizeof(buffer), size) == cSocket::Done) { + receivedStr.append(buffer, buffer + size); + } + + // Build the Response object from the received data + received.Parse(receivedStr); + } + } + + // Close the connection + mConnection.Disconnect(); + } + + return received; +} + +}} diff --git a/src/eepp/network/cipaddress.cpp b/src/eepp/network/cipaddress.cpp new file mode 100644 index 000000000..e060d38ec --- /dev/null +++ b/src/eepp/network/cipaddress.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +namespace { + EE::Uint32 Resolve(const std::string& address) { + if (address == "255.255.255.255") { + // The broadcast address needs to be handled explicitely, + // because it is also the value returned by inet_addr on error + return INADDR_BROADCAST; + } else { + // Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx") + EE::Uint32 ip = inet_addr(address.c_str()); + if (ip != INADDR_NONE) + return ip; + + // Not a valid address, try to convert it as a host name + addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + addrinfo* result = NULL; + + if (getaddrinfo(address.c_str(), NULL, &hints, &result) == 0) { + if (result) { + ip = reinterpret_cast(result->ai_addr)->sin_addr.s_addr; + freeaddrinfo(result); + return ip; + } + } + + // Not a valid address nor a host name + return 0; + } + } +} + +namespace EE { namespace Network { + +const cIpAddress cIpAddress::None; +const cIpAddress cIpAddress::LocalHost(127, 0, 0, 1); +const cIpAddress cIpAddress::Broadcast(255, 255, 255, 255); + +cIpAddress::cIpAddress() : + mAddress(0) +{ + // We're using 0 (INADDR_ANY) instead of INADDR_NONE to represent the invalid address, + // because the latter is also the broadcast address (255.255.255.255); it's ok because + // eepp doesn't publicly use INADDR_ANY (it is always used implicitely) +} + +cIpAddress::cIpAddress(const std::string& address) : + mAddress(Resolve(address)) +{ +} + +cIpAddress::cIpAddress(const char* address) : + mAddress(Resolve(address)) +{ +} + +cIpAddress::cIpAddress(Uint8 byte0, Uint8 byte1, Uint8 byte2, Uint8 byte3) : + mAddress(htonl((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3)) +{ +} + +cIpAddress::cIpAddress(Uint32 address) : + mAddress(htonl(address)) +{ +} + +std::string cIpAddress::ToString() const { + in_addr address; + address.s_addr = mAddress; + + return inet_ntoa(address); +} + + +Uint32 cIpAddress::ToInteger() const { + return ntohl(mAddress); +} + +cIpAddress cIpAddress::GetLocalAddress() { + // The method here is to connect a UDP socket to anyone (here to localhost), + // and get the local socket address with the getsockname function. + // UDP connection will not send anything to the network, so this function won't cause any overhead. + + cIpAddress localAddress; + + // Create the socket + SocketHandle sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == Private::cSocketImpl::InvalidSocket()) + return localAddress; + + // Connect the socket to localhost on any port + sockaddr_in address = Private::cSocketImpl::CreateAddress(ntohl(INADDR_LOOPBACK), 0); + if (connect(sock, reinterpret_cast(&address), sizeof(address)) == -1) { + Private::cSocketImpl::Close(sock); + return localAddress; + } + + // Get the local address of the socket connection + Private::cSocketImpl::AddrLength size = sizeof(address); + if (getsockname(sock, reinterpret_cast(&address), &size) == -1) { + Private::cSocketImpl::Close(sock); + return localAddress; + } + + // Close the socket + Private::cSocketImpl::Close(sock); + + // Finally build the IP address + localAddress = cIpAddress(ntohl(address.sin_addr.s_addr)); + + return localAddress; +} + +cIpAddress cIpAddress::GetPublicAddress(cTime timeout) { + // The trick here is more complicated, because the only way + // to get our public IP address is to get it from a distant computer. + // Here we get the web page from http://www.sfml-dev.org/ip-provider.php + // and parse the result to extract our IP address + // (not very hard: the web page contains only our IP address). + cHttp server("www.sfml-dev.org"); + cHttp::Request request("/ip-provider.php", cHttp::Request::Get); + cHttp::Response page = server.SendRequest(request, timeout); + if (page.GetStatus() == cHttp::Response::Ok) + return cIpAddress(page.GetBody()); + + // Something failed: return an invalid address + return cIpAddress(); +} + +bool operator ==(const cIpAddress& left, const cIpAddress& right) { + return left.ToInteger() == right.ToInteger(); +} + +bool operator !=(const cIpAddress& left, const cIpAddress& right) { + return !(left == right); +} + +bool operator <(const cIpAddress& left, const cIpAddress& right) { + return left.ToInteger() < right.ToInteger(); +} + +bool operator >(const cIpAddress& left, const cIpAddress& right) { + return right < left; +} + +bool operator <=(const cIpAddress& left, const cIpAddress& right) { + return !(right < left); +} + +bool operator >=(const cIpAddress& left, const cIpAddress& right) { + return !(left < right); +} + +std::istream& operator >>(std::istream& stream, cIpAddress& address) { + std::string str; + stream >> str; + address = cIpAddress(str); + + return stream; +} + +std::ostream& operator <<(std::ostream& stream, const cIpAddress& address) { + return stream << address.ToString(); +} + +}} diff --git a/src/eepp/network/cpacket.cpp b/src/eepp/network/cpacket.cpp new file mode 100644 index 000000000..a306fb9d3 --- /dev/null +++ b/src/eepp/network/cpacket.cpp @@ -0,0 +1,353 @@ +#include +#include +#include + +#ifndef EE_NO_WIDECHAR +#include +#endif + +namespace EE { namespace Network { + +cPacket::cPacket() : + mReadPos(0), + mIsValid(true) +{ +} + +cPacket::~cPacket() +{ +} + +void cPacket::Append(const void* data, std::size_t sizeInBytes) { + if (data && (sizeInBytes > 0)) { + std::size_t start = mData.size(); + mData.resize(start + sizeInBytes); + std::memcpy(&mData[start], data, sizeInBytes); + } +} + +void cPacket::Clear() { + mData.clear(); + mReadPos = 0; + mIsValid = true; +} + +const void* cPacket::GetData() const { + return !mData.empty() ? &mData[0] : NULL; +} + +std::size_t cPacket::GetDataSize() const { + return mData.size(); +} + +bool cPacket::EndOfcPacket() const { + return mReadPos >= mData.size(); +} + +cPacket::operator BoolType() const { + return mIsValid ? &cPacket::CheckSize : NULL; +} + +cPacket& cPacket::operator >>(bool& data) { + Uint8 value; + if (*this >> value) + data = (value != 0); + + return *this; +} + +cPacket& cPacket::operator >>(Int8& data) { + if (CheckSize(sizeof(data))) { + data = *reinterpret_cast(&mData[mReadPos]); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(Uint8& data) { + if (CheckSize(sizeof(data))) { + data = *reinterpret_cast(&mData[mReadPos]); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(Int16& data) { + if (CheckSize(sizeof(data))) { + data = ntohs(*reinterpret_cast(&mData[mReadPos])); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(Uint16& data) { + if (CheckSize(sizeof(data))) { + data = ntohs(*reinterpret_cast(&mData[mReadPos])); + mReadPos += sizeof(data); + } + + return *this; +} + + +cPacket& cPacket::operator >>(Int32& data) { + if (CheckSize(sizeof(data))) { + data = ntohl(*reinterpret_cast(&mData[mReadPos])); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(Uint32& data) { + if (CheckSize(sizeof(data))) { + data = ntohl(*reinterpret_cast(&mData[mReadPos])); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(float& data) { + if (CheckSize(sizeof(data))) { + data = *reinterpret_cast(&mData[mReadPos]); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(double& data) { + if (CheckSize(sizeof(data))) { + data = *reinterpret_cast(&mData[mReadPos]); + mReadPos += sizeof(data); + } + + return *this; +} + +cPacket& cPacket::operator >>(char* data) { + // First extract string length + Uint32 length = 0; + *this >> length; + + if ((length > 0) && CheckSize(length)) { + // Then extract characters + std::memcpy(data, &mData[mReadPos], length); + data[length] = '\0'; + + // Update reading position + mReadPos += length; + } + + return *this; +} + +cPacket& cPacket::operator >>(std::string& data) { + // First extract string length + Uint32 length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && CheckSize(length)) + { + // Then extract characters + data.assign(&mData[mReadPos], length); + + // Update reading position + mReadPos += length; + } + + return *this; +} + +#ifndef EE_NO_WIDECHAR +cPacket& cPacket::operator >>(wchar_t* data) { + // First extract string length + Uint32 length = 0; + *this >> length; + + if ((length > 0) && CheckSize(length * sizeof(Uint32))) { + // Then extract characters + for (Uint32 i = 0; i < length; ++i) { + Uint32 character = 0; + *this >> character; + data[i] = static_cast(character); + } + + data[length] = L'\0'; + } + + return *this; +} + +cPacket& cPacket::operator >>(std::wstring& data) { + // First extract string length + Uint32 length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && CheckSize(length * sizeof(Uint32))) { + // Then extract characters + for (Uint32 i = 0; i < length; ++i) { + Uint32 character = 0; + *this >> character; + data += static_cast(character); + } + } + + return *this; +} +#endif + +cPacket& cPacket::operator >>(String& data) { + // First extract the string length + Uint32 length = 0; + *this >> length; + + data.clear(); + if ((length > 0) && CheckSize(length * sizeof(Uint32))) { + // Then extract characters + for (Uint32 i = 0; i < length; ++i) { + Uint32 character = 0; + *this >> character; + data += character; + } + } + + return *this; +} + +cPacket& cPacket::operator <<(bool data) { + *this << static_cast(data); + return *this; +} + +cPacket& cPacket::operator <<(Int8 data) { + Append(&data, sizeof(data)); + return *this; +} + +cPacket& cPacket::operator <<(Uint8 data) { + Append(&data, sizeof(data)); + return *this; +} + +cPacket& cPacket::operator <<(Int16 data) { + Int16 toWrite = htons(data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +cPacket& cPacket::operator <<(Uint16 data) { + Uint16 toWrite = htons(data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +cPacket& cPacket::operator <<(Int32 data) { + Int32 toWrite = htonl(data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +cPacket& cPacket::operator <<(Uint32 data) { + Uint32 toWrite = htonl(data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +cPacket& cPacket::operator <<(float data) { + Append(&data, sizeof(data)); + return *this; +} + +cPacket& cPacket::operator <<(double data) { + Append(&data, sizeof(data)); + return *this; +} + +cPacket& cPacket::operator <<(const char* data) { + // First insert string length + Uint32 length = std::strlen(data); + *this << length; + + // Then insert characters + Append(data, length * sizeof(char)); + + return *this; +} + +cPacket& cPacket::operator <<(const std::string& data) { + // First insert string length + Uint32 length = static_cast(data.size()); + *this << length; + + // Then insert characters + if (length > 0) + Append(data.c_str(), length * sizeof(std::string::value_type)); + + return *this; +} + +#ifndef EE_NO_WIDECHAR +cPacket& cPacket::operator <<(const wchar_t* data) { + // First insert string length + Uint32 length = std::wcslen(data); + *this << length; + + // Then insert characters + for (const wchar_t* c = data; *c != L'\0'; ++c) + *this << static_cast(*c); + + return *this; +} + +cPacket& cPacket::operator <<(const std::wstring& data) { + // First insert string length + Uint32 length = static_cast(data.size()); + *this << length; + + // Then insert characters + if (length > 0) { + for (std::wstring::const_iterator c = data.begin(); c != data.end(); ++c) + *this << static_cast(*c); + } + + return *this; +} +#endif + +cPacket& cPacket::operator <<(const String& data) { + // First insert the string length + Uint32 length = static_cast(data.size()); + *this << length; + + // Then insert characters + if (length > 0) { + for (String::ConstIterator c = data.begin(); c != data.end(); ++c) + *this << *c; + } + + return *this; +} + +bool cPacket::CheckSize(std::size_t size) { + mIsValid = mIsValid && (mReadPos + size <= mData.size()); + return mIsValid; +} + +const void* cPacket::OnSend(std::size_t& size) { + size = GetDataSize(); + return GetData(); +} + +void cPacket::OnReceive(const void* data, std::size_t size) { + Append(data, size); +} + +}} + diff --git a/src/eepp/network/csocket.cpp b/src/eepp/network/csocket.cpp new file mode 100644 index 000000000..98ab259d6 --- /dev/null +++ b/src/eepp/network/csocket.cpp @@ -0,0 +1,86 @@ +#include +#include + +namespace EE { namespace Network { + +cSocket::cSocket(Type type) : + mType(type), + mSocket (Private::cSocketImpl::InvalidSocket()), + mIsBlocking(true) +{ +} + +cSocket::~cSocket() { + // Close the socket before it gets destructed + Close(); +} + + +void cSocket::SetBlocking(bool blocking) { + // Apply if the socket is already created + if (mSocket != Private::cSocketImpl::InvalidSocket()) + Private::cSocketImpl::SetBlocking(mSocket, blocking); + + mIsBlocking = blocking; +} + +bool cSocket::IsBlocking() const { + return mIsBlocking; +} + +SocketHandle cSocket::GetHandle() const { + return mSocket; +} + +void cSocket::Create() { + // Don't create the socket if it already exists + if (mSocket == Private::cSocketImpl::InvalidSocket()) { + SocketHandle handle = socket(PF_INET, mType == Tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + Create(handle); + } +} + +void cSocket::Create(SocketHandle handle) { + // Don't create the socket if it already exists + if (mSocket == Private::cSocketImpl::InvalidSocket()) { + // Assign the new handle + mSocket = handle; + + // Set the current blocking state + SetBlocking(mIsBlocking); + + if (mType == Tcp) { + // Disable the Nagle algorithm (ie. removes buffering of TCP packets) + int yes = 1; + + if (setsockopt(mSocket, 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;*/ + } + + // On Mac OS X, disable the SIGPIPE signal on disconnection + #if EE_PLATFORM == EE_PLATFORM_MACOSX + if (setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast(&yes), sizeof(yes)) == -1) { + //err() << "Failed to set socket option \"SO_NOSIGPIPE\"" << std::endl; + } + #endif + } else { + // Enable broadcast by default for UDP sockets + int yes = 1; + + if (setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&yes), sizeof(yes)) == -1) { + //err() << "Failed to enable broadcast on UDP socket" << std::endl; + } + } + } +} + +void cSocket::Close() { + // Close the socket + if (mSocket != Private::cSocketImpl::InvalidSocket()) { + Private::cSocketImpl::Close(mSocket); + mSocket = Private::cSocketImpl::InvalidSocket(); + } +} + +}} diff --git a/src/eepp/network/csocketselector.cpp b/src/eepp/network/csocketselector.cpp new file mode 100644 index 000000000..236d4c143 --- /dev/null +++ b/src/eepp/network/csocketselector.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#ifdef _MSC_VER + #pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro +#endif + +namespace EE { namespace Network { + +struct cSocketSelector::cSocketSelectorImpl { + fd_set AllSockets; ///< Set containing all the sockets handles + fd_set SocketsReady; ///< Set containing handles of the sockets that are ready + int MaxSocket; ///< Maximum socket handle +}; + +cSocketSelector::cSocketSelector() : + mImpl( eeNew( cSocketSelectorImpl, () ) ) +{ + Clear(); +} + +cSocketSelector::cSocketSelector(const cSocketSelector& copy) : + mImpl( eeNew( cSocketSelectorImpl, (*copy.mImpl) ) ) +{ +} + +cSocketSelector::~cSocketSelector() { + eeSAFE_DELETE( mImpl ); +} + +void cSocketSelector::Add(cSocket& socket) { + SocketHandle handle = socket.GetHandle(); + + if (handle != Private::cSocketImpl::InvalidSocket()) { + FD_SET(handle, &mImpl->AllSockets); + + int size = static_cast(handle); + if (size > mImpl->MaxSocket) + mImpl->MaxSocket = size; + } +} + +void cSocketSelector::Remove(cSocket& socket) { + FD_CLR(socket.GetHandle(), &mImpl->AllSockets); + FD_CLR(socket.GetHandle(), &mImpl->SocketsReady); +} + +void cSocketSelector::Clear() { + FD_ZERO(&mImpl->AllSockets); + FD_ZERO(&mImpl->SocketsReady); + + mImpl->MaxSocket = 0; +} + +bool cSocketSelector::Wait(cTime timeout) { + // Setup the timeout + timeval time; + time.tv_sec = static_cast(timeout.AsMicroseconds() / 1000000); + time.tv_usec = static_cast(timeout.AsMicroseconds() % 1000000); + + // Initialize the set that will contain the sockets that are ready + mImpl->SocketsReady = mImpl->AllSockets; + + // Wait until one of the sockets is ready for reading, or timeout is reached + int count = select(mImpl->MaxSocket + 1, &mImpl->SocketsReady, NULL, NULL, timeout != cTime::Zero ? &time : NULL); + + return count > 0; +} + +bool cSocketSelector::IsReady(cSocket& socket) const { + return FD_ISSET(socket.GetHandle(), &mImpl->SocketsReady) != 0; +} + +cSocketSelector& cSocketSelector::operator =(const cSocketSelector& right) { + cSocketSelector temp(right); + + std::swap(mImpl, temp.mImpl); + + return *this; +} + +}} diff --git a/src/eepp/network/ctcplistener.cpp b/src/eepp/network/ctcplistener.cpp new file mode 100644 index 000000000..b0246508e --- /dev/null +++ b/src/eepp/network/ctcplistener.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +namespace EE { namespace Network { + +cTcpListener::cTcpListener() : + cSocket(Tcp) +{ +} + +unsigned short cTcpListener::GetLocalPort() const { + if (GetHandle() != Private::cSocketImpl::InvalidSocket()) { + // Retrieve informations about the local end of the socket + sockaddr_in address; + Private::cSocketImpl::AddrLength size = sizeof(address); + + if (getsockname(GetHandle(), reinterpret_cast(&address), &size) != -1) { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + +cSocket::Status cTcpListener::Listen(unsigned short port) { + // Create the internal socket if it doesn't exist + Create(); + + // Bind the socket to the specified port + sockaddr_in address = Private::cSocketImpl::CreateAddress(INADDR_ANY, port); + if (bind(GetHandle(), reinterpret_cast(&address), sizeof(address)) == -1) { + // Not likely to happen, but... + //err() << "Failed to bind listener socket to port " << port << std::endl; + return Error; + } + + // Listen to the bound port + if (::listen(GetHandle(), 0) == -1) { + // Oops, socket is deaf + //err() << "Failed to Listen to port " << port << std::endl; + return Error; + } + + return Done; +} + +void cTcpListener::Close() { + // Simply close the socket + cSocket::Close(); +} + +cSocket::Status cTcpListener::Accept(cTcpSocket& socket) { + // Make sure that we're listening + if (GetHandle() == Private::cSocketImpl::InvalidSocket()) { + //err() << "Failed to accept a new connection, the socket is not listening" << std::endl; + return Error; + } + + // Accept a new connection + sockaddr_in address; + Private::cSocketImpl::AddrLength length = sizeof(address); + SocketHandle remote = ::accept(GetHandle(), reinterpret_cast(&address), &length); + + // Check for errors + if (remote == Private::cSocketImpl::InvalidSocket()) + return Private::cSocketImpl::GetErrorStatus(); + + // Initialize the new connected socket + socket.Close(); + socket.Create(remote); + + return Done; +} + +}} diff --git a/src/eepp/network/ctcpsocket.cpp b/src/eepp/network/ctcpsocket.cpp new file mode 100644 index 000000000..2b00a29f5 --- /dev/null +++ b/src/eepp/network/ctcpsocket.cpp @@ -0,0 +1,291 @@ +#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 +{ + // Define the low-level send/receive flags, which depend on the OS + #if EE_PLATFORM == EE_PLATFORM_LINUX + const int flags = MSG_NOSIGNAL; + #else + const int flags = 0; + #endif +} + +namespace EE { namespace Network { + +cTcpSocket::cTcpSocket() : + cSocket(Tcp) +{ +} + +unsigned short cTcpSocket::GetLocalPort() const { + if (GetHandle() != Private::cSocketImpl::InvalidSocket()) { + // Retrieve informations about the local end of the socket + sockaddr_in address; + Private::cSocketImpl::AddrLength size = sizeof(address); + if (getsockname(GetHandle(), reinterpret_cast(&address), &size) != -1) { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + +cIpAddress cTcpSocket::GetRemoteAddress() const { + if (GetHandle() != Private::cSocketImpl::InvalidSocket()) { + // Retrieve informations about the remote end of the socket + sockaddr_in address; + Private::cSocketImpl::AddrLength size = sizeof(address); + if (getpeername(GetHandle(), reinterpret_cast(&address), &size) != -1) { + return cIpAddress(ntohl(address.sin_addr.s_addr)); + } + } + + // We failed to retrieve the address + return cIpAddress::None; +} + +unsigned short cTcpSocket::GetRemotePort() const { + if (GetHandle() != Private::cSocketImpl::InvalidSocket()) { + // Retrieve informations about the remote end of the socket + sockaddr_in address; + Private::cSocketImpl::AddrLength size = sizeof(address); + if (getpeername(GetHandle(), reinterpret_cast(&address), &size) != -1) { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + +cSocket::Status cTcpSocket::Connect(const cIpAddress& remoteAddress, unsigned short remotePort, cTime timeout) { + // Create the internal socket if it doesn't exist + Create(); + + // Create the remote address + sockaddr_in address = Private::cSocketImpl::CreateAddress(remoteAddress.ToInteger(), remotePort); + + if (timeout <= cTime::Zero) { + // ----- We're not using a timeout: just try to connect ----- + + // Connect the socket + if (::connect(GetHandle(), reinterpret_cast(&address), sizeof(address)) == -1) + return Private::cSocketImpl::GetErrorStatus(); + + // Connection succeeded + return Done; + } else { + // ----- We're using a timeout: we'll need a few tricks to make it work ----- + + // Save the previous blocking state + bool blocking = IsBlocking(); + + // Switch to non-blocking to enable our connection timeout + if (blocking) + SetBlocking(false); + + // Try to connect to the remote address + if (::connect(GetHandle(), reinterpret_cast(&address), sizeof(address)) >= 0) { + // We got instantly connected! (it may no happen a lot...) + SetBlocking(blocking); + return Done; + } + + // Get the error status + Status status = Private::cSocketImpl::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 == cSocket::NotReady) { + // Setup the selector + fd_set selector; + FD_ZERO(&selector); + FD_SET(GetHandle(), &selector); + + // Setup the timeout + timeval time; + time.tv_sec = static_cast(timeout.AsMicroseconds() / 1000000); + time.tv_usec = static_cast(timeout.AsMicroseconds() % 1000000); + + // Wait for something to write on our socket (which means that the connection request has returned) + if (select(static_cast(GetHandle() + 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 must check the address of the connected peer + if (GetRemoteAddress() != cIpAddress::None) { + // Connection accepted + status = Done; + } else { + // Connection refused + status = Private::cSocketImpl::GetErrorStatus(); + } + } else { + // Failed to connect before timeout is over + status = Private::cSocketImpl::GetErrorStatus(); + } + } + + // Switch back to blocking mode + SetBlocking(true); + + return status; + } +} + +void cTcpSocket::Disconnect() { + // Close the socket + Close(); + + // Reset the pending packet data + mPendingPacket = PendingPacket(); +} + +cSocket::Status cTcpSocket::Send(const void* data, std::size_t size) { + // Check the parameters + if (!data || (size == 0)) { + //err() << "Cannot send data over the network (no data to send)" << std::endl; + return Error; + } + + // Loop until every byte has been sent + int sent = 0; + int sizeToSend = static_cast(size); + for (int length = 0; length < sizeToSend; length += sent) { + // Send a chunk of data + sent = ::send(GetHandle(), static_cast(data) + length, sizeToSend - length, flags); + + // Check for errors + if (sent < 0) + return Private::cSocketImpl::GetErrorStatus(); + } + + return Done; +} + +cSocket::Status cTcpSocket::Receive(void* data, std::size_t size, std::size_t& received) { + // First clear the variables to fill + received = 0; + + // Check the destination buffer + if (!data) { + //err() << "Cannot receive data from the network (the destination buffer is invalid)" << std::endl; + return Error; + } + + // Receive a chunk of bytes + int sizeReceived = recv(GetHandle(), static_cast(data), static_cast(size), flags); + + // Check the number of bytes received + if (sizeReceived > 0) { + received = static_cast(sizeReceived); + return Done; + } else if (sizeReceived == 0) { + return cSocket::Disconnected; + } else { + return Private::cSocketImpl::GetErrorStatus(); + } +} + +cSocket::Status cTcpSocket::Send(cPacket& packet) { + // TCP is a stream protocol, it doesn't preserve messages boundaries. + // This means that we have to send the packet size first, so that the + // receiver knows the actual end of the packet in the data stream. + + // We allocate an extra memory block so that the size can be sent + // together with the data in a single call. This may seem inefficient, + // but it is actually required to avoid partial send, which could cause + // data corruption on the receiving end. + + // Get the data to send from the packet + std::size_t size = 0; + const void* data = packet.OnSend(size); + + // First convert the packet size to network byte order + Uint32 packetSize = htonl(static_cast(size)); + + // Allocate memory for the data block to send + std::vector blockToSend(sizeof(packetSize) + size); + + // Copy the packet size and data into the block to send + std::memcpy(&blockToSend[0], &packetSize, sizeof(packetSize)); + if (size > 0) + std::memcpy(&blockToSend[0] + sizeof(packetSize), data, size); + + // Send the data block + return Send(&blockToSend[0], blockToSend.size()); +} + +cSocket::Status cTcpSocket::Receive(cPacket& packet) +{ + // First clear the variables to fill + packet.Clear(); + + // We start by getting the size of the incoming packet + Uint32 packetSize = 0; + std::size_t received = 0; + if (mPendingPacket.SizeReceived < sizeof(mPendingPacket.Size)) { + // Loop until we've received the entire size of the packet + // (even a 4 byte variable may be received in more than one call) + while (mPendingPacket.SizeReceived < sizeof(mPendingPacket.Size)) { + char* data = reinterpret_cast(&mPendingPacket.Size) + mPendingPacket.SizeReceived; + Status status = Receive(data, sizeof(mPendingPacket.Size) - mPendingPacket.SizeReceived, received); + mPendingPacket.SizeReceived += received; + + if (status != Done) + return status; + } + + // The packet size has been fully received + packetSize = ntohl(mPendingPacket.Size); + } else { + // The packet size has already been received in a previous call + packetSize = ntohl(mPendingPacket.Size); + } + + // Loop until we receive all the packet data + char buffer[1024]; + while (mPendingPacket.Data.size() < packetSize) { + // Receive a chunk of data + std::size_t sizeToGet = std::min(static_cast(packetSize - mPendingPacket.Data.size()), sizeof(buffer)); + Status status = Receive(buffer, sizeToGet, received); + if (status != Done) + return status; + + // Append it into the packet + if (received > 0) { + mPendingPacket.Data.resize(mPendingPacket.Data.size() + received); + char* begin = &mPendingPacket.Data[0] + mPendingPacket.Data.size() - received; + std::memcpy(begin, buffer, received); + } + } + + // We have received all the packet data: we can copy it to the user packet + if (!mPendingPacket.Data.empty()) + packet.OnReceive(&mPendingPacket.Data[0], mPendingPacket.Data.size()); + + // Clear the pending packet data + mPendingPacket = PendingPacket(); + + return Done; +} + +cTcpSocket::PendingPacket::PendingPacket() : + Size (0), + SizeReceived(0), + Data () +{ +} + +}} diff --git a/src/eepp/network/cudpsocket.cpp b/src/eepp/network/cudpsocket.cpp new file mode 100644 index 000000000..009b2f08a --- /dev/null +++ b/src/eepp/network/cudpsocket.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +namespace EE { namespace Network { + +cUdpSocket::cUdpSocket() : + cSocket (Udp), + mBuffer(MaxDatagramSize) +{ +} + +unsigned short cUdpSocket::GetLocalPort() const { + if (GetHandle() != Private::cSocketImpl::InvalidSocket()) { + // Retrieve informations about the local end of the socket + sockaddr_in address; + Private::cSocketImpl::AddrLength size = sizeof(address); + if (getsockname(GetHandle(), reinterpret_cast(&address), &size) != -1) { + return ntohs(address.sin_port); + } + } + + // We failed to retrieve the port + return 0; +} + +cSocket::Status cUdpSocket::Bind(unsigned short port) { + // Create the internal socket if it doesn't exist + Create(); + + // Bind the socket + sockaddr_in address = Private::cSocketImpl::CreateAddress(INADDR_ANY, port); + if (::bind(GetHandle(), reinterpret_cast(&address), sizeof(address)) == -1) { + //err() << "Failed to bind socket to port " << port << std::endl; + return Error; + } + + return Done; +} + +void cUdpSocket::Unbind() { + // Simply close the socket + Close(); +} + +cSocket::Status cUdpSocket::Send(const void* data, std::size_t size, const cIpAddress& remoteAddress, unsigned short remotePort) { + // Create the internal socket if it doesn't exist + Create(); + + // Make sure that all the data will fit in one datagram + if (size > MaxDatagramSize) + { + /*err() << "Cannot send data over the network " + << "(the number of bytes to send is greater than cUdpSocket::MaxDatagramSize)" << std::endl;*/ + return Error; + } + + // Build the target address + sockaddr_in address = Private::cSocketImpl::CreateAddress(remoteAddress.ToInteger(), remotePort); + + // Send the data (unlike TCP, all the data is always sent in one call) + int sent = sendto(GetHandle(), static_cast(data), static_cast(size), 0, reinterpret_cast(&address), sizeof(address)); + + // Check for errors + if (sent < 0) + return Private::cSocketImpl::GetErrorStatus(); + + return Done; +} + +cSocket::Status cUdpSocket::Receive(void* data, std::size_t size, std::size_t& received, cIpAddress& remoteAddress, unsigned short& remotePort) { + // First clear the variables to fill + received = 0; + remoteAddress = cIpAddress(); + remotePort = 0; + + // Check the destination buffer + if (!data) { + //err() << "Cannot receive data from the network (the destination buffer is invalid)" << std::endl; + return Error; + } + + // Data that will be filled with the other computer's address + sockaddr_in address = Private::cSocketImpl::CreateAddress(INADDR_ANY, 0); + + // Receive a chunk of bytes + Private::cSocketImpl::AddrLength addressSize = sizeof(address); + int sizeReceived = recvfrom(GetHandle(), static_cast(data), static_cast(size), 0, reinterpret_cast(&address), &addressSize); + + // Check for errors + if (sizeReceived < 0) + return Private::cSocketImpl::GetErrorStatus(); + + // Fill the sender informations + received = static_cast(sizeReceived); + remoteAddress = cIpAddress(ntohl(address.sin_addr.s_addr)); + remotePort = ntohs(address.sin_port); + + return Done; +} + +cSocket::Status cUdpSocket::Send(cPacket& packet, const cIpAddress& remoteAddress, unsigned short remotePort) { + // UDP is a datagram-oriented protocol (as opposed to TCP which is a stream protocol). + // Sending one datagram is almost safe: it may be lost but if it's received, then its data + // is guaranteed to be ok. However, splitting a packet into multiple datagrams would be highly + // unreliable, since datagrams may be reordered, dropped or mixed between different sources. + // That's why eepp imposes a limit on packet size so that they can be sent in a single datagram. + // This also removes the overhead associated to packets -- there's no size to send in addition + // to the packet's data. + + // Get the data to send from the packet + std::size_t size = 0; + const void* data = packet.OnSend(size); + + // Send it + return Send(data, size, remoteAddress, remotePort); +} + +cSocket::Status cUdpSocket::Receive(cPacket& packet, cIpAddress& remoteAddress, unsigned short& remotePort) { + // See the detailed comment in Send(cPacket) above. + + // Receive the datagram + std::size_t received = 0; + Status status = Receive(&mBuffer[0], mBuffer.size(), received, remoteAddress, remotePort); + + // If we received valid data, we can copy it to the user packet + packet.Clear(); + if ((status == Done) && (received > 0)) + packet.OnReceive(&mBuffer[0], received); + + return status; +} + + +}} diff --git a/src/eepp/network/platform/platformimpl.hpp b/src/eepp/network/platform/platformimpl.hpp new file mode 100644 index 000000000..464895be0 --- /dev/null +++ b/src/eepp/network/platform/platformimpl.hpp @@ -0,0 +1,14 @@ +#ifndef EE_NETWORK_PLATFORMIMPL_HPP +#define EE_NETWORK_PLATFORMIMPL_HPP + +#include + +#if defined( EE_PLATFORM_POSIX ) + #include +#elif EE_PLATFORM == EE_PLATFORM_WIN + #include +#else + #error Sockets not implemented for this platform +#endif + +#endif diff --git a/src/eepp/network/platform/unix/csocketimpl.cpp b/src/eepp/network/platform/unix/csocketimpl.cpp new file mode 100644 index 000000000..bcfb78209 --- /dev/null +++ b/src/eepp/network/platform/unix/csocketimpl.cpp @@ -0,0 +1,58 @@ +#include + +#if defined( EE_PLATFORM_POSIX ) + +#include +#include +#include + +namespace EE { namespace Network { namespace Private { + +sockaddr_in cSocketImpl::CreateAddress(Uint32 address, unsigned short port) { + sockaddr_in addr; + std::memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + addr.sin_addr.s_addr = htonl(address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + return addr; +} + +SocketHandle cSocketImpl::InvalidSocket() { + return -1; +} + +void cSocketImpl::Close(SocketHandle sock) { + ::close(sock); +} + +void cSocketImpl::SetBlocking(SocketHandle sock, bool block) { + int status = fcntl(sock, F_GETFL); + if (block) + fcntl(sock, F_SETFL, status & ~O_NONBLOCK); + else + fcntl(sock, F_SETFL, status | O_NONBLOCK); +} + +cSocket::Status cSocketImpl::GetErrorStatus() { + // The followings are sometimes equal to EWOULDBLOCK, + // so we have to make a special case for them in order + // to avoid having double values in the switch case + if ((errno == EAGAIN) || (errno == EINPROGRESS)) + return cSocket::NotReady; + + switch (errno) { + case EWOULDBLOCK: return cSocket::NotReady; + case ECONNABORTED: return cSocket::Disconnected; + case ECONNRESET: return cSocket::Disconnected; + case ETIMEDOUT: return cSocket::Disconnected; + case ENETRESET: return cSocket::Disconnected; + case ENOTCONN: return cSocket::Disconnected; + case EPIPE: return cSocket::Disconnected; + default: return cSocket::Error; + } +} + +}}} + +#endif diff --git a/src/eepp/network/platform/unix/csocketimpl.hpp b/src/eepp/network/platform/unix/csocketimpl.hpp new file mode 100644 index 000000000..f4adf9b68 --- /dev/null +++ b/src/eepp/network/platform/unix/csocketimpl.hpp @@ -0,0 +1,53 @@ +#ifndef EE_NETWORKCSOCKETIMPL_UNIX_HPP +#define EE_NETWORKCSOCKETIMPL_UNIX_HPP + +#include + +#if defined( EE_PLATFORM_POSIX ) + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EE { namespace Network { namespace Private { + +/** @brief Helper class implementing all the non-portable socket stuff; this is the Unix version */ +class cSocketImpl { + public : + // Types + typedef socklen_t AddrLength; + + /** @brief Create an internal sockaddr_in address + ** @param address Target address + ** @param port Target port + ** @return sockaddr_in ready to be used by socket functions */ + static sockaddr_in CreateAddress(Uint32 address, unsigned short port); + + /** @brief Return the value of the invalid socket + ** @return Special value of the invalid socket */ + static SocketHandle InvalidSocket(); + + /** @brief Close and destroy a socket + ** @param sock Handle of the socket to close */ + static void Close(SocketHandle sock); + + /** @brief Set a socket as blocking or non-blocking + ** @param sock Handle of the socket + ** @param block New blocking state of the socket */ + static void SetBlocking(SocketHandle sock, bool block); + + /** Get the last socket error status + ** @return Status corresponding to the last socket error */ + static cSocket::Status GetErrorStatus(); +}; + +}}} + +#endif + +#endif // EE_NETWORKCSOCKETIMPL_UNIX_HPP diff --git a/src/eepp/network/platform/win/csocketimpl.cpp b/src/eepp/network/platform/win/csocketimpl.cpp new file mode 100644 index 000000000..583bcf203 --- /dev/null +++ b/src/eepp/network/platform/win/csocketimpl.cpp @@ -0,0 +1,65 @@ +#include +#include + +#if EE_PLATFORM == EE_PLATFORM_WIN + +namespace EE { namespace Network { namespace Private { + +sockaddr_in cSocketImpl::CreateAddress(Uint32 address, unsigned short port) { + sockaddr_in addr; + std::memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + addr.sin_addr.s_addr = htonl(address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + return addr; +} + +SocketHandle cSocketImpl::InvalidSocket() { + return INVALID_SOCKET; +} + +void cSocketImpl::Close(SocketHandle sock) { + closesocket(sock); +} + +void cSocketImpl::SetBlocking(SocketHandle sock, bool block) { + u_long blocking = block ? 0 : 1; + ioctlsocket(sock, FIONBIO, &blocking); +} + +cSocket::Status cSocketImpl::GetErrorStatus() { + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: return cSocket::NotReady; + case WSAEALREADY: return cSocket::NotReady; + case WSAECONNABORTED: return cSocket::Disconnected; + case WSAECONNRESET: return cSocket::Disconnected; + case WSAETIMEDOUT: return cSocket::Disconnected; + case WSAENETRESET: return cSocket::Disconnected; + case WSAENOTCONN: return cSocket::Disconnected; + case WSAEISCONN: return cSocket::Done; // when connecting a non-blocking socket + default: return cSocket::Error; + } +} + +/** Windows needs some initialization and cleanup to get +** sockets working properly... so let's create a class that will do it automatically */ +struct SocketInitializer +{ + SocketInitializer() + { + WSADATA init; + WSAStartup(MAKEWORD(2, 2), &init); + } + + ~SocketInitializer() + { + WSACleanup(); + } +}; + +SocketInitializer globalInitializer; + +}}} + +#endif diff --git a/src/eepp/network/platform/win/csocketimpl.hpp b/src/eepp/network/platform/win/csocketimpl.hpp new file mode 100644 index 000000000..e47a471c9 --- /dev/null +++ b/src/eepp/network/platform/win/csocketimpl.hpp @@ -0,0 +1,56 @@ +#ifndef EE_NETWORKCSOCKETIMPL_WIN_HPP +#define EE_NETWORKCSOCKETIMPL_WIN_HPP + +#include + +#if EE_PLATFORM == EE_PLATFORM_WIN + +#ifdef _WIN32_WINDOWS + #undef _WIN32_WINDOWS +#endif +#ifdef _WIN32_WINNT + #undef _WIN32_WINNT +#endif +#define _WIN32_WINDOWS 0x0501 +#define _WIN32_WINNT 0x0501 +#include +#include +#include + +namespace EE { namespace Network { namespace Private { + +/** @brief Helper class implementing all the non-portable socket stuff; this is the Windows version */ +class cSocketImpl { + public : + // Types + typedef socklen_t AddrLength; + + /** @brief Create an internal sockaddr_in address + ** @param address Target address + ** @param port Target port + ** @return sockaddr_in ready to be used by socket functions */ + static sockaddr_in CreateAddress(Uint32 address, unsigned short port); + + /** @brief Return the value of the invalid socket + ** @return Special value of the invalid socket */ + static SocketHandle InvalidSocket(); + + /** @brief Close and destroy a socket + ** @param sock Handle of the socket to close */ + static void Close(SocketHandle sock); + + /** @brief Set a socket as blocking or non-blocking + ** @param sock Handle of the socket + ** @param block New blocking state of the socket */ + static void SetBlocking(SocketHandle sock, bool block); + + /** Get the last socket error status + ** @return Status corresponding to the last socket error */ + static cSocket::Status GetErrorStatus(); +}; + +}}} + +#endif + +#endif // EE_NETWORKCSOCKETIMPL_WIN_HPP diff --git a/src/examples/http_request/http_request.cpp b/src/examples/http_request/http_request.cpp new file mode 100644 index 000000000..f6e06f860 --- /dev/null +++ b/src/examples/http_request/http_request.cpp @@ -0,0 +1,28 @@ +#include + +/// Entry point of application +EE_MAIN_FUNC int main (int argc, char * argv []) +{ + // Create a new HTTP client + cHttp http; + + // We'll work on http://www.wikipedia.org + http.SetHost("http://www.wikipedia.org"); + + // Prepare a request to get the '/' page + cHttp::Request request("/"); + + // Send the request + cHttp::Response response = http.SendRequest(request); + + // Check the status code and display the result + cHttp::Response::Status status = response.GetStatus(); + + if ( status == cHttp::Response::Ok ) { + std::cout << response.GetBody() << std::endl; + } else { + std::cout << "Error " << status << std::endl; + } + + return EXIT_SUCCESS; +}