Each project is now a module, makeing this project more portable to other premake projects
-added sfml network
This commit is contained in:
parent
5b0dfa5b1f
commit
f4ad9dc157
|
|
@ -1,9 +1,16 @@
|
|||
project"flac"
|
||||
cppdialect"c++17"
|
||||
local m = {}
|
||||
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
local ogg = require("vendor/ogg/build-ogg")
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"flac"
|
||||
language"C" -- c++ will mangle names and sfml wont build
|
||||
kind"staticLib"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
ogg.link()
|
||||
|
||||
defines
|
||||
{
|
||||
|
|
@ -14,41 +21,46 @@ project"flac"
|
|||
}
|
||||
|
||||
filter"system:linux"
|
||||
defines
|
||||
{
|
||||
"HAVE_LROUND",--fix error in lpc.c <Static declaration of 'lround' follows non-static declaration>
|
||||
"HAVE_STDINT_H" --fix error in alloc.h <# error>
|
||||
}
|
||||
defines
|
||||
{
|
||||
"HAVE_LROUND",--fix error in lpc.c <Static declaration of 'lround' follows non-static declaration>
|
||||
"HAVE_STDINT_H", --fix error in alloc.h <# error>
|
||||
}
|
||||
|
||||
includedirs
|
||||
{
|
||||
"include",
|
||||
"src/libFLAC/include",
|
||||
"../ogg/include"
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "src/libFLAC/include"),
|
||||
}
|
||||
|
||||
files
|
||||
{
|
||||
"src/libFLAC/**.c",
|
||||
path.join(scriptdir, "src/libFLAC/**.c"),
|
||||
}
|
||||
|
||||
removefiles
|
||||
{
|
||||
"src/libFLAC/deduplication/**"
|
||||
path.join(scriptdir, "src/libFLAC/deduplication/**"),
|
||||
}
|
||||
|
||||
filter"system:windows"
|
||||
files
|
||||
{
|
||||
"src/share/win_utf8_io/**.c"
|
||||
}
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
files
|
||||
{
|
||||
path.join(scriptdir, "src/share/win_utf8_io/**.c"),
|
||||
}
|
||||
|
||||
filter""
|
||||
|
||||
end
|
||||
|
||||
function m.link()
|
||||
externalincludedirs
|
||||
{
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "src/libFLAC/include"),
|
||||
path.join(scriptdir, "../ogg/include")
|
||||
}
|
||||
links {"flac", "ogg"}
|
||||
end
|
||||
|
||||
return m
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
project"freetype"
|
||||
kind"staticLib"
|
||||
cppdialect"c++17"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
objdir(intdir)
|
||||
local m = {}
|
||||
|
||||
includedirs {"include"}
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"freetype"
|
||||
kind"staticLib"
|
||||
language"C" -- c++ will mangle names and sfml wont build
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
includedirs {path.join(scriptdir, "include")}
|
||||
|
||||
defines
|
||||
{
|
||||
|
|
@ -17,75 +22,75 @@ project"freetype"
|
|||
|
||||
files
|
||||
{
|
||||
"src/autofit/autofit.c",
|
||||
"src/base/ftbase.c",
|
||||
"src/base/ftbbox.c",
|
||||
"src/base/ftbdf.c",
|
||||
"src/base/ftbitmap.c",
|
||||
"src/base/ftcid.c",
|
||||
"src/base/ftfstype.c",
|
||||
"src/base/ftgasp.c",
|
||||
"src/base/ftglyph.c",
|
||||
"src/base/ftgxval.c",
|
||||
"src/base/ftinit.c",
|
||||
"src/base/ftmm.c",
|
||||
"src/base/ftotval.c",
|
||||
"src/base/ftpatent.c",
|
||||
"src/base/ftpfr.c",
|
||||
"src/base/ftstroke.c",
|
||||
"src/base/ftsynth.c",
|
||||
"src/base/fttype1.c",
|
||||
"src/base/ftwinfnt.c",
|
||||
"src/bdf/bdf.c",
|
||||
"src/bzip2/ftbzip2.c",
|
||||
"src/cache/ftcache.c",
|
||||
"src/cff/cff.c",
|
||||
"src/cid/type1cid.c",
|
||||
"src/gzip/ftgzip.c",
|
||||
"src/lzw/ftlzw.c",
|
||||
"src/pcf/pcf.c",
|
||||
"src/pfr/pfr.c",
|
||||
"src/psaux/psaux.c",
|
||||
"src/pshinter/pshinter.c",
|
||||
"src/psnames/psnames.c",
|
||||
"src/raster/raster.c",
|
||||
"src/sdf/sdf.c",
|
||||
"src/sfnt/sfnt.c",
|
||||
"src/smooth/smooth.c",
|
||||
"src/svg/svg.c",
|
||||
"src/truetype/truetype.c",
|
||||
"src/type1/type1.c",
|
||||
"src/type42/type42.c",
|
||||
"src/winfonts/winfnt.c",
|
||||
"src/base/ftdebug.c" --fix linking errors related to FT_THROW, etc
|
||||
path.join(scriptdir, "src/autofit/autofit.c"),
|
||||
path.join(scriptdir, "src/base/ftbase.c"),
|
||||
path.join(scriptdir, "src/base/ftbbox.c"),
|
||||
path.join(scriptdir, "src/base/ftbdf.c"),
|
||||
path.join(scriptdir, "src/base/ftbitmap.c"),
|
||||
path.join(scriptdir, "src/base/ftcid.c"),
|
||||
path.join(scriptdir, "src/base/ftfstype.c"),
|
||||
path.join(scriptdir, "src/base/ftgasp.c"),
|
||||
path.join(scriptdir, "src/base/ftglyph.c"),
|
||||
path.join(scriptdir, "src/base/ftgxval.c"),
|
||||
path.join(scriptdir, "src/base/ftinit.c"),
|
||||
path.join(scriptdir, "src/base/ftmm.c"),
|
||||
path.join(scriptdir, "src/base/ftotval.c"),
|
||||
path.join(scriptdir, "src/base/ftpatent.c"),
|
||||
path.join(scriptdir, "src/base/ftpfr.c"),
|
||||
path.join(scriptdir, "src/base/ftstroke.c"),
|
||||
path.join(scriptdir, "src/base/ftsynth.c"),
|
||||
path.join(scriptdir, "src/base/fttype1.c"),
|
||||
path.join(scriptdir, "src/base/ftwinfnt.c"),
|
||||
path.join(scriptdir, "src/bdf/bdf.c"),
|
||||
path.join(scriptdir, "src/bzip2/ftbzip2.c"),
|
||||
path.join(scriptdir, "src/cache/ftcache.c"),
|
||||
path.join(scriptdir, "src/cff/cff.c"),
|
||||
path.join(scriptdir, "src/cid/type1cid.c"),
|
||||
path.join(scriptdir, "src/gzip/ftgzip.c"),
|
||||
path.join(scriptdir, "src/lzw/ftlzw.c"),
|
||||
path.join(scriptdir, "src/pcf/pcf.c"),
|
||||
path.join(scriptdir, "src/pfr/pfr.c"),
|
||||
path.join(scriptdir, "src/psaux/psaux.c"),
|
||||
path.join(scriptdir, "src/pshinter/pshinter.c"),
|
||||
path.join(scriptdir, "src/psnames/psnames.c"),
|
||||
path.join(scriptdir, "src/raster/raster.c"),
|
||||
path.join(scriptdir, "src/sdf/sdf.c"),
|
||||
path.join(scriptdir, "src/sfnt/sfnt.c"),
|
||||
path.join(scriptdir, "src/smooth/smooth.c"),
|
||||
path.join(scriptdir, "src/svg/svg.c"),
|
||||
path.join(scriptdir, "src/truetype/truetype.c"),
|
||||
path.join(scriptdir, "src/type1/type1.c"),
|
||||
path.join(scriptdir, "src/type42/type42.c"),
|
||||
path.join(scriptdir, "src/winfonts/winfnt.c"),
|
||||
path.join(scriptdir, "src/base/ftdebug.c"), --fix linking errors related to FT_THROW, et)c
|
||||
}
|
||||
|
||||
filter "system:linux"
|
||||
defines
|
||||
{
|
||||
"HAVE_FCNTL_H",--fix error in ftsystem.c <implicit declaration of function ‘open’>
|
||||
"HAVE_UNISTD_H" --fix error in ftsystem.c <implicit declaration of function ‘read’>
|
||||
}
|
||||
|
||||
filter""
|
||||
|
||||
defines
|
||||
{
|
||||
"HAVE_FCNTL_H",--fix error in ftsystem.c <implicit declaration of function ‘open’>
|
||||
"HAVE_UNISTD_H" --fix error in ftsystem.c <implicit declaration of function ‘read’>
|
||||
}
|
||||
|
||||
filter"system:windows"
|
||||
files
|
||||
{
|
||||
"builds/windows/ftsystem.c",
|
||||
"builds/windows/ftdebug.c"
|
||||
}
|
||||
files
|
||||
{
|
||||
path.join(scriptdir, "builds/windows/ftsystem.c"),
|
||||
path.join(scriptdir, "builds/windows/ftdebug.c"),
|
||||
}
|
||||
|
||||
filter"system:linux"
|
||||
files{"builds/unix/ftsystem.c"}
|
||||
files{path.join(scriptdir, "builds/unix/ftsystem.c")}
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
optimize"Speed"
|
||||
|
||||
filter""
|
||||
end
|
||||
|
||||
function m.link()
|
||||
externalincludedirs{path.join(scriptdir, "include")}
|
||||
links {"freetype"}
|
||||
end
|
||||
|
||||
return m
|
||||
|
|
@ -1,29 +1,35 @@
|
|||
project"imgui-sfml"
|
||||
cppdialect"c++17"
|
||||
kind"staticLib"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
objdir(intdir)
|
||||
local m = {}
|
||||
|
||||
includedirs
|
||||
{
|
||||
"../sfml/include",
|
||||
"."
|
||||
}
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
local sfml = require("vendor/sfml/build-sfml")
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"imgui-sfml"
|
||||
cppdialect"c++17"
|
||||
kind"staticLib"
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
files
|
||||
{
|
||||
"imgui.cpp",
|
||||
"imgui_draw.cpp",
|
||||
"imgui_tables.cpp",
|
||||
"imgui_widgets.cpp",
|
||||
"imgui-SFML.cpp"
|
||||
}
|
||||
sfml.link()
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
includedirs
|
||||
{
|
||||
scriptdir,
|
||||
}
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
files
|
||||
{
|
||||
path.join(scriptdir, "imgui.cpp"),
|
||||
path.join(scriptdir, "imgui_draw.cpp"),
|
||||
path.join(scriptdir, "imgui_tables.cpp"),
|
||||
path.join(scriptdir, "imgui_widgets.cpp"),
|
||||
path.join(scriptdir, "imgui-SFML.cpp"),
|
||||
}
|
||||
end
|
||||
|
||||
function m.link()
|
||||
externalincludedirs{scriptdir, path.join(scriptdir, "../sfml/include")}
|
||||
links {"imgui-sfml"}
|
||||
end
|
||||
|
||||
return m
|
||||
|
|
@ -1,24 +1,28 @@
|
|||
project"ogg"
|
||||
cppdialect"c++17"
|
||||
kind"staticLib"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
objdir(intdir)
|
||||
local m = {}
|
||||
|
||||
includedirs"include"
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"ogg"
|
||||
language"C" -- c++ will mangle names and sfml wont build
|
||||
kind"staticLib"
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
includedirs {path.join(scriptdir, "include")}
|
||||
|
||||
files
|
||||
{
|
||||
"src/**.h",
|
||||
"src/**.c"
|
||||
path.join(scriptdir, "src/**.h"),
|
||||
path.join(scriptdir, "src/**.c"),
|
||||
}
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
end
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
function m.link()
|
||||
links{"ogg"}
|
||||
externalincludedirs{path.join(scriptdir, "/include")}
|
||||
end
|
||||
|
||||
filter""
|
||||
return m
|
||||
|
|
@ -1,11 +1,22 @@
|
|||
project"sfml"
|
||||
local m = {}
|
||||
|
||||
links{"freetype", "ogg", "flac", "vorbis"}
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
local ogg = require("vendor/ogg/build-ogg")
|
||||
local flac = require("vendor/flac/build-flac")
|
||||
local vorbis = require("vendor/vorbis/build-vorbis")
|
||||
local freetype = require("vendor/freetype/build-freetype")
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"sfml"
|
||||
cppdialect"c++17"
|
||||
kind "staticLib"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
freetype.link()
|
||||
ogg.link()
|
||||
flac.link()
|
||||
vorbis.link()
|
||||
|
||||
defines
|
||||
{
|
||||
|
|
@ -20,52 +31,75 @@ project"sfml"
|
|||
"SFML_IS_BIG_ENDIAN=0",
|
||||
"FT2_BUILD_LIBRARY",
|
||||
"FLAC__NO_DLL",
|
||||
"OV_EXCLUDE_STATIC_CALLBACKS"
|
||||
"OV_EXCLUDE_STATIC_CALLBACKS",
|
||||
}
|
||||
|
||||
includedirs
|
||||
{
|
||||
"include",
|
||||
"src",
|
||||
"extlibs/headers/glad/include",
|
||||
"extlibs/headers/mingw",
|
||||
"extlibs/headers/miniaudio",
|
||||
"extlibs/headers/minimp3",
|
||||
"extlibs/headers/stb_image",
|
||||
"extlibs/headers/vulkan",
|
||||
"../freetype/include",
|
||||
"../ogg/include",
|
||||
"../flac/include",
|
||||
"../vorbis/include"
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "src"),
|
||||
path.join(scriptdir, "extlibs/headers/glad/include"),
|
||||
path.join(scriptdir, "extlibs/headers/mingw"),
|
||||
path.join(scriptdir, "extlibs/headers/miniaudio"),
|
||||
path.join(scriptdir, "extlibs/headers/minimp3"),
|
||||
path.join(scriptdir, "extlibs/headers/stb_image"),
|
||||
path.join(scriptdir, "extlibs/headers/vulkan"),
|
||||
|
||||
}
|
||||
|
||||
files
|
||||
{
|
||||
"include/SFML/**.hpp",
|
||||
"include/SFML/**.inl",
|
||||
"src/SFML/**.hpp",
|
||||
"src/SFML/**.cpp"
|
||||
path.join(scriptdir, "include/SFML/**.hpp"),
|
||||
path.join(scriptdir, "include/SFML/**.inl"),
|
||||
path.join(scriptdir, "src/SFML/**.hpp"),
|
||||
path.join(scriptdir, "src/SFML/**.cpp"),
|
||||
}
|
||||
|
||||
filter"system:windows"
|
||||
removefiles
|
||||
{
|
||||
"src/SFML/System/Unix/**",
|
||||
"src/SFML/Window/Unix/**"
|
||||
}
|
||||
removefiles
|
||||
{
|
||||
path.join(scriptdir, "src/SFML/System/Unix/**"),
|
||||
path.join(scriptdir, "src/SFML/Window/Unix/**"),
|
||||
path.join(scriptdir, "src/SFML/Network/Unix/**"),
|
||||
}
|
||||
|
||||
filter"system:linux"
|
||||
removefiles
|
||||
{
|
||||
"src/SFML/System/Win32/**",
|
||||
"src/SFML/Window/Win32/**",
|
||||
"src/SFML/Main/**"
|
||||
}
|
||||
removefiles
|
||||
{
|
||||
path.join(scriptdir, "src/SFML/System/Win32/**"),
|
||||
path.join(scriptdir, "src/SFML/Network/Win32/**"),
|
||||
path.join(scriptdir, "src/SFML/Window/Win32/**"),
|
||||
path.join(scriptdir, "src/SFML/Main/**"),
|
||||
}
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
end
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
function m.link()
|
||||
defines{"SFML_STATIC"}
|
||||
externalincludedirs
|
||||
{
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "src"),
|
||||
path.join(scriptdir, "extlibs/headers/glad/include"),
|
||||
path.join(scriptdir, "extlibs/headers/mingw"),
|
||||
path.join(scriptdir, "extlibs/headers/miniaudio"),
|
||||
path.join(scriptdir, "extlibs/headers/minimp3"),
|
||||
path.join(scriptdir, "extlibs/headers/stb_image"),
|
||||
path.join(scriptdir, "extlibs/headers/vulkan"),
|
||||
path.join(scriptdir, "../freetype/include"),
|
||||
path.join(scriptdir, "../ogg/include"),
|
||||
path.join(scriptdir, "../flac/include"),
|
||||
path.join(scriptdir, "../vorbis/include")
|
||||
}
|
||||
|
||||
links{"sfml", "freetype", "flac", "vorbis", "ogg"}
|
||||
|
||||
filter"system:windows"
|
||||
links{"legacy_stdio_definitions", "opengl32", "gdi32", "winmm", "ws2_32", "openal32"}
|
||||
|
||||
filter"system:linux"
|
||||
links{"Xi", "Xrandr", "Xcursor", "X11", "udev", "pthread", "OpenGL", "openal"}
|
||||
|
||||
end
|
||||
|
||||
return m
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Config.hpp>
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Portable import / export macros
|
||||
////////////////////////////////////////////////////////////
|
||||
#if defined(SFML_NETWORK_EXPORTS)
|
||||
|
||||
#define SFML_NETWORK_API SFML_API_EXPORT
|
||||
|
||||
#else
|
||||
|
||||
#define SFML_NETWORK_API SFML_API_IMPORT
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,629 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/TcpSocket.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class IpAddress;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief A FTP client
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Ftp
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Enumeration of transfer modes
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class TransferMode
|
||||
{
|
||||
Binary, //!< Binary mode (file is transferred as a sequence of bytes)
|
||||
Ascii, //!< Text mode using ASCII encoding
|
||||
Ebcdic //!< Text mode using EBCDIC encoding
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief FTP response
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Response
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Status codes possibly returned by a FTP response
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class 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: SFML custom codes
|
||||
InvalidResponse = 1000, //!< Not part of the FTP standard, generated by SFML when a received response cannot be parsed
|
||||
ConnectionFailed = 1001, //!< Not part of the FTP standard, generated by SFML when the low-level socket connection with the server fails
|
||||
ConnectionClosed = 1002, //!< Not part of the FTP standard, generated by SFML when the low-level socket connection is unexpectedly closed
|
||||
InvalidFile = 1003 //!< Not part of the FTP standard, generated by SFML when a local file cannot be read or written
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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 = Status::InvalidResponse, 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool isOk() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the status code of the response
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status getStatus() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the full message contained in the response
|
||||
///
|
||||
/// \return The response message
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] const std::string& getMessage() const;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
Status m_status; //!< Status code returned from the server
|
||||
std::string m_message; //!< Last message received from the server
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Specialization of FTP response returning a directory
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] const std::filesystem::path& getDirectory() const;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
std::filesystem::path m_directory; //!< Directory extracted from the response message
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Specialization of FTP response returning a
|
||||
/// file name listing
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_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::string& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Return the array of directory/file names
|
||||
///
|
||||
/// \return Array containing the requested listing
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] const std::vector<std::string>& getListing() const;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
std::vector<std::string> m_listing; //!< Directory/file names extracted from the data
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Destructor
|
||||
///
|
||||
/// Automatically closes the connection with the server if
|
||||
/// it is still opened.
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
~Ftp();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp(const Ftp&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp& operator=(const Ftp&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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, `Time::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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response connect(IpAddress server, unsigned short port = 21, Time timeout = Time::Zero);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Close the connection with the server
|
||||
///
|
||||
/// \return Server response to the request
|
||||
///
|
||||
/// \see `connect`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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 `directory` parameter is relative to the current
|
||||
/// working directory.
|
||||
///
|
||||
/// \param directory Directory to list
|
||||
///
|
||||
/// \return Server response to the request
|
||||
///
|
||||
/// \see `getWorkingDirectory`, `changeDirectory`, `parentDirectory`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response deleteDirectory(const std::string& name);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Rename an existing file
|
||||
///
|
||||
/// The file names 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response renameFile(const std::filesystem::path& file, const std::filesystem::path& 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response deleteFile(const std::filesystem::path& name);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Download a file from the server
|
||||
///
|
||||
/// The file name 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.
|
||||
/// If a file with the same file name as the distant file
|
||||
/// already exists in the local destination path, it will
|
||||
/// be overwritten.
|
||||
///
|
||||
/// \param remoteFile File name of the distant file to download
|
||||
/// \param localPath The directory in which to put the file on the local computer
|
||||
/// \param mode Transfer mode
|
||||
///
|
||||
/// \return Server response to the request
|
||||
///
|
||||
/// \see `upload`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response download(const std::filesystem::path& remoteFile,
|
||||
const std::filesystem::path& localPath,
|
||||
TransferMode mode = TransferMode::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.
|
||||
///
|
||||
/// The append parameter controls whether the remote file is
|
||||
/// appended to or overwritten if it already exists.
|
||||
///
|
||||
/// \param localFile Path of the local file to upload
|
||||
/// \param remotePath The directory in which to put the file on the server
|
||||
/// \param mode Transfer mode
|
||||
/// \param append Pass `true` to append to or `false` to overwrite the remote file if it already exists
|
||||
///
|
||||
/// \return Server response to the request
|
||||
///
|
||||
/// \see `download`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response upload(const std::filesystem::path& localFile,
|
||||
const std::filesystem::path& remotePath,
|
||||
TransferMode mode = TransferMode::Binary,
|
||||
bool append = false);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Send a command to the FTP server
|
||||
///
|
||||
/// While the most often used commands are provided as member
|
||||
/// functions in the `sf::Ftp` class, this method can be used
|
||||
/// to send any FTP command to the server. If the command
|
||||
/// requires one or more parameters, they can be specified
|
||||
/// in `parameter`. If the server returns information, you
|
||||
/// can extract it from the response using `Response::getMessage()`.
|
||||
///
|
||||
/// \param command Command to send
|
||||
/// \param parameter Command parameter
|
||||
///
|
||||
/// \return Server response to the request
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response sendCommand(const std::string& command, const std::string& parameter = "");
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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 data with the server
|
||||
/// on the data channel
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class DataChannel;
|
||||
|
||||
friend class DataChannel;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpSocket m_commandSocket; //!< Socket holding the control connection with the server
|
||||
std::string m_receiveBuffer; //!< Received command data that is yet to be processed
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::Ftp
|
||||
/// \ingroup network
|
||||
///
|
||||
/// `sf::Ftp` 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 implicitly 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
|
||||
/// `sf::Ftp::Response` to provide this data. The most often used
|
||||
/// commands are directly provided as member functions, but it is
|
||||
/// also possible to use specific commands with the `sendCommand()` function.
|
||||
///
|
||||
/// Note that response statuses >= 1000 are not part of the FTP standard,
|
||||
/// they are generated by SFML when an internal error occurs.
|
||||
///
|
||||
/// 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
|
||||
/// sf::Ftp ftp;
|
||||
///
|
||||
/// // Connect to the server
|
||||
/// sf::Ftp::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
|
||||
/// sf::Ftp::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", sf::Ftp::TransferMode::Ascii);
|
||||
/// if (response.isOk())
|
||||
/// std::cout << "File uploaded" << std::endl;
|
||||
///
|
||||
/// // Send specific commands (here: FEAT to list supported FTP features)
|
||||
/// response = ftp.sendCommand("FEAT");
|
||||
/// if (response.isOk())
|
||||
/// std::cout << "Feature list:\n" << response.getMessage() << std::endl;
|
||||
///
|
||||
/// // Disconnect from the server (optional)
|
||||
/// ftp.disconnect();
|
||||
/// \endcode
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/TcpSocket.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief A HTTP client
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Http
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief HTTP request
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Request
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Enumerate the available HTTP methods for a request
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class 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
|
||||
Put, //!< Request in put mode, useful for a REST API
|
||||
Delete //!< Request in delete mode, useful for a REST API
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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 = 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 available methods.
|
||||
/// The method is `Http::Request::Method::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 Http;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Prepare the final request to send to the server
|
||||
///
|
||||
/// This is used internally by Http before sending the
|
||||
/// request to the web server.
|
||||
///
|
||||
/// \return String containing the request, ready to be sent
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool hasField(const std::string& field) const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Types
|
||||
////////////////////////////////////////////////////////////
|
||||
using FieldTable = std::map<std::string, std::string>; // Use an ordered map for predictable payloads
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
FieldTable m_fields; //!< Fields of the header associated to their value
|
||||
Method m_method; //!< Method to use for the request
|
||||
std::string m_uri; //!< Target URI of the request
|
||||
unsigned int m_majorVersion{1}; //!< Major HTTP version
|
||||
unsigned int m_minorVersion{}; //!< Minor HTTP version
|
||||
std::string m_body; //!< Body of the request
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief HTTP response
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Response
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Enumerate all the valid status codes for a response
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class 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 conditional 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 authentication to be accessed
|
||||
Forbidden = 403, //!< The requested page cannot be accessed at all, even with authentication
|
||||
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: SFML custom codes
|
||||
InvalidResponse = 1000, //!< Response is not a valid HTTP one
|
||||
ConnectionFailed = 1001 //!< Connection with server failed
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the value of a field
|
||||
///
|
||||
/// If the field `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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status getStatus() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the major HTTP version number of the response
|
||||
///
|
||||
/// \return Major HTTP version number
|
||||
///
|
||||
/// \see `getMinorHttpVersion`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] unsigned int getMajorHttpVersion() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the minor HTTP version number of the response
|
||||
///
|
||||
/// \return Minor HTTP version number
|
||||
///
|
||||
/// \see `getMajorHttpVersion`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] const std::string& getBody() const;
|
||||
|
||||
private:
|
||||
friend class Http;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Construct the header from a response string
|
||||
///
|
||||
/// This function is used by `Http` to build the response
|
||||
/// of a request.
|
||||
///
|
||||
/// \param data Content of the response to parse
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void parse(const std::string& data);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Read values passed in the answer header
|
||||
///
|
||||
/// This function is used by `Http` to extract values passed
|
||||
/// in the response.
|
||||
///
|
||||
/// \param in String stream containing the header values
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void parseFields(std::istream& in);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Types
|
||||
////////////////////////////////////////////////////////////
|
||||
using FieldTable = std::map<std::string, std::string>; // Use an ordered map for predictable payloads
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
FieldTable m_fields; //!< Fields of the header
|
||||
Status m_status{Status::ConnectionFailed}; //!< Status code
|
||||
unsigned int m_majorVersion{}; //!< Major HTTP version
|
||||
unsigned int m_minorVersion{}; //!< Minor HTTP version
|
||||
std::string m_body; //!< Body of the response
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Http() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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). 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Http(const std::string& host, unsigned short port = 0);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Http(const Http&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Http& operator=(const Http&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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). 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 `Time::Zero` means that the client will use the system default timeout
|
||||
/// (which is usually pretty long).
|
||||
///
|
||||
/// \param request Request to send
|
||||
/// \param timeout Maximum time to wait
|
||||
///
|
||||
/// \return Server's response
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Response sendRequest(const Request& request, Time timeout = Time::Zero);
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpSocket m_connection; //!< Connection to the host
|
||||
std::optional<IpAddress> m_host; //!< Web host address
|
||||
std::string m_hostName; //!< Web host name
|
||||
unsigned short m_port{}; //!< Port used for connection with host
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::Http
|
||||
/// \ingroup network
|
||||
///
|
||||
/// `sf::Http` 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 HTTPS protocol is
|
||||
/// not supported.
|
||||
///
|
||||
/// The HTTP client is split into 3 classes:
|
||||
/// \li `sf::Http::Request`
|
||||
/// \li `sf::Http::Response`
|
||||
/// \li `sf::Http`
|
||||
///
|
||||
/// `sf::Http::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)
|
||||
///
|
||||
/// `sf::Http::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
|
||||
///
|
||||
/// `sf::Http` provides a simple function, SendRequest, to send a
|
||||
/// `sf::Http::Request` and return the corresponding `sf::Http::Response`
|
||||
/// from the server.
|
||||
///
|
||||
/// Usage example:
|
||||
/// \code
|
||||
/// // Create a new HTTP client
|
||||
/// sf::Http http;
|
||||
///
|
||||
/// // We'll work on http://www.sfml-dev.org
|
||||
/// http.setHost("http://www.sfml-dev.org");
|
||||
///
|
||||
/// // Prepare a request to get the 'features.php' page
|
||||
/// sf::Http::Request request("features.php");
|
||||
///
|
||||
/// // Send the request
|
||||
/// sf::Http::Response response = http.sendRequest(request);
|
||||
///
|
||||
/// // Check the status code and display the result
|
||||
/// sf::Http::Response::Status status = response.getStatus();
|
||||
/// if (status == sf::Http::Response::Status::Ok)
|
||||
/// {
|
||||
/// std::cout << response.getBody() << std::endl;
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// std::cout << "Error " << status << std::endl;
|
||||
/// }
|
||||
/// \endcode
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Encapsulate an IPv4 network address
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API IpAddress
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Construct the address from a null-terminated string view
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// \return Address if provided argument was valid, otherwise `std::nullopt`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] static std::optional<IpAddress> resolve(std::string_view address);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Construct the address from 4 bytes
|
||||
///
|
||||
/// Calling `IpAddress(a, b, c, d)` is equivalent to calling
|
||||
/// `IpAddress::resolve("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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
IpAddress(std::uint8_t byte0, std::uint8_t byte1, std::uint8_t byte2, std::uint8_t 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
|
||||
/// `IpAddress::toInteger()`.
|
||||
///
|
||||
/// \param address 4 bytes of the address packed into a 32-bits integer
|
||||
///
|
||||
/// \see `toInteger`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
explicit IpAddress(std::uint32_t 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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 `sf::IpAddress` with the proper constructor.
|
||||
///
|
||||
/// \return 32-bits unsigned integer representation of the address
|
||||
///
|
||||
/// \see `toString`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] std::uint32_t 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 on success, `std::nullopt` otherwise
|
||||
///
|
||||
/// \see `getPublicAddress`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] static std::optional<IpAddress> 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 on success, `std::nullopt` otherwise
|
||||
///
|
||||
/// \see `getLocalAddress`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] static std::optional<IpAddress> getPublicAddress(Time timeout = Time::Zero);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Static member data
|
||||
////////////////////////////////////////////////////////////
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static const IpAddress Any; //!< Value representing any address (0.0.0.0)
|
||||
static const IpAddress LocalHost; //!< The "localhost" address (for connecting a computer to itself locally)
|
||||
static const IpAddress Broadcast; //!< The "broadcast" address (for sending UDP messages to everyone on a local network)
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
friend SFML_NETWORK_API bool operator<(IpAddress left, IpAddress right);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
std::uint32_t m_address; //!< Address stored as an unsigned 32 bit 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator==(IpAddress left, IpAddress 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator!=(IpAddress left, IpAddress 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 `left` is lesser than `right`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator<(IpAddress left, IpAddress 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 `left` is greater than `right`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator>(IpAddress left, IpAddress 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator<=(IpAddress left, IpAddress 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 `left` is greater or equal than `right`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SFML_NETWORK_API bool operator>=(IpAddress left, IpAddress 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SFML_NETWORK_API std::istream& operator>>(std::istream& stream, std::optional<IpAddress>& 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SFML_NETWORK_API std::ostream& operator<<(std::ostream& stream, IpAddress address);
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::IpAddress
|
||||
/// \ingroup network
|
||||
///
|
||||
/// `sf::IpAddress` 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
|
||||
/// auto a2 = sf::IpAddress::resolve("127.0.0.1"); // the local host address
|
||||
/// auto a3 = sf::IpAddress::Broadcast; // the broadcast address
|
||||
/// sf::IpAddress a4(192, 168, 1, 56); // a local address
|
||||
/// auto a5 = sf::IpAddress::resolve("my_computer"); // a local address created from a network name
|
||||
/// auto a6 = sf::IpAddress::resolve("89.54.1.169"); // a distant address
|
||||
/// auto a7 = sf::IpAddress::resolve("www.google.com"); // a distant address created from a network name
|
||||
/// auto a8 = sf::IpAddress::getLocalAddress(); // my address on the local network
|
||||
/// auto a9 = sf::IpAddress::getPublicAddress(); // my address on the internet
|
||||
/// \endcode
|
||||
///
|
||||
/// Note that `sf::IpAddress` currently doesn't support IPv6
|
||||
/// nor other types of network addresses.
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class String;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Utility class to build blocks of data to transfer
|
||||
/// over the network
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Packet
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
/// Creates an empty packet.
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Virtual destructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
virtual ~Packet() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Copy constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet(const Packet&) = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Copy assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator=(const Packet&) = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet(Packet&&) noexcept = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator=(Packet&&) noexcept = default;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
/// \see `getReadPosition`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void append(const void* data, std::size_t sizeInBytes);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the current reading position in the packet
|
||||
///
|
||||
/// The next read operation will read data from this position
|
||||
///
|
||||
/// \return The byte offset of the current read position
|
||||
///
|
||||
/// \see `append`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] std::size_t getReadPosition() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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 a `nullptr` if the packet is empty.
|
||||
///
|
||||
/// \return Pointer to the data
|
||||
///
|
||||
/// \see `getDataSize`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool endOfPacket() 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 behavior 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
|
||||
///
|
||||
/// \return `true` if last data extraction from packet was successful
|
||||
///
|
||||
/// \see `endOfPacket`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
explicit operator bool() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// Overload of `operator>>` to read data from the packet
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(bool& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::int8_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::uint8_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::int16_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::uint16_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::int32_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::uint32_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::int64_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::uint64_t& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(float& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(double& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(char* data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::string& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(wchar_t* data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(std::wstring& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator>>(String& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// Overload of `operator<<` to write data into the packet
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(bool data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::int8_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::uint8_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::int16_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::uint16_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::int32_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::uint32_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::int64_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(std::uint64_t data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(float data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(double data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(const char* data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(const std::string& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(const wchar_t* data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(const std::wstring& data);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \overload
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& operator<<(const String& data);
|
||||
|
||||
protected:
|
||||
friend class TcpSocket;
|
||||
friend class UdpSocket;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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 decompression, 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:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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<std::byte> m_data; //!< Data stored in the packet
|
||||
std::size_t m_readPos{}; //!< Current reading position in the packet
|
||||
std::size_t m_sendPos{}; //!< Current send position in the packet (for handling partial sends)
|
||||
bool m_isValid{true}; //!< Reading state of the packet
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::Packet
|
||||
/// \ingroup network
|
||||
///
|
||||
/// Packets provide a safe and easy way to serialize data,
|
||||
/// in order to send it over the network using sockets
|
||||
/// (`sf::TcpSocket`, `sf::UdpSocket`).
|
||||
///
|
||||
/// Packets solve 2 fundamental problems that arise when
|
||||
/// transferring 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 `sf::Packet` class provides both input and output modes.
|
||||
/// It is designed to follow the behavior of standard C++ streams,
|
||||
/// using operators >> and << to extract and insert data.
|
||||
///
|
||||
/// It is recommended to use only fixed-size types (like `std::int32_t`, 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
|
||||
/// std::uint32_t x = 24;
|
||||
/// std::string s = "hello";
|
||||
/// double d = 5.89;
|
||||
///
|
||||
/// // Group the variables to send into a packet
|
||||
/// sf::Packet packet;
|
||||
/// packet << x << s << d;
|
||||
///
|
||||
/// // Send it over the network (socket is a valid sf::TcpSocket)
|
||||
/// socket.send(packet);
|
||||
///
|
||||
/// -----------------------------------------------------------------
|
||||
///
|
||||
/// // Receive the packet at the other end
|
||||
/// sf::Packet packet;
|
||||
/// socket.receive(packet);
|
||||
///
|
||||
/// // Extract the variables contained in the packet
|
||||
/// std::uint32_t x;
|
||||
/// std::string s;
|
||||
/// double d;
|
||||
/// if (packet >> x >> s >> d)
|
||||
/// {
|
||||
/// // Data extracted successfully...
|
||||
/// }
|
||||
/// \endcode
|
||||
///
|
||||
/// Packets have built-in `operator>>` and << overloads for
|
||||
/// standard types:
|
||||
/// \li `bool`
|
||||
/// \li fixed-size integer types (`int[8|16|32]_t`, `uint[8|16|32]_t`)
|
||||
/// \li floating point numbers (`float`, `double`)
|
||||
/// \li string types (`char*`, `wchar_t*`, `std::string`, `std::wstring`, `sf::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{};
|
||||
/// std::int8_t integer{};
|
||||
/// std::string str;
|
||||
/// };
|
||||
///
|
||||
/// sf::Packet& operator <<(sf::Packet& packet, const MyStruct& m)
|
||||
/// {
|
||||
/// return packet << m.number << m.integer << m.str;
|
||||
/// }
|
||||
///
|
||||
/// sf::Packet& operator >>(sf::Packet& packet, MyStruct& m)
|
||||
/// {
|
||||
/// return packet >> m.number >> m.integer >> m.str;
|
||||
/// }
|
||||
/// \endcode
|
||||
///
|
||||
/// Packets 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 `sf::Packet`, and overriding
|
||||
/// the onSend and onReceive functions.
|
||||
///
|
||||
/// Here is an example:
|
||||
/// \code
|
||||
/// class ZipPacket : public sf::Packet
|
||||
/// {
|
||||
/// const void* onSend(std::size_t& size) override
|
||||
/// {
|
||||
/// const void* srcData = getData();
|
||||
/// std::size_t srcSize = getDataSize();
|
||||
///
|
||||
/// return MySuperZipFunction(srcData, srcSize, &size);
|
||||
/// }
|
||||
///
|
||||
/// void onReceive(const void* data, std::size_t size) override
|
||||
/// {
|
||||
/// std::size_t dstSize;
|
||||
/// const void* dstData = MySuperUnzipFunction(data, size, &dstSize);
|
||||
///
|
||||
/// append(dstData, dstSize);
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// // Use like regular packets:
|
||||
/// ZipPacket packet;
|
||||
/// packet << x << s << d;
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \see `sf::TcpSocket`, `sf::UdpSocket`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/SocketHandle.hpp>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Base class for all the socket types
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API Socket
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Status codes that may be returned by socket functions
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class Status
|
||||
{
|
||||
Done, //!< The socket has sent / received the data
|
||||
NotReady, //!< The socket is not ready to send / receive data yet
|
||||
Partial, //!< The socket sent a part of the data
|
||||
Disconnected, //!< The TCP socket has been disconnected
|
||||
Error //!< An unexpected error happened
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Some special values used by sockets
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
static constexpr unsigned short AnyPort{0}; //!< Special value that tells the system to pick any available port
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Destructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
virtual ~Socket();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket(const Socket&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket& operator=(const Socket&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket(Socket&& socket) noexcept;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket& operator=(Socket&& socket) noexcept;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool isBlocking() const;
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Types of protocols that the socket can use
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
enum class 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)
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
explicit Socket(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
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] SocketHandle getNativeHandle() 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 SocketSelector;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
Type m_type; //!< Type of the socket (TCP or UDP)
|
||||
SocketHandle m_socket; //!< Socket descriptor
|
||||
bool m_isBlocking{true}; //!< Current blocking mode of the socket
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::Socket
|
||||
/// \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 (`Socket::Status::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 `sf::TcpListener`, `sf::TcpSocket`, `sf::UdpSocket`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Config.hpp>
|
||||
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
#include <basetsd.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Low-level socket handle type, specific to each platform
|
||||
////////////////////////////////////////////////////////////
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
|
||||
using SocketHandle = UINT_PTR;
|
||||
|
||||
#else
|
||||
|
||||
using SocketHandle = int;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class Socket;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Multiplexer that allows to read from multiple sockets
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API SocketSelector
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Destructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
~SocketSelector();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Copy constructor
|
||||
///
|
||||
/// \param copy Instance to copy
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector(const SocketSelector& copy);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Overload of assignment operator
|
||||
///
|
||||
/// \param right Instance to assign
|
||||
///
|
||||
/// \return Reference to self
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector& operator=(const SocketSelector& right);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector(SocketSelector&&) noexcept;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Move assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector& operator=(SocketSelector&&) noexcept;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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(Socket& 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(Socket& 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 Time::Zero for infinity)
|
||||
///
|
||||
/// \return `true` if there are sockets ready, `false` otherwise
|
||||
///
|
||||
/// \see `isReady`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool wait(Time timeout = Time::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 TcpListener,
|
||||
/// this means that it is ready to accept a new connection.
|
||||
///
|
||||
/// \param socket Socket to test
|
||||
///
|
||||
/// \return `true` if the socket is ready to read, `false` otherwise
|
||||
///
|
||||
/// \see `isReady`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] bool isReady(Socket& socket) const;
|
||||
|
||||
private:
|
||||
struct SocketSelectorImpl;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
std::unique_ptr<SocketSelectorImpl> m_impl; //!< Opaque pointer to the implementation (which requires OS-specific types)
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::SocketSelector
|
||||
/// \ingroup network
|
||||
///
|
||||
/// Socket 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 `sf::TcpListener`
|
||||
/// \li `sf::TcpSocket`
|
||||
/// \li `sf::UdpSocket`
|
||||
///
|
||||
/// 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 outside 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
|
||||
/// sf::TcpListener listener;
|
||||
/// if (listener.listen(55001) != sf::Socket::Status::Done)
|
||||
/// {
|
||||
/// // Handle error...
|
||||
/// }
|
||||
///
|
||||
/// // Create a list to store the future clients
|
||||
/// std::vector<sf::TcpSocket> clients;
|
||||
///
|
||||
/// // Create a selector
|
||||
/// sf::SocketSelector 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
|
||||
/// sf::TcpSocket client;
|
||||
/// if (listener.accept(client) == sf::Socket::Status::Done)
|
||||
/// {
|
||||
/// // Add the new client to the selector so that we will
|
||||
/// // be notified when they send something
|
||||
/// selector.add(client);
|
||||
///
|
||||
/// // Add the new client to the clients list
|
||||
/// clients.push_back(std::move(client));
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// // Handle error...
|
||||
/// }
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// // The listener socket is not ready, test all other sockets (the clients)
|
||||
/// for (sf::TcpSocket& client : clients)
|
||||
/// {
|
||||
/// if (selector.isReady(client))
|
||||
/// {
|
||||
/// // The client has sent some data, we can receive it
|
||||
/// sf::Packet packet;
|
||||
/// if (client.receive(packet) == sf::Socket::Status::Done)
|
||||
/// {
|
||||
/// ...
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// \endcode
|
||||
///
|
||||
/// \see `sf::Socket`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class TcpSocket;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Socket that listens to new TCP connections
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API TcpListener : public Socket
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpListener();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] unsigned short getLocalPort() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Start listening for incoming connection attempts
|
||||
///
|
||||
/// This function makes the socket start listening on the
|
||||
/// specified port, waiting for incoming connection attempts.
|
||||
///
|
||||
/// If the socket is already listening on a port when this
|
||||
/// function is called, it will stop listening on the old
|
||||
/// port before starting to listen on the new port.
|
||||
///
|
||||
/// When providing `sf::Socket::AnyPort` as port, the listener
|
||||
/// will request an available port from the system.
|
||||
/// The chosen port can be retrieved by calling `getLocalPort()`.
|
||||
///
|
||||
/// \param port Port to listen on for incoming connection attempts
|
||||
/// \param address Address of the interface to listen on
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `accept`, `close`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status listen(unsigned short port, IpAddress address = IpAddress::Any);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status accept(TcpSocket& socket);
|
||||
};
|
||||
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::TcpListener
|
||||
/// \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 `sf::TcpSocket` 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 `sf::TcpSocket` 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
|
||||
/// sf::TcpListener listener;
|
||||
/// listener.listen(55001);
|
||||
///
|
||||
/// // Endless loop that waits for new connections
|
||||
/// while (running)
|
||||
/// {
|
||||
/// sf::TcpSocket client;
|
||||
/// if (listener.accept(client) == sf::Socket::Done)
|
||||
/// {
|
||||
/// // A new client just connected!
|
||||
/// std::cout << "New connection received from " << client.getRemoteAddress().value() << std::endl;
|
||||
/// doSomethingWith(client);
|
||||
/// }
|
||||
/// }
|
||||
/// \endcode
|
||||
///
|
||||
/// \see `sf::TcpSocket`, `sf::Socket`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class TcpListener;
|
||||
class IpAddress;
|
||||
class Packet;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Specialized socket using the TCP protocol
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API TcpSocket : public Socket
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpSocket();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] unsigned short getLocalPort() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the address of the connected peer
|
||||
///
|
||||
/// If the socket is not connected, this function returns
|
||||
/// an unset optional.
|
||||
///
|
||||
/// \return Address of the remote peer
|
||||
///
|
||||
/// \see `getRemotePort`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] std::optional<IpAddress> 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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 is already connected, the connection is
|
||||
/// forcibly disconnected before attempting to connect again.
|
||||
///
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status connect(IpAddress remoteAddress, unsigned short remotePort, Time timeout = Time::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
|
||||
///
|
||||
/// To be able to handle partial sends over non-blocking
|
||||
/// sockets, use the `send(const void*, std::size_t, std::size_t&)`
|
||||
/// overload instead.
|
||||
/// 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status send(const void* data, std::size_t size);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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
|
||||
/// \param sent The number of bytes sent will be written here
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `receive`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status send(const void* data, std::size_t size, std::size_t& sent);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status receive(void* data, std::size_t size, std::size_t& received);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Send a formatted packet of data to the remote peer
|
||||
///
|
||||
/// In non-blocking mode, if this function returns `sf::Socket::Status::Partial`,
|
||||
/// you \em must retry sending the same unmodified packet before sending
|
||||
/// anything else in order to guarantee the packet arrives at the remote
|
||||
/// peer uncorrupted.
|
||||
/// This function will fail if the socket is not connected.
|
||||
///
|
||||
/// \param packet Packet to send
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `receive`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status send(Packet& 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 Packet to fill with the received data
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `send`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status receive(Packet& packet);
|
||||
|
||||
private:
|
||||
friend class TcpListener;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Structure holding the data of a pending packet
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
struct PendingPacket
|
||||
{
|
||||
std::uint32_t size{}; //!< Data of packet size
|
||||
std::size_t sizeReceived{}; //!< Number of size bytes received so far
|
||||
std::vector<std::byte> data; //!< Data of the packet
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
PendingPacket m_pendingPacket; //!< Temporary data of the packet currently being received
|
||||
std::vector<std::byte> m_blockToSendBuffer; //!< Buffer used to prepare data being sent from the socket
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::TcpSocket
|
||||
/// \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 information 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 `sf::Packet`),
|
||||
/// which are easier to use and provide more safety regarding
|
||||
/// the data that is exchanged. You can look at the `sf::Packet`
|
||||
/// class to get more details about how they work.
|
||||
///
|
||||
/// The socket is automatically disconnected when it is destroyed,
|
||||
/// but if you want to explicitly 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
|
||||
/// sf::TcpSocket 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
|
||||
/// std::array<char, 1024> buffer;
|
||||
/// std::size_t received = 0;
|
||||
/// socket.receive(buffer.data(), buffer.size(), received);
|
||||
/// std::cout << "The server said: " << buffer.data() << std::endl;
|
||||
///
|
||||
/// // ----- The server -----
|
||||
///
|
||||
/// // Create a listener to wait for incoming connections on port 55001
|
||||
/// sf::TcpListener listener;
|
||||
/// listener.listen(55001);
|
||||
///
|
||||
/// // Wait for a connection
|
||||
/// sf::TcpSocket socket;
|
||||
/// listener.accept(socket);
|
||||
/// std::cout << "New client connected: " << socket.getRemoteAddress().value() << std::endl;
|
||||
///
|
||||
/// // Receive a message from the client
|
||||
/// std::array<char, 1024> buffer;
|
||||
/// std::size_t received = 0;
|
||||
/// socket.receive(buffer.data(), buffer.size(), received);
|
||||
/// std::cout << "The client said: " << buffer.data() << std::endl;
|
||||
///
|
||||
/// // Send an answer
|
||||
/// std::string message = "Welcome, client";
|
||||
/// socket.send(message.c_str(), message.size() + 1);
|
||||
/// \endcode
|
||||
///
|
||||
/// \see `sf::Socket`, `sf::UdpSocket`, `sf::Packet`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Export.hpp>
|
||||
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class Packet;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Specialized socket using the UDP protocol
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SFML_NETWORK_API UdpSocket : public Socket
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
////////////////////////////////////////////////////////////
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
static constexpr std::size_t MaxDatagramSize{65507}; //!< The maximum number of bytes that can be sent in a single UDP datagram
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
UdpSocket();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] 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.
|
||||
///
|
||||
/// When providing `sf::Socket::AnyPort` as port, the listener
|
||||
/// will request an available port from the system.
|
||||
/// The chosen port can be retrieved by calling `getLocalPort()`.
|
||||
///
|
||||
/// Since the socket can only be bound to a single port at
|
||||
/// any given moment, if it is already bound when this
|
||||
/// function is called, it will be unbound from the previous
|
||||
/// port before being bound to the new one.
|
||||
///
|
||||
/// \param port Port to bind the socket to
|
||||
/// \param address Address of the interface to bind to
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `unbind`, `getLocalPort`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status bind(unsigned short port, IpAddress address = IpAddress::Any);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Unbind the socket from the local port to which it is bound
|
||||
///
|
||||
/// The port that the socket was previously bound to is immediately
|
||||
/// made available to the operating system after this function is called.
|
||||
/// This means that a subsequent call to `bind()` will be able to re-bind
|
||||
/// the port if no other process has done so in the mean time.
|
||||
/// 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 `size` is not greater than
|
||||
/// `UdpSocket::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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status send(const void* data, std::size_t size, IpAddress 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status receive(void* data,
|
||||
std::size_t size,
|
||||
std::size_t& received,
|
||||
std::optional<IpAddress>& 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
|
||||
/// `UdpSocket::MaxDatagramSize`, otherwise this function will
|
||||
/// fail and no data will be sent.
|
||||
///
|
||||
/// \param packet Packet to send
|
||||
/// \param remoteAddress Address of the receiver
|
||||
/// \param remotePort Port of the receiver to send the data to
|
||||
///
|
||||
/// \return Status code
|
||||
///
|
||||
/// \see `receive`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status send(Packet& packet, IpAddress 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 Packet 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`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] Status receive(Packet& packet, std::optional<IpAddress>& remoteAddress, unsigned short& remotePort);
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
std::vector<std::byte> m_buffer{MaxDatagramSize}; //!< Temporary buffer holding the received data in Receive(Packet)
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::UdpSocket
|
||||
/// \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 transferred 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 `sf::Packet`), which are easier
|
||||
/// to use and provide more safety regarding the data that is
|
||||
/// exchanged. You can look at the `sf::Packet` class to get
|
||||
/// more details about how they work.
|
||||
///
|
||||
/// It is important to note that `UdpSocket` 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 explicitly 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
|
||||
/// sf::UdpSocket socket;
|
||||
/// socket.bind(55001);
|
||||
///
|
||||
/// // Send a message to 192.168.1.50 on port 55002
|
||||
/// std::string message = "Hi, I am " + sf::IpAddress::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)
|
||||
/// std::array<char, 1024> buffer;
|
||||
/// std::size_t received = 0;
|
||||
/// std::optional<sf::IpAddress> sender;
|
||||
/// unsigned short port;
|
||||
/// if (socket.receive(buffer.data(), buffer.size(), received, sender, port) == sf::Socket::Status::Done)
|
||||
/// std::cout << sender->toString() << " said: " << buffer.data() << std::endl;
|
||||
///
|
||||
/// // ----- The server -----
|
||||
///
|
||||
/// // Create a socket and bind it to the port 55002
|
||||
/// sf::UdpSocket socket;
|
||||
/// socket.bind(55002);
|
||||
///
|
||||
/// // Receive a message from anyone
|
||||
/// std::array<char, 1024> buffer;
|
||||
/// std::size_t received = 0;
|
||||
/// std::optional<sf::IpAddress> sender;
|
||||
/// unsigned short port;
|
||||
/// if (socket.receive(buffer.data(), buffer.size(), received, sender, port) == sf::Socket::Status::Done)
|
||||
/// std::cout << sender->toString() << " said: " << buffer.data() << std::endl;
|
||||
///
|
||||
/// // Send an answer
|
||||
/// std::string message = "Welcome " + sender.toString();
|
||||
/// socket.send(message.c_str(), message.size() + 1, sender, port);
|
||||
/// \endcode
|
||||
///
|
||||
/// \see `sf::Socket`, `sf::TcpSocket`, `sf::Packet`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
|
|
@ -0,0 +1,643 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Ftp.hpp>
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
class Ftp::DataChannel
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
explicit DataChannel(Ftp& owner);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy constructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
DataChannel(const DataChannel&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleted copy assignment
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
DataChannel& operator=(const DataChannel&) = delete;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response open(Ftp::TransferMode mode);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void send(std::istream& stream);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void receive(std::ostream& stream);
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp& m_ftp; //!< Reference to the owner Ftp instance
|
||||
TcpSocket m_dataSocket; //!< Socket used for data transfers
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response::Response(Status code, std::string message) : m_status(code), m_message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Ftp::Response::isOk() const
|
||||
{
|
||||
return static_cast<int>(m_status) < 400;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response::Status Ftp::Response::getStatus() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const std::string& Ftp::Response::getMessage() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::DirectoryResponse::DirectoryResponse(const Ftp::Response& response) : Ftp::Response(response)
|
||||
{
|
||||
if (isOk())
|
||||
{
|
||||
// Extract the directory from the server response
|
||||
const std::string::size_type begin = getMessage().find('"', 0);
|
||||
const std::string::size_type end = getMessage().find('"', begin + 1);
|
||||
m_directory = getMessage().substr(begin + 1, end - begin - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const std::filesystem::path& Ftp::DirectoryResponse::getDirectory() const
|
||||
{
|
||||
return m_directory;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::ListingResponse::ListingResponse(const Ftp::Response& response, const std::string& data) : Ftp::Response(response)
|
||||
{
|
||||
if (isOk())
|
||||
{
|
||||
// Fill the array of strings
|
||||
std::string::size_type lastPos = 0;
|
||||
for (std::string::size_type pos = data.find("\r\n"); pos != std::string::npos; pos = data.find("\r\n", lastPos))
|
||||
{
|
||||
m_listing.push_back(data.substr(lastPos, pos - lastPos));
|
||||
lastPos = pos + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const std::vector<std::string>& Ftp::ListingResponse::getListing() const
|
||||
{
|
||||
return m_listing;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::~Ftp()
|
||||
{
|
||||
(void)disconnect();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::connect(IpAddress server, unsigned short port, Time timeout)
|
||||
{
|
||||
// Connect to the server
|
||||
if (m_commandSocket.connect(server, port, timeout) != Socket::Status::Done)
|
||||
return Response(Response::Status::ConnectionFailed);
|
||||
|
||||
// Get the response to the connection
|
||||
return getResponse();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::login()
|
||||
{
|
||||
return login("anonymous", "user@sfml-dev.org");
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::login(const std::string& name, const std::string& password)
|
||||
{
|
||||
Response response = sendCommand("USER", name);
|
||||
if (response.isOk())
|
||||
response = sendCommand("PASS", password);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::disconnect()
|
||||
{
|
||||
// Send the exit command
|
||||
Response response = sendCommand("QUIT");
|
||||
if (response.isOk())
|
||||
m_commandSocket.disconnect();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::keepAlive()
|
||||
{
|
||||
return sendCommand("NOOP");
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::DirectoryResponse Ftp::getWorkingDirectory()
|
||||
{
|
||||
return {sendCommand("PWD")};
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::ListingResponse Ftp::getDirectoryListing(const std::string& directory)
|
||||
{
|
||||
// Open a data channel on default port (20) using ASCII transfer mode
|
||||
std::ostringstream directoryData;
|
||||
DataChannel data(*this);
|
||||
Response response = data.open(TransferMode::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 {response, directoryData.str()};
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::changeDirectory(const std::string& directory)
|
||||
{
|
||||
return sendCommand("CWD", directory);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::parentDirectory()
|
||||
{
|
||||
return sendCommand("CDUP");
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::createDirectory(const std::string& name)
|
||||
{
|
||||
return sendCommand("MKD", name);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::deleteDirectory(const std::string& name)
|
||||
{
|
||||
return sendCommand("RMD", name);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::renameFile(const std::filesystem::path& file, const std::filesystem::path& newName)
|
||||
{
|
||||
Response response = sendCommand("RNFR", file.string());
|
||||
if (response.isOk())
|
||||
response = sendCommand("RNTO", newName.string());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::deleteFile(const std::filesystem::path& name)
|
||||
{
|
||||
return sendCommand("DELE", name.string());
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::download(const std::filesystem::path& remoteFile, const std::filesystem::path& 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.string());
|
||||
if (response.isOk())
|
||||
{
|
||||
// Create the file and truncate it if necessary
|
||||
const std::filesystem::path filepath = localPath / remoteFile.filename();
|
||||
std::ofstream file(filepath, std::ios_base::binary | std::ios_base::trunc);
|
||||
if (!file)
|
||||
return Response(Response::Status::InvalidFile);
|
||||
|
||||
// Receive the file data
|
||||
data.receive(file);
|
||||
|
||||
// Close the file
|
||||
file.close();
|
||||
|
||||
// Get the response from the server
|
||||
response = getResponse();
|
||||
|
||||
// If the download was unsuccessful, delete the partial file
|
||||
if (!response.isOk())
|
||||
std::filesystem::remove(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::upload(const std::filesystem::path& localFile,
|
||||
const std::filesystem::path& remotePath,
|
||||
TransferMode mode,
|
||||
bool append)
|
||||
{
|
||||
// Get the contents of the file to send
|
||||
std::ifstream file(localFile, std::ios_base::binary);
|
||||
if (!file)
|
||||
return Response(Response::Status::InvalidFile);
|
||||
|
||||
// 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(append ? "APPE" : "STOR", (remotePath / localFile.filename()).string());
|
||||
if (response.isOk())
|
||||
{
|
||||
// Send the file data
|
||||
data.send(file);
|
||||
|
||||
// Get the response from the server
|
||||
response = getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::sendCommand(const std::string& command, const std::string& parameter)
|
||||
{
|
||||
// Build the command string
|
||||
const std::string commandStr = parameter.empty() ? command + "\r\n" : command + " " + parameter + "\r\n";
|
||||
|
||||
// Send it to the server
|
||||
if (m_commandSocket.send(commandStr.c_str(), commandStr.length()) != Socket::Status::Done)
|
||||
return Response(Response::Status::ConnectionClosed);
|
||||
|
||||
// Get the response
|
||||
return getResponse();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::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
|
||||
std::array<char, 1024> buffer{};
|
||||
std::size_t length = 0;
|
||||
|
||||
if (m_receiveBuffer.empty())
|
||||
{
|
||||
if (m_commandSocket.receive(buffer.data(), buffer.size(), length) != Socket::Status::Done)
|
||||
return Response(Response::Status::ConnectionClosed);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::copy(m_receiveBuffer.begin(), m_receiveBuffer.end(), buffer.data());
|
||||
length = m_receiveBuffer.size();
|
||||
m_receiveBuffer.clear();
|
||||
}
|
||||
|
||||
// There can be several lines inside the received buffer, extract them all
|
||||
std::istringstream in(std::string(buffer.data(), length), std::ios_base::binary);
|
||||
while (in)
|
||||
{
|
||||
// Try to extract the code
|
||||
unsigned int code = 0;
|
||||
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)))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Save the remaining data for the next time getResponse() is called
|
||||
m_receiveBuffer.assign(buffer.data() + static_cast<std::size_t>(in.tellg()),
|
||||
length - static_cast<std::size_t>(in.tellg()));
|
||||
|
||||
// Return the response code and message
|
||||
return Response(static_cast<Response::Status>(code), message);
|
||||
}
|
||||
|
||||
// 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::Status::InvalidResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We never reach there
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::DataChannel::DataChannel(Ftp& owner) : m_ftp(owner)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Ftp::Response Ftp::DataChannel::open(Ftp::TransferMode mode)
|
||||
{
|
||||
// Open a data connection in active mode (we connect to the server)
|
||||
Ftp::Response response = m_ftp.sendCommand("PASV");
|
||||
if (response.isOk())
|
||||
{
|
||||
// Extract the connection address and port from the response
|
||||
const std::string::size_type begin = response.getMessage().find_first_of("0123456789");
|
||||
if (begin != std::string::npos)
|
||||
{
|
||||
std::array<std::uint8_t, 6> data{};
|
||||
std::string str = response.getMessage().substr(begin);
|
||||
std::size_t index = 0;
|
||||
for (unsigned char& datum : data)
|
||||
{
|
||||
// Extract the current number
|
||||
while (std::isdigit(str[index]))
|
||||
{
|
||||
datum = static_cast<std::uint8_t>(
|
||||
static_cast<std::uint8_t>(datum * 10) + static_cast<std::uint8_t>(str[index] - '0'));
|
||||
++index;
|
||||
}
|
||||
|
||||
// Skip separator
|
||||
++index;
|
||||
}
|
||||
|
||||
// Reconstruct connection port and address
|
||||
const auto port = static_cast<std::uint16_t>(data[4] * 256 + data[5]);
|
||||
const IpAddress address(data[0], data[1], data[2], data[3]);
|
||||
|
||||
// Connect the data channel to the server
|
||||
if (m_dataSocket.connect(address, port) == Socket::Status::Done)
|
||||
{
|
||||
// Translate the transfer mode to the corresponding FTP parameter
|
||||
std::string modeStr;
|
||||
switch (mode)
|
||||
{
|
||||
case Ftp::TransferMode::Binary:
|
||||
modeStr = "I";
|
||||
break;
|
||||
case Ftp::TransferMode::Ascii:
|
||||
modeStr = "A";
|
||||
break;
|
||||
case Ftp::TransferMode::Ebcdic:
|
||||
modeStr = "E";
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the transfer mode
|
||||
response = m_ftp.sendCommand("TYPE", modeStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to connect to the server
|
||||
response = Ftp::Response(Ftp::Response::Status::ConnectionFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Ftp::DataChannel::receive(std::ostream& stream)
|
||||
{
|
||||
// Receive data
|
||||
std::array<char, 1024> buffer{};
|
||||
std::size_t received = 0;
|
||||
while (m_dataSocket.receive(buffer.data(), buffer.size(), received) == Socket::Status::Done)
|
||||
{
|
||||
stream.write(buffer.data(), static_cast<std::streamsize>(received));
|
||||
|
||||
if (!stream.good())
|
||||
{
|
||||
err() << "FTP Error: Writing to the file has failed" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the data socket
|
||||
m_dataSocket.disconnect();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Ftp::DataChannel::send(std::istream& stream)
|
||||
{
|
||||
// Send data
|
||||
std::array<char, 1024> buffer{};
|
||||
std::size_t count = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// read some data from the stream
|
||||
stream.read(buffer.data(), buffer.size());
|
||||
|
||||
if (!stream.good() && !stream.eof())
|
||||
{
|
||||
err() << "FTP Error: Reading from the file has failed" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
count = static_cast<std::size_t>(stream.gcount());
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
// we could read more data from the stream: send them
|
||||
if (m_dataSocket.send(buffer.data(), count) != Socket::Status::Done)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no more data: exit the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the data socket
|
||||
m_dataSocket.disconnect();
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Http.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
#include <SFML/System/Utils.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
Http::Request::Request(const std::string& uri, Method method, const std::string& body) : m_method(method)
|
||||
{
|
||||
setUri(uri);
|
||||
setBody(body);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Request::setField(const std::string& field, const std::string& value)
|
||||
{
|
||||
m_fields[toLower(field)] = value;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Request::setMethod(Http::Request::Method method)
|
||||
{
|
||||
m_method = method;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Request::setUri(const std::string& uri)
|
||||
{
|
||||
m_uri = uri;
|
||||
|
||||
// Make sure it starts with a '/'
|
||||
if (m_uri.empty() || (m_uri[0] != '/'))
|
||||
m_uri.insert(m_uri.begin(), '/');
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Request::setHttpVersion(unsigned int major, unsigned int minor)
|
||||
{
|
||||
m_majorVersion = major;
|
||||
m_minorVersion = minor;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Request::setBody(const std::string& body)
|
||||
{
|
||||
m_body = body;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::string Http::Request::prepare() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
|
||||
// Convert the method to its string representation
|
||||
std::string method;
|
||||
switch (m_method)
|
||||
{
|
||||
case Method::Get:
|
||||
method = "GET";
|
||||
break;
|
||||
case Method::Post:
|
||||
method = "POST";
|
||||
break;
|
||||
case Method::Head:
|
||||
method = "HEAD";
|
||||
break;
|
||||
case Method::Put:
|
||||
method = "PUT";
|
||||
break;
|
||||
case Method::Delete:
|
||||
method = "DELETE";
|
||||
break;
|
||||
}
|
||||
|
||||
// Write the first line containing the request type
|
||||
out << method << " " << m_uri << " ";
|
||||
out << "HTTP/" << m_majorVersion << "." << m_minorVersion << "\r\n";
|
||||
|
||||
// Write fields
|
||||
for (const auto& [fieldKey, fieldValue] : m_fields)
|
||||
{
|
||||
out << fieldKey << ": " << fieldValue << "\r\n";
|
||||
}
|
||||
|
||||
// Use an extra \r\n to separate the header from the body
|
||||
out << "\r\n";
|
||||
|
||||
// Add the body
|
||||
out << m_body;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Http::Request::hasField(const std::string& field) const
|
||||
{
|
||||
return m_fields.find(toLower(field)) != m_fields.end();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const std::string& Http::Response::getField(const std::string& field) const
|
||||
{
|
||||
if (const auto it = m_fields.find(toLower(field)); it != m_fields.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static const std::string empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Http::Response::Status Http::Response::getStatus() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned int Http::Response::getMajorHttpVersion() const
|
||||
{
|
||||
return m_majorVersion;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned int Http::Response::getMinorHttpVersion() const
|
||||
{
|
||||
return m_minorVersion;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const std::string& Http::Response::getBody() const
|
||||
{
|
||||
return m_body;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Response::parse(const std::string& data)
|
||||
{
|
||||
std::istringstream in(data);
|
||||
|
||||
// Extract the HTTP version from the first line
|
||||
std::string version;
|
||||
if (in >> version)
|
||||
{
|
||||
if ((version.size() >= 8) && (version[6] == '.') && (toLower(version.substr(0, 5)) == "http/") &&
|
||||
std::isdigit(version[5]) && std::isdigit(version[7]))
|
||||
{
|
||||
m_majorVersion = static_cast<unsigned int>(version[5] - '0');
|
||||
m_minorVersion = static_cast<unsigned int>(version[7] - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid HTTP version
|
||||
m_status = Status::InvalidResponse;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the status code from the first line
|
||||
int status = 0;
|
||||
if (in >> status)
|
||||
{
|
||||
m_status = static_cast<Status>(status);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid status code
|
||||
m_status = Status::InvalidResponse;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the end of the first line
|
||||
in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
|
||||
// Parse the other lines, which contain fields, one by one
|
||||
parseFields(in);
|
||||
|
||||
m_body.clear();
|
||||
|
||||
// Determine whether the transfer is chunked
|
||||
if (toLower(getField("transfer-encoding")) != "chunked")
|
||||
{
|
||||
// Not chunked - just read everything at once
|
||||
std::copy(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>(), std::back_inserter(m_body));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chunked - have to read chunk by chunk
|
||||
std::size_t length = 0;
|
||||
|
||||
// Read all chunks, identified by a chunk-size not being 0
|
||||
while (in >> std::hex >> length)
|
||||
{
|
||||
// Drop the rest of the line (chunk-extension)
|
||||
in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
|
||||
// Copy the actual content data
|
||||
std::istreambuf_iterator<char> it(in);
|
||||
const std::istreambuf_iterator<char> itEnd;
|
||||
for (std::size_t i = 0; ((i < length) && (it != itEnd)); ++i)
|
||||
{
|
||||
m_body.push_back(*it);
|
||||
++it; // Iterate in separate expression to work around false positive -Wnull-dereference warning in GCC 12.1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the rest of the line (chunk-extension)
|
||||
in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
|
||||
// Read all trailers (if present)
|
||||
parseFields(in);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::Response::parseFields(std::istream& in)
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(in, line) && (line.size() > 2))
|
||||
{
|
||||
const std::string::size_type pos = line.find(": ");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
// Extract the field name and its value
|
||||
const 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
|
||||
m_fields[toLower(field)] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Http::Http(const std::string& host, unsigned short port)
|
||||
{
|
||||
setHost(host, port);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Http::setHost(const std::string& host, unsigned short port)
|
||||
{
|
||||
// Check the protocol
|
||||
if (toLower(host.substr(0, 7)) == "http://")
|
||||
{
|
||||
// HTTP protocol
|
||||
m_hostName = host.substr(7);
|
||||
m_port = (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 sf::Http" << std::endl;
|
||||
m_hostName.clear();
|
||||
m_port = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Undefined protocol - use HTTP
|
||||
m_hostName = host;
|
||||
m_port = (port != 0 ? port : 80);
|
||||
}
|
||||
|
||||
// Remove any trailing '/' from the host name
|
||||
if (!m_hostName.empty() && (*m_hostName.rbegin() == '/'))
|
||||
m_hostName.erase(m_hostName.size() - 1);
|
||||
|
||||
m_host = IpAddress::resolve(m_hostName);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Http::Response Http::sendRequest(const Http::Request& request, Time timeout)
|
||||
{
|
||||
// First make sure that the request is valid -- add missing mandatory fields
|
||||
Request toSend(request);
|
||||
if (!toSend.hasField("From"))
|
||||
{
|
||||
toSend.setField("From", "user@sfml-dev.org");
|
||||
}
|
||||
if (!toSend.hasField("User-Agent"))
|
||||
{
|
||||
toSend.setField("User-Agent", "libsfml-network/3.x");
|
||||
}
|
||||
if (!toSend.hasField("Host"))
|
||||
{
|
||||
toSend.setField("Host", m_hostName);
|
||||
}
|
||||
if (!toSend.hasField("Content-Length"))
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << toSend.m_body.size();
|
||||
toSend.setField("Content-Length", out.str());
|
||||
}
|
||||
if ((toSend.m_method == Request::Method::Post) && !toSend.hasField("Content-Type"))
|
||||
{
|
||||
toSend.setField("Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
if ((toSend.m_majorVersion * 10 + toSend.m_minorVersion >= 11) && !toSend.hasField("Connection"))
|
||||
{
|
||||
toSend.setField("Connection", "close");
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
Response received;
|
||||
|
||||
// Connect the socket to the host
|
||||
if (m_connection.connect(m_host.value(), m_port, timeout) == Socket::Status::Done)
|
||||
{
|
||||
// Convert the request to string and send it through the connected socket
|
||||
const std::string requestStr = toSend.prepare();
|
||||
|
||||
if (!requestStr.empty())
|
||||
{
|
||||
// Send it through the socket
|
||||
if (m_connection.send(requestStr.c_str(), requestStr.size()) == Socket::Status::Done)
|
||||
{
|
||||
// Wait for the server's response
|
||||
std::string receivedStr;
|
||||
std::size_t size = 0;
|
||||
std::array<char, 1024> buffer{};
|
||||
while (m_connection.receive(buffer.data(), buffer.size(), size) == Socket::Status::Done)
|
||||
{
|
||||
receivedStr.append(buffer.data(), buffer.data() + size);
|
||||
}
|
||||
|
||||
// Build the Response object from the received data
|
||||
received.parse(receivedStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
m_connection.disconnect();
|
||||
}
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Http.hpp>
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
const IpAddress IpAddress::Any(0, 0, 0, 0);
|
||||
const IpAddress IpAddress::LocalHost(127, 0, 0, 1);
|
||||
const IpAddress IpAddress::Broadcast(255, 255, 255, 255);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<IpAddress> IpAddress::resolve(std::string_view address)
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
if (address.empty())
|
||||
{
|
||||
// Not generating en error message here as resolution failure is a valid outcome.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (address == "255.255.255.255"sv)
|
||||
{
|
||||
// The broadcast address needs to be handled explicitly,
|
||||
// because it is also the value returned by inet_addr on error
|
||||
return Broadcast;
|
||||
}
|
||||
|
||||
if (address == "0.0.0.0"sv)
|
||||
return Any;
|
||||
|
||||
// Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx")
|
||||
if (const std::uint32_t ip = inet_addr(address.data()); ip != INADDR_NONE)
|
||||
return IpAddress(ntohl(ip));
|
||||
|
||||
// Not a valid address, try to convert it as a host name
|
||||
addrinfo hints{}; // Zero-initialize
|
||||
hints.ai_family = AF_INET;
|
||||
|
||||
addrinfo* result = nullptr;
|
||||
if (getaddrinfo(address.data(), nullptr, &hints, &result) == 0 && result != nullptr)
|
||||
{
|
||||
sockaddr_in sin{};
|
||||
std::memcpy(&sin, result->ai_addr, sizeof(*result->ai_addr));
|
||||
|
||||
const std::uint32_t ip = sin.sin_addr.s_addr;
|
||||
freeaddrinfo(result);
|
||||
|
||||
return IpAddress(ntohl(ip));
|
||||
}
|
||||
|
||||
// Not generating en error message here as resolution failure is a valid outcome.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
IpAddress::IpAddress(std::uint8_t byte0, std::uint8_t byte1, std::uint8_t byte2, std::uint8_t byte3) :
|
||||
m_address(static_cast<std::uint32_t>((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
IpAddress::IpAddress(std::uint32_t address) : m_address(address)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::string IpAddress::toString() const
|
||||
{
|
||||
in_addr address{};
|
||||
address.s_addr = htonl(m_address);
|
||||
|
||||
return inet_ntoa(address);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::uint32_t IpAddress::toInteger() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<IpAddress> IpAddress::getLocalAddress()
|
||||
{
|
||||
// The method here is to connect a UDP socket to a public ip,
|
||||
// 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.
|
||||
|
||||
// Create the socket
|
||||
const SocketHandle sock = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (sock == priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
err() << "Failed to retrieve local address (invalid socket)" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Connect the socket to a public ip (here 1.1.1.1) on any
|
||||
// port. This will give the local address of the network interface
|
||||
// used for default routing which is usually what we want.
|
||||
sockaddr_in address = priv::SocketImpl::createAddress(0x01010101, 9);
|
||||
if (connect(sock, reinterpret_cast<sockaddr*>(&address), sizeof(address)) == -1)
|
||||
{
|
||||
priv::SocketImpl::close(sock);
|
||||
|
||||
err() << "Failed to retrieve local address (socket connection failure)" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Get the local address of the socket connection
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getsockname(sock, reinterpret_cast<sockaddr*>(&address), &size) == -1)
|
||||
{
|
||||
priv::SocketImpl::close(sock);
|
||||
|
||||
err() << "Failed to retrieve local address (socket local address retrieval failure)" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Close the socket
|
||||
priv::SocketImpl::close(sock);
|
||||
|
||||
// Finally build the IP address
|
||||
return IpAddress(ntohl(address.sin_addr.s_addr));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<IpAddress> IpAddress::getPublicAddress(Time 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).
|
||||
|
||||
Http server("www.sfml-dev.org");
|
||||
const Http::Request request("/ip-provider.php", Http::Request::Method::Get);
|
||||
const Http::Response page = server.sendRequest(request, timeout);
|
||||
|
||||
const Http::Response::Status status = page.getStatus();
|
||||
|
||||
if (status == Http::Response::Status::Ok)
|
||||
return IpAddress::resolve(page.getBody());
|
||||
|
||||
err() << "Failed to retrieve public address from external IP resolution server (HTTP response status "
|
||||
<< static_cast<int>(status) << ")" << std::endl;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator==(IpAddress left, IpAddress right)
|
||||
{
|
||||
return !(left < right) && !(right < left);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator!=(IpAddress left, IpAddress right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator<(IpAddress left, IpAddress right)
|
||||
{
|
||||
return left.m_address < right.m_address;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator>(IpAddress left, IpAddress right)
|
||||
{
|
||||
return right < left;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator<=(IpAddress left, IpAddress right)
|
||||
{
|
||||
return !(right < left);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool operator>=(IpAddress left, IpAddress right)
|
||||
{
|
||||
return !(left < right);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::istream& operator>>(std::istream& stream, std::optional<IpAddress>& address)
|
||||
{
|
||||
std::string str;
|
||||
stream >> str;
|
||||
address = IpAddress::resolve(str);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::ostream& operator<<(std::ostream& stream, IpAddress address)
|
||||
{
|
||||
return stream << address.toString();
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,596 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
|
||||
#include <SFML/System/String.hpp>
|
||||
#include <SFML/System/Utils.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
void Packet::append(const void* data, std::size_t sizeInBytes)
|
||||
{
|
||||
if (data && (sizeInBytes > 0))
|
||||
{
|
||||
const auto* begin = reinterpret_cast<const std::byte*>(data);
|
||||
const auto* end = begin + sizeInBytes;
|
||||
m_data.insert(m_data.end(), begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::size_t Packet::getReadPosition() const
|
||||
{
|
||||
return m_readPos;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Packet::clear()
|
||||
{
|
||||
m_data.clear();
|
||||
m_readPos = 0;
|
||||
m_isValid = true;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const void* Packet::getData() const
|
||||
{
|
||||
return !m_data.empty() ? m_data.data() : nullptr;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::size_t Packet::getDataSize() const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Packet::endOfPacket() const
|
||||
{
|
||||
return m_readPos >= m_data.size();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet::operator bool() const
|
||||
{
|
||||
return m_isValid;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(bool& data)
|
||||
{
|
||||
std::uint8_t value = 0;
|
||||
if (*this >> value)
|
||||
data = (value != 0);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::int8_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::uint8_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::int16_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
data = static_cast<std::int16_t>(ntohs(static_cast<std::uint16_t>(data)));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::uint16_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
data = ntohs(data);
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::int32_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
data = static_cast<std::int32_t>(ntohl(static_cast<std::uint32_t>(data)));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::uint32_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
data = ntohl(data);
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::int64_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
// Since ntohll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
std::array<std::byte, sizeof(data)> bytes{};
|
||||
std::memcpy(bytes.data(), &m_data[m_readPos], bytes.size());
|
||||
|
||||
data = toInteger<std::int64_t>(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::uint64_t& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
// Since ntohll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
std::array<std::byte, sizeof(data)> bytes{};
|
||||
std::memcpy(bytes.data(), &m_data[m_readPos], sizeof(data));
|
||||
|
||||
data = toInteger<std::uint64_t>(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(float& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(double& data)
|
||||
{
|
||||
if (checkSize(sizeof(data)))
|
||||
{
|
||||
std::memcpy(&data, &m_data[m_readPos], sizeof(data));
|
||||
m_readPos += sizeof(data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(char* data)
|
||||
{
|
||||
assert(data && "Packet::operator>> Data must not be null");
|
||||
|
||||
// First extract string length
|
||||
std::uint32_t length = 0;
|
||||
*this >> length;
|
||||
|
||||
if ((length > 0) && checkSize(length))
|
||||
{
|
||||
// Then extract characters
|
||||
std::memcpy(data, &m_data[m_readPos], length);
|
||||
data[length] = '\0';
|
||||
|
||||
// Update reading position
|
||||
m_readPos += length;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::string& data)
|
||||
{
|
||||
// First extract string length
|
||||
std::uint32_t length = 0;
|
||||
*this >> length;
|
||||
|
||||
data.clear();
|
||||
if ((length > 0) && checkSize(length))
|
||||
{
|
||||
// Then extract characters
|
||||
data.assign(reinterpret_cast<char*>(&m_data[m_readPos]), length);
|
||||
|
||||
// Update reading position
|
||||
m_readPos += length;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(wchar_t* data)
|
||||
{
|
||||
assert(data && "Packet::operator>> Data must not be null");
|
||||
|
||||
// First extract string length
|
||||
std::uint32_t length = 0;
|
||||
*this >> length;
|
||||
|
||||
if ((length > 0) && checkSize(length * sizeof(std::uint32_t)))
|
||||
{
|
||||
// Then extract characters
|
||||
for (std::uint32_t i = 0; i < length; ++i)
|
||||
{
|
||||
std::uint32_t character = 0;
|
||||
*this >> character;
|
||||
data[i] = static_cast<wchar_t>(character);
|
||||
}
|
||||
data[length] = L'\0';
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(std::wstring& data)
|
||||
{
|
||||
// First extract string length
|
||||
std::uint32_t length = 0;
|
||||
*this >> length;
|
||||
|
||||
data.clear();
|
||||
if ((length > 0) && checkSize(length * sizeof(std::uint32_t)))
|
||||
{
|
||||
// Then extract characters
|
||||
for (std::uint32_t i = 0; i < length; ++i)
|
||||
{
|
||||
std::uint32_t character = 0;
|
||||
*this >> character;
|
||||
data += static_cast<wchar_t>(character);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator>>(String& data)
|
||||
{
|
||||
// First extract the string length
|
||||
std::uint32_t length = 0;
|
||||
*this >> length;
|
||||
|
||||
data.clear();
|
||||
if ((length > 0) && checkSize(length * sizeof(std::uint32_t)))
|
||||
{
|
||||
// Then extract characters
|
||||
for (std::uint32_t i = 0; i < length; ++i)
|
||||
{
|
||||
std::uint32_t character = 0;
|
||||
*this >> character;
|
||||
data += static_cast<char32_t>(character);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(bool data)
|
||||
{
|
||||
*this << static_cast<std::uint8_t>(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::int8_t data)
|
||||
{
|
||||
append(&data, sizeof(data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::uint8_t data)
|
||||
{
|
||||
append(&data, sizeof(data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::int16_t data)
|
||||
{
|
||||
const auto toWrite = static_cast<std::int16_t>(htons(static_cast<std::uint16_t>(data)));
|
||||
append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::uint16_t data)
|
||||
{
|
||||
const std::uint16_t toWrite = htons(data);
|
||||
append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::int32_t data)
|
||||
{
|
||||
const auto toWrite = static_cast<std::int32_t>(htonl(static_cast<std::uint32_t>(data)));
|
||||
append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::uint32_t data)
|
||||
{
|
||||
const std::uint32_t toWrite = htonl(data);
|
||||
append(&toWrite, sizeof(toWrite));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::int64_t data)
|
||||
{
|
||||
// Since htonll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
|
||||
const std::array toWrite = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 48) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
||||
static_cast<std::uint8_t>((data) & 0xFF)};
|
||||
|
||||
append(toWrite.data(), toWrite.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(std::uint64_t data)
|
||||
{
|
||||
// Since htonll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
|
||||
const std::array toWrite = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 48) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
||||
static_cast<std::uint8_t>((data) & 0xFF)};
|
||||
|
||||
append(toWrite.data(), toWrite.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(float data)
|
||||
{
|
||||
append(&data, sizeof(data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(double data)
|
||||
{
|
||||
append(&data, sizeof(data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(const char* data)
|
||||
{
|
||||
assert(data && "Packet::operator<< Data must not be null");
|
||||
|
||||
// First insert string length
|
||||
const auto length = static_cast<std::uint32_t>(std::strlen(data));
|
||||
*this << length;
|
||||
|
||||
// Then insert characters
|
||||
append(data, length * sizeof(char));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(const std::string& data)
|
||||
{
|
||||
// First insert string length
|
||||
const auto length = static_cast<std::uint32_t>(data.size());
|
||||
*this << length;
|
||||
|
||||
// Then insert characters
|
||||
if (length > 0)
|
||||
append(data.c_str(), length * sizeof(std::string::value_type));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(const wchar_t* data)
|
||||
{
|
||||
assert(data && "Packet::operator<< Data must not be null");
|
||||
|
||||
// First insert string length
|
||||
const auto length = static_cast<std::uint32_t>(std::wcslen(data));
|
||||
*this << length;
|
||||
|
||||
// Then insert characters
|
||||
for (const wchar_t* c = data; *c != L'\0'; ++c)
|
||||
*this << static_cast<std::uint32_t>(*c);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(const std::wstring& data)
|
||||
{
|
||||
// First insert string length
|
||||
const auto length = static_cast<std::uint32_t>(data.size());
|
||||
*this << length;
|
||||
|
||||
// Then insert characters
|
||||
if (length > 0)
|
||||
{
|
||||
for (const wchar_t c : data)
|
||||
*this << static_cast<std::uint32_t>(c);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Packet& Packet::operator<<(const String& data)
|
||||
{
|
||||
// First insert the string length
|
||||
const auto length = static_cast<std::uint32_t>(data.getSize());
|
||||
*this << length;
|
||||
|
||||
// Then insert characters
|
||||
if (length > 0)
|
||||
{
|
||||
for (const unsigned int datum : data)
|
||||
*this << datum;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Packet::checkSize(std::size_t size)
|
||||
{
|
||||
// Determine if size is big enough to trigger an overflow
|
||||
const bool overflowDetected = m_readPos + size < m_readPos;
|
||||
m_isValid = m_isValid && (m_readPos + size <= m_data.size()) && !overflowDetected;
|
||||
|
||||
return m_isValid;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const void* Packet::onSend(std::size_t& size)
|
||||
{
|
||||
size = getDataSize();
|
||||
return getData();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Packet::onReceive(const void* data, std::size_t size)
|
||||
{
|
||||
append(data, size);
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Socket(Type type) : m_type(type), m_socket(priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::~Socket()
|
||||
{
|
||||
// Close the socket before it gets destructed
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Socket(Socket&& socket) noexcept :
|
||||
m_type(socket.m_type),
|
||||
m_socket(std::exchange(socket.m_socket, priv::SocketImpl::invalidSocket())),
|
||||
m_isBlocking(socket.m_isBlocking)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket& Socket::operator=(Socket&& socket) noexcept
|
||||
{
|
||||
if (&socket == this)
|
||||
return *this;
|
||||
|
||||
close();
|
||||
|
||||
m_type = socket.m_type;
|
||||
m_socket = std::exchange(socket.m_socket, priv::SocketImpl::invalidSocket());
|
||||
m_isBlocking = socket.m_isBlocking;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Socket::setBlocking(bool blocking)
|
||||
{
|
||||
// Apply if the socket is already created
|
||||
if (m_socket != priv::SocketImpl::invalidSocket())
|
||||
priv::SocketImpl::setBlocking(m_socket, blocking);
|
||||
|
||||
m_isBlocking = blocking;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Socket::isBlocking() const
|
||||
{
|
||||
return m_isBlocking;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketHandle Socket::getNativeHandle() const
|
||||
{
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Socket::create()
|
||||
{
|
||||
// Don't create the socket if it already exists
|
||||
if (m_socket == priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
const SocketHandle handle = socket(PF_INET, m_type == Type::Tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
|
||||
|
||||
if (handle == priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
err() << "Failed to create socket" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
create(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Socket::create(SocketHandle handle)
|
||||
{
|
||||
// Don't create the socket if it already exists
|
||||
if (m_socket == priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Assign the new handle
|
||||
m_socket = handle;
|
||||
|
||||
// Set the current blocking state
|
||||
setBlocking(m_isBlocking);
|
||||
|
||||
if (m_type == Type::Tcp)
|
||||
{
|
||||
// Disable the Nagle algorithm (i.e. removes buffering of TCP packets)
|
||||
int yes = 1;
|
||||
if (setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&yes), sizeof(yes)) == -1)
|
||||
{
|
||||
err() << "Failed to set socket option \"TCP_NODELAY\" ; "
|
||||
<< "all your TCP packets will be buffered" << std::endl;
|
||||
}
|
||||
|
||||
// On macOS, disable the SIGPIPE signal on disconnection
|
||||
#ifdef SFML_SYSTEM_MACOS
|
||||
if (setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&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(m_socket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char*>(&yes), sizeof(yes)) == -1)
|
||||
{
|
||||
err() << "Failed to enable broadcast on UDP socket" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Socket::close()
|
||||
{
|
||||
// Close the socket
|
||||
if (m_socket != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
priv::SocketImpl::close(m_socket);
|
||||
m_socket = priv::SocketImpl::invalidSocket();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
#include <SFML/Network/SocketHandle.hpp>
|
||||
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
|
||||
#include <SFML/System/Win32/WindowsHeader.hpp>
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace sf::priv
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Helper class implementing all the non-portable
|
||||
/// socket stuff
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
class SocketImpl
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Types
|
||||
////////////////////////////////////////////////////////////
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
using AddrLength = int;
|
||||
using Size = int;
|
||||
#else
|
||||
using AddrLength = socklen_t;
|
||||
using Size = std::size_t;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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(std::uint32_t 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 Socket::Status getErrorStatus();
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/Socket.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
#include <SFML/Network/SocketSelector.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro
|
||||
#endif
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
struct SocketSelector::SocketSelectorImpl
|
||||
{
|
||||
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
|
||||
int socketCount{}; //!< Number of socket handles
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector::SocketSelector() : m_impl(std::make_unique<SocketSelectorImpl>())
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector::~SocketSelector() = default;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector::SocketSelector(const SocketSelector& copy) : m_impl(std::make_unique<SocketSelectorImpl>(*copy.m_impl))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector& SocketSelector::operator=(const SocketSelector& right)
|
||||
{
|
||||
SocketSelector temp(right);
|
||||
std::swap(m_impl, temp.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketSelector::SocketSelector(SocketSelector&&) noexcept = default;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////]
|
||||
SocketSelector& SocketSelector::operator=(SocketSelector&&) noexcept = default;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketSelector::add(Socket& socket)
|
||||
{
|
||||
const SocketHandle handle = socket.getNativeHandle();
|
||||
if (handle != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
|
||||
if (m_impl->socketCount >= FD_SETSIZE)
|
||||
{
|
||||
err() << "The socket can't be added to the selector because the "
|
||||
<< "selector is full. This is a limitation of your operating "
|
||||
<< "system's FD_SETSIZE setting.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (FD_ISSET(handle, &m_impl->allSockets))
|
||||
return;
|
||||
|
||||
++m_impl->socketCount;
|
||||
|
||||
#else
|
||||
|
||||
if (handle >= FD_SETSIZE)
|
||||
{
|
||||
err() << "The socket can't be added to the selector because its "
|
||||
<< "ID is too high. This is a limitation of your operating "
|
||||
<< "system's FD_SETSIZE setting.";
|
||||
return;
|
||||
}
|
||||
|
||||
// SocketHandle is an int in POSIX
|
||||
m_impl->maxSocket = std::max(m_impl->maxSocket, handle);
|
||||
|
||||
#endif
|
||||
|
||||
FD_SET(handle, &m_impl->allSockets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketSelector::remove(Socket& socket)
|
||||
{
|
||||
const SocketHandle handle = socket.getNativeHandle();
|
||||
if (handle != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
|
||||
#if defined(SFML_SYSTEM_WINDOWS)
|
||||
|
||||
if (!FD_ISSET(handle, &m_impl->allSockets))
|
||||
return;
|
||||
|
||||
--m_impl->socketCount;
|
||||
|
||||
#else
|
||||
|
||||
if (handle >= FD_SETSIZE)
|
||||
return;
|
||||
|
||||
#endif
|
||||
|
||||
FD_CLR(handle, &m_impl->allSockets);
|
||||
FD_CLR(handle, &m_impl->socketsReady);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketSelector::clear()
|
||||
{
|
||||
FD_ZERO(&m_impl->allSockets);
|
||||
FD_ZERO(&m_impl->socketsReady);
|
||||
|
||||
m_impl->maxSocket = 0;
|
||||
m_impl->socketCount = 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool SocketSelector::wait(Time timeout)
|
||||
{
|
||||
// Setup the timeout
|
||||
timeval time{};
|
||||
time.tv_sec = static_cast<long>(timeout.asMicroseconds() / 1000000);
|
||||
time.tv_usec = static_cast<int>(timeout.asMicroseconds() % 1000000);
|
||||
|
||||
// Initialize the set that will contain the sockets that are ready
|
||||
m_impl->socketsReady = m_impl->allSockets;
|
||||
|
||||
// Wait until one of the sockets is ready for reading, or timeout is reached
|
||||
// The first parameter is ignored on Windows
|
||||
const int count = select(m_impl->maxSocket + 1, &m_impl->socketsReady, nullptr, nullptr, timeout != Time::Zero ? &time : nullptr);
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
bool SocketSelector::isReady(Socket& socket) const
|
||||
{
|
||||
const SocketHandle handle = socket.getNativeHandle();
|
||||
if (handle != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
|
||||
#if !defined(SFML_SYSTEM_WINDOWS)
|
||||
|
||||
if (handle >= FD_SETSIZE)
|
||||
return false;
|
||||
|
||||
#endif
|
||||
|
||||
return FD_ISSET(handle, &m_impl->socketsReady) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
#include <SFML/Network/TcpListener.hpp>
|
||||
#include <SFML/Network/TcpSocket.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <ostream>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpListener::TcpListener() : Socket(Type::Tcp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned short TcpListener::getLocalPort() const
|
||||
{
|
||||
if (getNativeHandle() != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Retrieve information about the local end of the socket
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getsockname(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &size) != -1)
|
||||
{
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to retrieve the port
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpListener::listen(unsigned short port, IpAddress address)
|
||||
{
|
||||
// Close the socket if it is already bound
|
||||
close();
|
||||
|
||||
// Create the internal socket if it doesn't exist
|
||||
create();
|
||||
|
||||
// Check if the address is valid
|
||||
if (address == IpAddress::Broadcast)
|
||||
return Status::Error;
|
||||
|
||||
// Bind the socket to the specified port
|
||||
sockaddr_in addr = priv::SocketImpl::createAddress(address.toInteger(), port);
|
||||
if (bind(getNativeHandle(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1)
|
||||
{
|
||||
// Not likely to happen, but...
|
||||
err() << "Failed to bind listener socket to port " << port << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
// Listen to the bound port
|
||||
if (::listen(getNativeHandle(), SOMAXCONN) == -1)
|
||||
{
|
||||
// Oops, socket is deaf
|
||||
err() << "Failed to listen to port " << port << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void TcpListener::close()
|
||||
{
|
||||
// Simply close the socket
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpListener::accept(TcpSocket& socket)
|
||||
{
|
||||
// Make sure that we're listening
|
||||
if (getNativeHandle() == priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
err() << "Failed to accept a new connection, the socket is not listening" << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
// Accept a new connection
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength length = sizeof(address);
|
||||
const SocketHandle remote = ::accept(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &length);
|
||||
|
||||
// Check for errors
|
||||
if (remote == priv::SocketImpl::invalidSocket())
|
||||
return priv::SocketImpl::getErrorStatus();
|
||||
|
||||
// Initialize the new connected socket
|
||||
socket.close();
|
||||
socket.create(remote);
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,429 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
#include <SFML/Network/TcpSocket.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro
|
||||
#endif
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// Low-level send/receive flags (OS-dependent)
|
||||
#ifdef SFML_SYSTEM_LINUX
|
||||
constexpr int flags = MSG_NOSIGNAL;
|
||||
#else
|
||||
constexpr int flags = 0;
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
TcpSocket::TcpSocket() : Socket(Type::Tcp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned short TcpSocket::getLocalPort() const
|
||||
{
|
||||
if (getNativeHandle() != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Retrieve information about the local end of the socket
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getsockname(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &size) != -1)
|
||||
{
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to retrieve the port
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<IpAddress> TcpSocket::getRemoteAddress() const
|
||||
{
|
||||
if (getNativeHandle() != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Retrieve information about the remote end of the socket
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getpeername(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &size) != -1)
|
||||
{
|
||||
return IpAddress(ntohl(address.sin_addr.s_addr));
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to retrieve the address
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned short TcpSocket::getRemotePort() const
|
||||
{
|
||||
if (getNativeHandle() != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Retrieve information about the remote end of the socket
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getpeername(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &size) != -1)
|
||||
{
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to retrieve the port
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::connect(IpAddress remoteAddress, unsigned short remotePort, Time timeout)
|
||||
{
|
||||
// Disconnect the socket if it is already connected
|
||||
disconnect();
|
||||
|
||||
// Create the internal socket if it doesn't exist
|
||||
create();
|
||||
|
||||
// Create the remote address
|
||||
sockaddr_in address = priv::SocketImpl::createAddress(remoteAddress.toInteger(), remotePort);
|
||||
|
||||
if (timeout <= Time::Zero)
|
||||
{
|
||||
// ----- We're not using a timeout: just try to connect -----
|
||||
|
||||
// Connect the socket
|
||||
if (::connect(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) == -1)
|
||||
return priv::SocketImpl::getErrorStatus();
|
||||
|
||||
// Connection succeeded
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
// ----- We're using a timeout: we'll need a few tricks to make it work -----
|
||||
|
||||
// Save the previous blocking state
|
||||
const 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(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) >= 0)
|
||||
{
|
||||
// We got instantly connected! (it may no happen a lot...)
|
||||
setBlocking(blocking);
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
// Get the error status
|
||||
Status status = priv::SocketImpl::getErrorStatus();
|
||||
|
||||
// If we were in non-blocking mode, return immediately
|
||||
if (!blocking)
|
||||
return status;
|
||||
|
||||
// Otherwise, wait until something happens to our socket (success, timeout or error)
|
||||
if (status == Socket::Status::NotReady)
|
||||
{
|
||||
// Setup the selector
|
||||
fd_set selector;
|
||||
FD_ZERO(&selector);
|
||||
FD_SET(getNativeHandle(), &selector);
|
||||
|
||||
// Setup the timeout
|
||||
timeval time{};
|
||||
time.tv_sec = static_cast<long>(timeout.asMicroseconds() / 1000000);
|
||||
time.tv_usec = static_cast<int>(timeout.asMicroseconds() % 1000000);
|
||||
|
||||
// Wait for something to write on our socket (which means that the connection request has returned)
|
||||
if (select(static_cast<int>(getNativeHandle() + 1), nullptr, &selector, nullptr, &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().has_value())
|
||||
{
|
||||
// Connection accepted
|
||||
status = Status::Done;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connection refused
|
||||
status = priv::SocketImpl::getErrorStatus();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to connect before timeout is over
|
||||
status = priv::SocketImpl::getErrorStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// Switch back to blocking mode
|
||||
setBlocking(true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void TcpSocket::disconnect()
|
||||
{
|
||||
// Close the socket
|
||||
close();
|
||||
|
||||
// Reset the pending packet data
|
||||
m_pendingPacket = PendingPacket();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::send(const void* data, std::size_t size)
|
||||
{
|
||||
if (!isBlocking())
|
||||
err() << "Warning: Partial sends might not be handled properly." << std::endl;
|
||||
|
||||
std::size_t sent = 0;
|
||||
|
||||
return send(data, size, sent);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::send(const void* data, std::size_t size, std::size_t& sent)
|
||||
{
|
||||
// Check the parameters
|
||||
if (!data || (size == 0))
|
||||
{
|
||||
err() << "Cannot send data over the network (no data to send)" << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
// Loop until every byte has been sent
|
||||
int result = 0;
|
||||
for (sent = 0; sent < size; sent += static_cast<std::size_t>(result))
|
||||
{
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuseless-cast"
|
||||
// Send a chunk of data
|
||||
result = static_cast<int>(::send(getNativeHandle(),
|
||||
static_cast<const char*>(data) + sent,
|
||||
static_cast<priv::SocketImpl::Size>(size - sent),
|
||||
flags));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Check for errors
|
||||
if (result < 0)
|
||||
{
|
||||
const Status status = priv::SocketImpl::getErrorStatus();
|
||||
|
||||
if ((status == Status::NotReady) && sent)
|
||||
return Status::Partial;
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::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 Status::Error;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuseless-cast"
|
||||
// Receive a chunk of bytes
|
||||
const int sizeReceived = static_cast<int>(
|
||||
recv(getNativeHandle(), static_cast<char*>(data), static_cast<priv::SocketImpl::Size>(size), flags));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Check the number of bytes received
|
||||
if (sizeReceived > 0)
|
||||
{
|
||||
received = static_cast<std::size_t>(sizeReceived);
|
||||
return Status::Done;
|
||||
}
|
||||
if (sizeReceived == 0)
|
||||
{
|
||||
return Socket::Status::Disconnected;
|
||||
}
|
||||
|
||||
return priv::SocketImpl::getErrorStatus();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::send(Packet& 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
|
||||
std::uint32_t packetSize = htonl(static_cast<std::uint32_t>(size));
|
||||
|
||||
// Allocate memory for the data block to send
|
||||
m_blockToSendBuffer.resize(sizeof(packetSize) + size);
|
||||
|
||||
// Copy the packet size and data into the block to send
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnull-dereference" // False positive.
|
||||
std::memcpy(m_blockToSendBuffer.data(), &packetSize, sizeof(packetSize));
|
||||
#pragma GCC diagnostic pop
|
||||
if (size > 0)
|
||||
std::memcpy(m_blockToSendBuffer.data() + sizeof(packetSize), data, size);
|
||||
|
||||
// These warnings are ignored here for portability, as even on Windows the
|
||||
// signature of `send` might change depending on whether Win32 or MinGW is
|
||||
// being used.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuseless-cast"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
// Send the data block
|
||||
std::size_t sent = 0;
|
||||
const Status status = send(m_blockToSendBuffer.data() + packet.m_sendPos,
|
||||
static_cast<priv::SocketImpl::Size>(m_blockToSendBuffer.size() - packet.m_sendPos),
|
||||
sent);
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// In the case of a partial send, record the location to resume from
|
||||
if (status == Status::Partial)
|
||||
{
|
||||
packet.m_sendPos += sent;
|
||||
}
|
||||
else if (status == Status::Done)
|
||||
{
|
||||
packet.m_sendPos = 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status TcpSocket::receive(Packet& packet)
|
||||
{
|
||||
// First clear the variables to fill
|
||||
packet.clear();
|
||||
|
||||
// We start by getting the size of the incoming packet
|
||||
std::uint32_t packetSize = 0;
|
||||
std::size_t received = 0;
|
||||
if (m_pendingPacket.sizeReceived < sizeof(m_pendingPacket.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 (m_pendingPacket.sizeReceived < sizeof(m_pendingPacket.size))
|
||||
{
|
||||
char* data = reinterpret_cast<char*>(&m_pendingPacket.size) + m_pendingPacket.sizeReceived;
|
||||
const Status status = receive(data, sizeof(m_pendingPacket.size) - m_pendingPacket.sizeReceived, received);
|
||||
m_pendingPacket.sizeReceived += received;
|
||||
|
||||
if (status != Status::Done)
|
||||
return status;
|
||||
}
|
||||
|
||||
// The packet size has been fully received
|
||||
packetSize = ntohl(m_pendingPacket.size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The packet size has already been received in a previous call
|
||||
packetSize = ntohl(m_pendingPacket.size);
|
||||
}
|
||||
|
||||
// Loop until we receive all the packet data
|
||||
std::array<char, 1024> buffer{};
|
||||
while (m_pendingPacket.data.size() < packetSize)
|
||||
{
|
||||
// Receive a chunk of data
|
||||
const std::size_t sizeToGet = std::min(packetSize - m_pendingPacket.data.size(), buffer.size());
|
||||
const Status status = receive(buffer.data(), sizeToGet, received);
|
||||
if (status != Status::Done)
|
||||
return status;
|
||||
|
||||
// Append it into the packet
|
||||
if (received > 0)
|
||||
{
|
||||
m_pendingPacket.data.resize(m_pendingPacket.data.size() + received);
|
||||
std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received;
|
||||
std::memcpy(begin, buffer.data(), received);
|
||||
}
|
||||
}
|
||||
|
||||
// We have received all the packet data: we can copy it to the user packet
|
||||
if (!m_pendingPacket.data.empty())
|
||||
packet.onReceive(m_pendingPacket.data.data(), m_pendingPacket.data.size());
|
||||
|
||||
// Clear the pending packet data
|
||||
m_pendingPacket = PendingPacket();
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/IpAddress.hpp>
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
#include <SFML/Network/UdpSocket.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
UdpSocket::UdpSocket() : Socket(Type::Udp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned short UdpSocket::getLocalPort() const
|
||||
{
|
||||
if (getNativeHandle() != priv::SocketImpl::invalidSocket())
|
||||
{
|
||||
// Retrieve information about the local end of the socket
|
||||
sockaddr_in address{};
|
||||
priv::SocketImpl::AddrLength size = sizeof(address);
|
||||
if (getsockname(getNativeHandle(), reinterpret_cast<sockaddr*>(&address), &size) != -1)
|
||||
{
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to retrieve the port
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status UdpSocket::bind(unsigned short port, IpAddress address)
|
||||
{
|
||||
// Close the socket if it is already bound
|
||||
close();
|
||||
|
||||
// Create the internal socket if it doesn't exist
|
||||
create();
|
||||
|
||||
// Check if the address is valid
|
||||
if (address == IpAddress::Broadcast)
|
||||
return Status::Error;
|
||||
|
||||
// Bind the socket
|
||||
sockaddr_in addr = priv::SocketImpl::createAddress(address.toInteger(), port);
|
||||
if (::bind(getNativeHandle(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1)
|
||||
{
|
||||
err() << "Failed to bind socket to port " << port << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void UdpSocket::unbind()
|
||||
{
|
||||
// Simply close the socket
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status UdpSocket::send(const void* data, std::size_t size, IpAddress 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 sf::UdpSocket::MaxDatagramSize)" << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
// Build the target address
|
||||
sockaddr_in address = priv::SocketImpl::createAddress(remoteAddress.toInteger(), remotePort);
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuseless-cast"
|
||||
// Send the data (unlike TCP, all the data is always sent in one call)
|
||||
const int sent = static_cast<int>(
|
||||
sendto(getNativeHandle(),
|
||||
static_cast<const char*>(data),
|
||||
static_cast<priv::SocketImpl::Size>(size),
|
||||
0,
|
||||
reinterpret_cast<sockaddr*>(&address),
|
||||
sizeof(address)));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Check for errors
|
||||
if (sent < 0)
|
||||
return priv::SocketImpl::getErrorStatus();
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status UdpSocket::receive(void* data,
|
||||
std::size_t size,
|
||||
std::size_t& received,
|
||||
std::optional<IpAddress>& remoteAddress,
|
||||
unsigned short& remotePort)
|
||||
{
|
||||
// First clear the variables to fill
|
||||
received = 0;
|
||||
remoteAddress = std::nullopt;
|
||||
remotePort = 0;
|
||||
|
||||
// Check the destination buffer
|
||||
if (!data)
|
||||
{
|
||||
err() << "Cannot receive data from the network (the destination buffer is invalid)" << std::endl;
|
||||
return Status::Error;
|
||||
}
|
||||
|
||||
// Data that will be filled with the other computer's address
|
||||
sockaddr_in address = priv::SocketImpl::createAddress(INADDR_ANY, 0);
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuseless-cast"
|
||||
// Receive a chunk of bytes
|
||||
priv::SocketImpl::AddrLength addressSize = sizeof(address);
|
||||
const int sizeReceived = static_cast<int>(
|
||||
recvfrom(getNativeHandle(),
|
||||
static_cast<char*>(data),
|
||||
static_cast<priv::SocketImpl::Size>(size),
|
||||
0,
|
||||
reinterpret_cast<sockaddr*>(&address),
|
||||
&addressSize));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Check for errors
|
||||
if (sizeReceived < 0)
|
||||
return priv::SocketImpl::getErrorStatus();
|
||||
|
||||
// Fill the sender information
|
||||
received = static_cast<std::size_t>(sizeReceived);
|
||||
remoteAddress = IpAddress(ntohl(address.sin_addr.s_addr));
|
||||
remotePort = ntohs(address.sin_port);
|
||||
|
||||
return Status::Done;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status UdpSocket::send(Packet& packet, IpAddress 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 SFML 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);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status UdpSocket::receive(Packet& packet, std::optional<IpAddress>& remoteAddress, unsigned short& remotePort)
|
||||
{
|
||||
// See the detailed comment in send(Packet) above.
|
||||
|
||||
// Receive the datagram
|
||||
std::size_t received = 0;
|
||||
const Status status = receive(m_buffer.data(), m_buffer.size(), received, remoteAddress, remotePort);
|
||||
|
||||
// If we received valid data, we can copy it to the user packet
|
||||
packet.clear();
|
||||
if ((status == Status::Done) && (received > 0))
|
||||
packet.onReceive(m_buffer.data(), received);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
} // namespace sf
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <ostream>
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
|
||||
namespace sf::priv
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
sockaddr_in SocketImpl::createAddress(std::uint32_t address, unsigned short port)
|
||||
{
|
||||
auto addr = sockaddr_in();
|
||||
addr.sin_addr.s_addr = htonl(address);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
#if defined(SFML_SYSTEM_MACOS)
|
||||
addr.sin_len = sizeof(addr);
|
||||
#endif
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketHandle SocketImpl::invalidSocket()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketImpl::close(SocketHandle sock)
|
||||
{
|
||||
::close(sock);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketImpl::setBlocking(SocketHandle sock, bool block)
|
||||
{
|
||||
const int status = fcntl(sock, F_GETFL);
|
||||
if (block)
|
||||
{
|
||||
if (fcntl(sock, F_SETFL, status & ~O_NONBLOCK) == -1)
|
||||
err() << "Failed to set file status flags: " << errno << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fcntl(sock, F_SETFL, status | O_NONBLOCK) == -1)
|
||||
err() << "Failed to set file status flags: " << errno << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status SocketImpl::getErrorStatus()
|
||||
{
|
||||
// The following 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 Socket::Status::NotReady;
|
||||
|
||||
// clang-format off
|
||||
switch (errno)
|
||||
{
|
||||
case EWOULDBLOCK: return Socket::Status::NotReady;
|
||||
case ECONNABORTED: return Socket::Status::Disconnected;
|
||||
case ECONNRESET: return Socket::Status::Disconnected;
|
||||
case ETIMEDOUT: return Socket::Status::Disconnected;
|
||||
case ENETRESET: return Socket::Status::Disconnected;
|
||||
case ENOTCONN: return Socket::Status::Disconnected;
|
||||
case EPIPE: return Socket::Status::Disconnected;
|
||||
default: return Socket::Status::Error;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
} // namespace sf::priv
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SFML - Simple and Fast Multimedia Library
|
||||
// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org)
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied warranty.
|
||||
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it freely,
|
||||
// subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented;
|
||||
// you must not claim that you wrote the original software.
|
||||
// If you use this software in a product, an acknowledgment
|
||||
// in the product documentation would be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such,
|
||||
// and must not be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Network/SocketImpl.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// 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();
|
||||
}
|
||||
} globalInitializer;
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace sf::priv
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
sockaddr_in SocketImpl::createAddress(std::uint32_t address, unsigned short port)
|
||||
{
|
||||
auto addr = sockaddr_in();
|
||||
addr.sin_addr.s_addr = htonl(address);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SocketHandle SocketImpl::invalidSocket()
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketImpl::close(SocketHandle sock)
|
||||
{
|
||||
closesocket(sock);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SocketImpl::setBlocking(SocketHandle sock, bool block)
|
||||
{
|
||||
u_long blocking = block ? 0 : 1;
|
||||
ioctlsocket(sock, static_cast<long>(FIONBIO), &blocking);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Socket::Status SocketImpl::getErrorStatus()
|
||||
{
|
||||
// clang-format off
|
||||
switch (WSAGetLastError())
|
||||
{
|
||||
case WSAEWOULDBLOCK: return Socket::Status::NotReady;
|
||||
case WSAEALREADY: return Socket::Status::NotReady;
|
||||
case WSAECONNABORTED: return Socket::Status::Disconnected;
|
||||
case WSAECONNRESET: return Socket::Status::Disconnected;
|
||||
case WSAETIMEDOUT: return Socket::Status::Disconnected;
|
||||
case WSAENETRESET: return Socket::Status::Disconnected;
|
||||
case WSAENOTCONN: return Socket::Status::Disconnected;
|
||||
case WSAEISCONN: return Socket::Status::Done; // when connecting a non-blocking socket
|
||||
default: return Socket::Status::Error;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
} // namespace sf::priv
|
||||
|
|
@ -1,70 +1,80 @@
|
|||
project"vorbis"
|
||||
cppdialect"c++17"
|
||||
kind"staticLib"
|
||||
targetdir (libout)
|
||||
staticruntime "off"
|
||||
objdir(intdir)
|
||||
local m = {}
|
||||
|
||||
links{"ogg"}
|
||||
local scriptdir = path.getabsolute(path.getdirectory(_SCRIPT))
|
||||
local ogg = require("vendor/ogg/build-ogg")
|
||||
function m.generateproject(liboutdir, intdir)
|
||||
project"vorbis"
|
||||
language"C" -- c++ will mangle names and sfml wont build
|
||||
kind"staticLib"
|
||||
targetdir (liboutdir)
|
||||
objdir(intdir)
|
||||
warnings"Off"
|
||||
|
||||
ogg.link()
|
||||
|
||||
includedirs
|
||||
{
|
||||
"include",
|
||||
"lib",
|
||||
"../ogg/include"
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "lib"),
|
||||
}
|
||||
|
||||
files
|
||||
{
|
||||
"lib/envelope.h",
|
||||
"lib/lpc.h",
|
||||
"lib/lsp.h",
|
||||
"lib/codebook.h",
|
||||
"lib/misc.h",
|
||||
"lib/psy.h",
|
||||
"lib/masking.h",
|
||||
"lib/os.h",
|
||||
"lib/mdct.h",
|
||||
"lib/smallft.h",
|
||||
"lib/highlevel.h",
|
||||
"lib/registry.h",
|
||||
"lib/scales.h",
|
||||
"lib/window.h",
|
||||
"lib/lookup.h",
|
||||
"lib/lookup_data.h",
|
||||
"lib/codec_internal.h",
|
||||
"lib/backends.h",
|
||||
"lib/bitrate.h",
|
||||
"lib/mdct.c",
|
||||
"lib/smallft.c",
|
||||
"lib/block.c",
|
||||
"lib/envelope.c",
|
||||
"lib/window.c",
|
||||
"lib/lsp.c",
|
||||
"lib/lpc.c",
|
||||
"lib/analysis.c",
|
||||
"lib/synthesis.c",
|
||||
"lib/psy.c",
|
||||
"lib/info.c",
|
||||
"lib/floor1.c",
|
||||
"lib/floor0.c",
|
||||
"lib/res0.c",
|
||||
"lib/mapping0.c",
|
||||
"lib/registry.c",
|
||||
"lib/codebook.c",
|
||||
"lib/sharedbook.c",
|
||||
"lib/lookup.c",
|
||||
"lib/bitrate.c",
|
||||
"lib/vorbisfile.c",
|
||||
"lib/vorbisenc.c",
|
||||
path.join(scriptdir, "lib/envelope.h"),
|
||||
path.join(scriptdir, "lib/lpc.h"),
|
||||
path.join(scriptdir, "lib/lsp.h"),
|
||||
path.join(scriptdir, "lib/codebook.h"),
|
||||
path.join(scriptdir, "lib/misc.h"),
|
||||
path.join(scriptdir, "lib/psy.h"),
|
||||
path.join(scriptdir, "lib/masking.h"),
|
||||
path.join(scriptdir, "lib/os.h"),
|
||||
path.join(scriptdir, "lib/mdct.h"),
|
||||
path.join(scriptdir, "lib/smallft.h"),
|
||||
path.join(scriptdir, "lib/highlevel.h"),
|
||||
path.join(scriptdir, "lib/registry.h"),
|
||||
path.join(scriptdir, "lib/scales.h"),
|
||||
path.join(scriptdir, "lib/window.h"),
|
||||
path.join(scriptdir, "lib/lookup.h"),
|
||||
path.join(scriptdir, "lib/lookup_data.h"),
|
||||
path.join(scriptdir, "lib/codec_internal.h"),
|
||||
path.join(scriptdir, "lib/backends.h"),
|
||||
path.join(scriptdir, "lib/bitrate.h"),
|
||||
path.join(scriptdir, "lib/mdct.c"),
|
||||
path.join(scriptdir, "lib/smallft.c"),
|
||||
path.join(scriptdir, "lib/block.c"),
|
||||
path.join(scriptdir, "lib/envelope.c"),
|
||||
path.join(scriptdir, "lib/window.c"),
|
||||
path.join(scriptdir, "lib/lsp.c"),
|
||||
path.join(scriptdir, "lib/lpc.c"),
|
||||
path.join(scriptdir, "lib/analysis.c"),
|
||||
path.join(scriptdir, "lib/synthesis.c"),
|
||||
path.join(scriptdir, "lib/psy.c"),
|
||||
path.join(scriptdir, "lib/info.c"),
|
||||
path.join(scriptdir, "lib/floor1.c"),
|
||||
path.join(scriptdir, "lib/floor0.c"),
|
||||
path.join(scriptdir, "lib/res0.c"),
|
||||
path.join(scriptdir, "lib/mapping0.c"),
|
||||
path.join(scriptdir, "lib/registry.c"),
|
||||
path.join(scriptdir, "lib/codebook.c"),
|
||||
path.join(scriptdir, "lib/sharedbook.c"),
|
||||
path.join(scriptdir, "lib/lookup.c"),
|
||||
path.join(scriptdir, "lib/bitrate.c"),
|
||||
path.join(scriptdir, "lib/vorbisfile.c"),
|
||||
path.join(scriptdir, "lib/vorbisenc.c"),
|
||||
}
|
||||
|
||||
filter "configurations:Debug"
|
||||
runtime "Debug"
|
||||
symbols "on"
|
||||
|
||||
filter "configurations:Release"
|
||||
runtime "Release"
|
||||
optimize "Speed"
|
||||
|
||||
filter""
|
||||
end
|
||||
|
||||
function m.link()
|
||||
links {"vorbis", "ogg"}
|
||||
externalincludedirs
|
||||
{
|
||||
path.join(scriptdir, "include"),
|
||||
path.join(scriptdir, "lib"),
|
||||
path.join(scriptdir, "../ogg/include")
|
||||
}
|
||||
end
|
||||
|
||||
return m
|
||||
Loading…
Reference in New Issue