From 014b96adb33fa902072d9f35661bc4f5e7c323e8 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 30 Jan 2015 21:24:02 +0100 Subject: [PATCH] Exported cServerHandle and cNetwork:Listen to Lua. Also added an example to the NetworkTest plugin. --- MCServer/Plugins/NetworkTest/Info.lua | 27 ++ MCServer/Plugins/NetworkTest/NetworkTest.lua | 148 +++++++- MCServer/Plugins/NetworkTest/splashes.txt | 357 +++++++++++++++++++ src/Bindings/CMakeLists.txt | 2 + src/Bindings/LuaNameLookup.h | 2 +- src/Bindings/LuaServerHandle.cpp | 213 +++++++++++ src/Bindings/LuaServerHandle.h | 89 +++++ src/Bindings/LuaState.cpp | 33 ++ src/Bindings/LuaState.h | 9 + src/Bindings/LuaTCPLink.cpp | 97 ++++- src/Bindings/LuaTCPLink.h | 21 ++ src/Bindings/ManualBindings_Network.cpp | 157 +++++++- src/Globals.h | 1 + src/OSSupport/Network.h | 3 + tests/Network/EchoServer.cpp | 1 + 15 files changed, 1145 insertions(+), 15 deletions(-) create mode 100644 MCServer/Plugins/NetworkTest/splashes.txt create mode 100644 src/Bindings/LuaServerHandle.cpp create mode 100644 src/Bindings/LuaServerHandle.h diff --git a/MCServer/Plugins/NetworkTest/Info.lua b/MCServer/Plugins/NetworkTest/Info.lua index 8c2604e31..f366fd1be 100644 --- a/MCServer/Plugins/NetworkTest/Info.lua +++ b/MCServer/Plugins/NetworkTest/Info.lua @@ -37,6 +37,32 @@ g_PluginInfo = }, -- ParameterCombinations }, -- client + close = + { + HelpString = "Close a listening socket", + Handler = HandleConsoleNetClose, + ParameterCombinations = + { + { + Params = "[Port]", + Help = "Closes a socket listening on the specified port [1024]", + }, + }, -- ParameterCombinations + }, -- close + + listen = + { + HelpString = "Creates a new listening socket on the specified port with the specified service attached to it", + Handler = HandleConsoleNetListen, + ParameterCombinations = + { + { + Params = "[Port] [Service]", + Help = "Starts listening on the specified port [1024] providing the specified service [echo]", + }, + }, -- ParameterCombinations + }, -- listen + lookup = { HelpString = "Looks up the IP addresses corresponding to the given hostname (google.com by default)", @@ -57,6 +83,7 @@ g_PluginInfo = }, }, }, -- lookup + }, -- Subcommands }, -- net }, diff --git a/MCServer/Plugins/NetworkTest/NetworkTest.lua b/MCServer/Plugins/NetworkTest/NetworkTest.lua index 30f34c879..9b69ee5b3 100644 --- a/MCServer/Plugins/NetworkTest/NetworkTest.lua +++ b/MCServer/Plugins/NetworkTest/NetworkTest.lua @@ -7,11 +7,109 @@ -function Initialize() +--- Map of all servers currently open +-- g_Servers[PortNum] = cServerHandle +local g_Servers = {} + +--- List of fortune messages for the fortune server +-- A random message is chosen for each incoming connection +-- The contents are loaded from the splashes.txt file on plugin startup +local g_Fortunes = +{ + "Empty splashes.txt", +} + +--- Map of all services that can be run as servers +-- g_Services[ServiceName] = function() -> callbacks +local g_Services = +{ + -- Echo service: each connection echoes back what has been sent to it + echo = function (a_Port) + return + { + -- A new connection has come, give it new link callbacks: + OnIncomingConnection = function (a_RemoteIP, a_RemotePort) + return + { + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("EchoServer(" .. a_Port .. ": Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Echo the received data back to the link: + a_Link:Send(a_Data) + end, + + OnRemoteClosed = function (a_Link) + end + } -- Link callbacks + end, -- OnIncomingConnection() + + -- Send a welcome message to newly accepted connections: + OnAccepted = function (a_Link) + a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the echo server @ MCServer-Lua\r\n") + end, -- OnAccepted() + + -- There was an error listening on the port: + OnError = function (a_ErrorCode, a_ErrorMsg) + LOGINFO("EchoServer(" .. a_Port .. ": Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, -- OnError() + } -- Listen callbacks + end, -- echo + + fortune = function (a_Port) + return + { + -- A new connection has come, give it new link callbacks: + OnIncomingConnection = function (a_RemoteIP, a_RemotePort) + return + { + OnError = function (a_Link, a_ErrorCode, a_ErrorMsg) + LOG("FortuneServer(" .. a_Port .. ": Connection to " .. a_Link:GetRemoteIP() .. ":" .. a_Link:GetRemotePort() .. " failed: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, + + OnReceivedData = function (a_Link, a_Data) + -- Ignore any received data + end, + + OnRemoteClosed = function (a_Link) + end + } -- Link callbacks + end, -- OnIncomingConnection() + + -- Send a welcome message to newly accepted connections: + OnAccepted = function (a_Link) + a_Link:Send("Hello, " .. a_Link:GetRemoteIP() .. ", welcome to the fortune server @ MCServer-Lua\r\n\r\nYour fortune:\r\n") + a_Link:Send(g_Fortunes[math.random(#g_Fortunes)] .. "\r\n") + end, -- OnAccepted() + + -- There was an error listening on the port: + OnError = function (a_ErrorCode, a_ErrorMsg) + LOGINFO("FortuneServer(" .. a_Port .. ": Cannot listen: " .. a_ErrorCode .. " (" .. a_ErrorMsg .. ")") + end, -- OnError() + } -- Listen callbacks + end, -- fortune + + -- TODO: Other services (fortune, daytime, ...) +} + + + + + +function Initialize(a_Plugin) + -- Load the splashes.txt file into g_Fortunes: + for line in io.lines(a_Plugin:GetLocalFolder() .. "/splashes.txt") do + table.insert(g_Fortunes, line) + end + -- Use the InfoReg shared library to process the Info.lua file: dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua") RegisterPluginInfoCommands() RegisterPluginInfoConsoleCommands() + + -- Seed the random generator: + math.randomseed(os.time()) return true end @@ -62,6 +160,26 @@ end +function HandleConsoleNetClose(a_Split) + -- Get the port to close: + local Port = tonumber(a_Split[3] or 1024) + if not(Port) then + return true, "Bad port number: \"" .. Port .. "\"." + end + + -- Close the server, if there is one: + if not(g_Servers[Port]) then + return true, "There is no server currently listening on port " .. Port .. "." + end + g_Servers[Port]:Close() + g_Servers[Port] = nil + return true, "Port " .. Port .. " closed." +end + + + + + function HandleConsoleNetLookup(a_Split) -- Get the name to look up: local Addr = a_Split[3] or "google.com" @@ -103,3 +221,31 @@ end + +function HandleConsoleNetListen(a_Split) + -- Get the params: + local Port = tonumber(a_Split[3] or 1024) + if not(Port) then + return true, "Invalid port: \"" .. Port .. "\"." + end + local Service = string.lower(a_Split[4] or "echo") + + -- Create the callbacks specific for the service: + if (g_Services[Service] == nil) then + return true, "No such service: " .. Service + end + local Callbacks = g_Services[Service](Port) + + -- Start the server: + local srv = cNetwork:Listen(Port, Callbacks) + if not(srv:IsListening()) then + -- The error message has already been printed in the Callbacks.OnError() + return true + end + g_Servers[Port] = srv + return true, Service .. " server started on port " .. Port +end + + + + diff --git a/MCServer/Plugins/NetworkTest/splashes.txt b/MCServer/Plugins/NetworkTest/splashes.txt new file mode 100644 index 000000000..50a87bd91 --- /dev/null +++ b/MCServer/Plugins/NetworkTest/splashes.txt @@ -0,0 +1,357 @@ +As seen on TV! +Awesome! +100% pure! +May contain nuts! +Better than Prey! +More polygons! +Sexy! +Limited edition! +Flashing letters! +Made by Notch! +It's here! +Best in class! +It's finished! +Kind of dragon free! +Excitement! +More than 500 sold! +One of a kind! +Heaps of hits on YouTube! +Indev! +Spiders everywhere! +Check it out! +Holy cow, man! +It's a game! +Made in Sweden! +Uses LWJGL! +Reticulating splines! +Minecraft! +Yaaay! +Singleplayer! +Keyboard compatible! +Undocumented! +Ingots! +Exploding creepers! +That's no moon! +l33t! +Create! +Survive! +Dungeon! +Exclusive! +The bee's knees! +Down with O.P.P.! +Closed source! +Classy! +Wow! +Not on steam! +Oh man! +Awesome community! +Pixels! +Teetsuuuuoooo! +Kaaneeeedaaaa! +Now with difficulty! +Enhanced! +90% bug free! +Pretty! +12 herbs and spices! +Fat free! +Absolutely no memes! +Free dental! +Ask your doctor! +Minors welcome! +Cloud computing! +Legal in Finland! +Hard to label! +Technically good! +Bringing home the bacon! +Indie! +GOTY! +Ceci n'est pas une title screen! +Euclidian! +Now in 3D! +Inspirational! +Herregud! +Complex cellular automata! +Yes, sir! +Played by cowboys! +OpenGL 2.1 (if supported)! +Thousands of colors! +Try it! +Age of Wonders is better! +Try the mushroom stew! +Sensational! +Hot tamale, hot hot tamale! +Play him off, keyboard cat! +Guaranteed! +Macroscopic! +Bring it on! +Random splash! +Call your mother! +Monster infighting! +Loved by millions! +Ultimate edition! +Freaky! +You've got a brand new key! +Water proof! +Uninflammable! +Whoa, dude! +All inclusive! +Tell your friends! +NP is not in P! +Notch <3 ez! +Music by C418! +Livestreamed! +Haunted! +Polynomial! +Terrestrial! +All is full of love! +Full of stars! +Scientific! +Cooler than Spock! +Collaborate and listen! +Never dig down! +Take frequent breaks! +Not linear! +Han shot first! +Nice to meet you! +Buckets of lava! +Ride the pig! +Larger than Earth! +sqrt(-1) love you! +Phobos anomaly! +Punching wood! +Falling off cliffs! +0% sugar! +150% hyperbole! +Synecdoche! +Let's danec! +Seecret Friday update! +Reference implementation! +Lewd with two dudes with food! +Kiss the sky! +20 GOTO 10! +Verlet intregration! +Peter Griffin! +Do not distribute! +Cogito ergo sum! +4815162342 lines of code! +A skeleton popped out! +The Work of Notch! +The sum of its parts! +BTAF used to be good! +I miss ADOM! +umop-apisdn! +OICU812! +Bring me Ray Cokes! +Finger-licking! +Thematic! +Pneumatic! +Sublime! +Octagonal! +Une baguette! +Gargamel plays it! +Rita is the new top dog! +SWM forever! +Representing Edsbyn! +Matt Damon! +Supercalifragilisticexpialidocious! +Consummate V's! +Cow Tools! +Double buffered! +Fan fiction! +Flaxkikare! +Jason! Jason! Jason! +Hotter than the sun! +Internet enabled! +Autonomous! +Engage! +Fantasy! +DRR! DRR! DRR! +Kick it root down! +Regional resources! +Woo, facepunch! +Woo, somethingawful! +Woo, /v/! +Woo, tigsource! +Woo, minecraftforum! +Woo, worldofminecraft! +Woo, reddit! +Woo, 2pp! +Google anlyticsed! +Now supports åäö! +Give us Gordon! +Tip your waiter! +Very fun! +12345 is a bad password! +Vote for net neutrality! +Lives in a pineapple under the sea! +MAP11 has two names! +Omnipotent! +Gasp! +...! +Bees, bees, bees, bees! +Jag känner en bot! +This text is hard to read if you play the game at the default resolution, but at 1080p it's fine! +Haha, LOL! +Hampsterdance! +Switches and ores! +Menger sponge! +idspispopd! +Eple (original edit)! +So fresh, so clean! +Slow acting portals! +Try the Nether! +Don't look directly at the bugs! +Oh, ok, Pigmen! +Finally with ladders! +Scary! +Play Minecraft, Watch Topgear, Get Pig! +Twittered about! +Jump up, jump up, and get down! +Joel is neat! +A riddle, wrapped in a mystery! +Huge tracts of land! +Welcome to your Doom! +Stay a while, stay forever! +Stay a while and listen! +Treatment for your rash! +"Autological" is! +Information wants to be free! +"Almost never" is an interesting concept! +Lots of truthiness! +The creeper is a spy! +Turing complete! +It's groundbreaking! +Let our battle's begin! +The sky is the limit! +Jeb has amazing hair! +Ryan also has amazing hair! +Casual gaming! +Undefeated! +Kinda like Lemmings! +Follow the train, CJ! +Leveraging synergy! +This message will never appear on the splash screen, isn't that weird? +DungeonQuest is unfair! +110813! +90210! +Check out the far lands! +Tyrion would love it! +Also try VVVVVV! +Also try Super Meat Boy! +Also try Terraria! +Also try Mount And Blade! +Also try Project Zomboid! +Also try World of Goo! +Also try Limbo! +Also try Pixeljunk Shooter! +Also try Braid! +That's super! +Bread is pain! +Read more books! +Khaaaaaaaaan! +Less addictive than TV Tropes! +More addictive than lemonade! +Bigger than a bread box! +Millions of peaches! +Fnord! +This is my true form! +Totally forgot about Dre! +Don't bother with the clones! +Pumpkinhead! +Hobo humping slobo babe! +Made by Jeb! +Has an ending! +Finally complete! +Feature packed! +Boots with the fur! +Stop, hammertime! +Testificates! +Conventional! +Homeomorphic to a 3-sphere! +Doesn't avoid double negatives! +Place ALL the blocks! +Does barrel rolls! +Meeting expectations! +PC gaming since 1873! +Ghoughpteighbteau tchoghs! +Déjà vu! +Déjà vu! +Got your nose! +Haley loves Elan! +Afraid of the big, black bat! +Doesn't use the U-word! +Child's play! +See you next Friday or so! +From the streets of Södermalm! +150 bpm for 400000 minutes! +Technologic! +Funk soul brother! +Pumpa kungen! +日本ハロー! +한국 안녕하세요! +Helo Cymru! +Cześć Polsko! +你好中国! +Привет Россия! +Γεια σου Ελλάδα! +My life for Aiur! +Lennart lennart = new Lennart(); +I see your vocabulary has improved! +Who put it there? +You can't explain that! +if not ok then return end +§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac +§kFUNKY LOL +SOPA means LOSER in Swedish! +Big Pointy Teeth! +Bekarton guards the gate! +Mmmph, mmph! +Don't feed avocados to parrots! +Swords for everyone! +Plz reply to my tweet! +.party()! +Take her pillow! +Put that cookie down! +Pretty scary! +I have a suggestion. +Now with extra hugs! +Now Java 6! +Woah. +HURNERJSGER? +What's up, Doc? +Now contains 32 random daily cats! +That's Numberwang! +pls rt +Do you want to join my server? +Put a little fence around it! +Throw a blanket over it! +One day, somewhere in the future, my work will be quoted! +Now with additional stuff! +Extra things! +Yay, puppies for everyone! +So sweet, like a nice bon bon! +Popping tags! +Very influential in its circle! +Now With Multiplayer! +Rise from your grave! +Warning! A huge battleship "STEVE" is approaching fast! +Blue warrior shot the food! +Run, coward! I hunger! +Flavor with no seasoning! +Strange, but not a stranger! +Tougher than diamonds, rich like cream! +Getting ready to show! +Getting ready to know! +Getting ready to drop! +Getting ready to shock! +Getting ready to freak! +Getting ready to speak! +It swings, it jives! +Cruising streets for gold! +Take an eggbeater and beat it against a skillet! +Make me a table, a funky table! +Take the elevator to the mezzanine! +Stop being reasonable, this is the Internet! +/give @a hugs 64 +This is good for Realms. +Any computer is a laptop if you're brave enough! \ No newline at end of file diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 13428b5c6..40b8d4bd9 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -9,6 +9,7 @@ SET (SRCS DeprecatedBindings.cpp LuaChunkStay.cpp LuaNameLookup.cpp + LuaServerHandle.cpp LuaState.cpp LuaTCPLink.cpp LuaWindow.cpp @@ -27,6 +28,7 @@ SET (HDRS LuaChunkStay.h LuaFunctions.h LuaNameLookup.h + LuaServerHandle.h LuaState.h LuaTCPLink.h LuaWindow.h diff --git a/src/Bindings/LuaNameLookup.h b/src/Bindings/LuaNameLookup.h index 8b456ad90..e4cdb9f53 100644 --- a/src/Bindings/LuaNameLookup.h +++ b/src/Bindings/LuaNameLookup.h @@ -25,7 +25,7 @@ public: cLuaNameLookup(const AString & a_Query, cPluginLua & a_Plugin, int a_CallbacksTableStackPos); protected: - /** The plugin for which the link is created. */ + /** The plugin for which the query is created. */ cPluginLua & m_Plugin; /** The Lua table that holds the callbacks to be invoked. */ diff --git a/src/Bindings/LuaServerHandle.cpp b/src/Bindings/LuaServerHandle.cpp new file mode 100644 index 000000000..7b82003bb --- /dev/null +++ b/src/Bindings/LuaServerHandle.cpp @@ -0,0 +1,213 @@ + +// LuaServerHandle.cpp + +// Implements the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API + +#include "Globals.h" +#include "LuaServerHandle.h" +#include "LuaTCPLink.h" +#include "tolua++/include/tolua++.h" + + + + + +cLuaServerHandle::cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos): + m_Plugin(a_Plugin), + m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos), + m_Port(a_Port) +{ + LOGD("Creating LuaServerHandle at %p.", this); +} + + + + + + +cLuaServerHandle::~cLuaServerHandle() +{ + // If the server handle is still open, close it explicitly: + LOGD("Deleting LuaServerHandle at %p.", this); + Close(); +} + + + + + +void cLuaServerHandle::SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self) +{ + ASSERT(m_ServerHandle == nullptr); // The handle can be set only once + + m_ServerHandle = a_ServerHandle; + m_Self = a_Self; +} + + + + + +void cLuaServerHandle::Close(void) +{ + LOGD("Closing LuaServerHandle at %p.", this); + + // Safely grab a copy of the server handle: + cServerHandlePtr ServerHandle = m_ServerHandle; + if (ServerHandle == nullptr) + { + return; + } + + // Close the underlying server handle: + ServerHandle->Close(); + + // Close all connections at this server: + cLuaTCPLinkPtrs Connections; + { + cCSLock Lock(m_CSConnections); + std::swap(Connections, m_Connections); + } + for (auto & conn: Connections) + { + conn->Close(); + } + Connections.clear(); + + // Allow the internal server handle to be deallocated: + m_ServerHandle.reset(); +} + + + + + +bool cLuaServerHandle::IsListening(void) +{ + // Safely grab a copy of the server handle: + cServerHandlePtr ServerHandle = m_ServerHandle; + if (ServerHandle == nullptr) + { + return false; + } + + // Query the underlying server handle: + return ServerHandle->IsListening(); +} + + + + + +void cLuaServerHandle::RemoveLink(cLuaTCPLink * a_Link) +{ + cCSLock Lock(m_CSConnections); + for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) + { + if (itr->get() == a_Link) + { + m_Connections.erase(itr); + break; + } + } // for itr - m_Connections[] +} + + + + + +void cLuaServerHandle::Release(void) +{ + // Close the server, if it isn't closed yet: + Close(); + + // Allow self to deallocate: + m_Self.reset(); +} + + + + + +cTCPLink::cCallbacksPtr cLuaServerHandle::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) +{ + // If not valid anymore, drop the connection: + if (!m_Callbacks.IsValid()) + { + return nullptr; + } + + // Ask the plugin for link callbacks: + cPluginLua::cOperation Op(m_Plugin); + cLuaState::cRef LinkCallbacks; + if ( + !Op().Call(cLuaState::cTableRef(m_Callbacks, "OnIncomingConnection"), a_RemoteIPAddress, a_RemotePort, m_Port, cLuaState::Return, LinkCallbacks) || + !LinkCallbacks.IsValid() + ) + { + LOGINFO("cNetwork server (port %d) OnIncomingConnection callback failed in plugin %s. Dropping connection.", + m_Port, m_Plugin.GetName().c_str() + ); + return nullptr; + } + + // Create the link wrapper to use with the callbacks: + auto res = std::make_shared(m_Plugin, std::move(LinkCallbacks), m_Self); + + // Add the link to the list of our connections: + cCSLock Lock(m_CSConnections); + m_Connections.push_back(res); + + return res; +} + + + + + +void cLuaServerHandle::OnAccepted(cTCPLink & a_Link) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Notify the plugin: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnAccepted"), static_cast(a_Link.GetCallbacks().get()))) + { + LOGINFO("cNetwork server (port %d) OnAccepted callback failed in plugin %s, connection to %s:%d.", + m_Port, m_Plugin.GetName().c_str(), a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort() + ); + return; + } +} + + + + + +void cLuaServerHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + // Check if we're still valid: + if (!m_Callbacks.IsValid()) + { + return; + } + + // Notify the plugin: + cPluginLua::cOperation Op(m_Plugin); + if (!Op().Call(cLuaState::cTableRef(m_Callbacks, "OnError"), a_ErrorCode, a_ErrorMsg)) + { + LOGINFO("cNetwork server (port %d) OnError callback failed in plugin %s. The error is %d (%s).", + m_Port, m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() + ); + return; + } +} + + + + + diff --git a/src/Bindings/LuaServerHandle.h b/src/Bindings/LuaServerHandle.h new file mode 100644 index 000000000..9325bca3e --- /dev/null +++ b/src/Bindings/LuaServerHandle.h @@ -0,0 +1,89 @@ + +// LuaServerHandle.h + +// Declares the cLuaServerHandle class wrapping the cServerHandle functionality for Lua API + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "PluginLua.h" + + + + + +// fwd: +class cLuaTCPLink; +typedef SharedPtr cLuaTCPLinkPtr; +typedef std::vector cLuaTCPLinkPtrs; +class cLuaServerHandle; +typedef SharedPtr cLuaServerHandlePtr; + + + + +class cLuaServerHandle: + public cNetwork::cListenCallbacks +{ +public: + /** Creates a new instance of the server handle, + attached to the specified lua plugin and wrapping the (listen-) callbacks that are in a table at the specified stack pos. */ + cLuaServerHandle(UInt16 a_Port, cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + + ~cLuaServerHandle(); + + /** Called by cNetwork::Listen()'s binding. + Sets the server handle around which this instance is wrapped, and a self SharedPtr for link management. */ + void SetServerHandle(cServerHandlePtr a_ServerHandle, cLuaServerHandlePtr a_Self); + + /** Terminates all connections and closes the listening socket. */ + void Close(void); + + /** Returns true if the server is currently listening. */ + bool IsListening(void); + + /** Removes the link from the list of links that the server is currently tracking. */ + void RemoveLink(cLuaTCPLink * a_Link); + + /** Called when Lua garbage-collects the object. + Releases the internal SharedPtr to self, so that the instance may be deallocated. */ + void Release(void); + +protected: + /** The plugin for which the server is created. */ + cPluginLua & m_Plugin; + + /** The Lua table that holds the callbacks to be invoked. */ + cLuaState::cRef m_Callbacks; + + /** The port on which the server is listening. + Used mainly for better error reporting. */ + UInt16 m_Port; + + /** The cServerHandle around which this instance is wrapped. */ + cServerHandlePtr m_ServerHandle; + + /** Protects m_Connections against multithreaded access. */ + cCriticalSection m_CSConnections; + + /** All connections that are currently active in this server. + Protected by m_CSConnections. */ + cLuaTCPLinkPtrs m_Connections; + + /** SharedPtr to self, given out to newly created links. */ + cLuaServerHandlePtr m_Self; + + + // cNetwork::cListenCallbacks overrides: + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override; + virtual void OnAccepted(cTCPLink & a_Link) override; + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; +}; + + + + diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 4de34c88d..73b114599 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -656,6 +656,18 @@ void cLuaState::Push(cItems * a_Items) +void cLuaState::Push(cLuaServerHandle * a_ServerHandle) +{ + ASSERT(IsValid()); + + tolua_pushusertype(m_LuaState, a_ServerHandle, "cServerHandle"); + m_NumCurrentFunctionArgs += 1; +} + + + + + void cLuaState::Push(cLuaTCPLink * a_TCPLink) { ASSERT(IsValid()); @@ -970,6 +982,15 @@ void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal) +void cLuaState::GetStackValue(int a_StackPos, cRef & a_Ref) +{ + a_Ref.RefStack(*this, a_StackPos); +} + + + + + bool cLuaState::CallFunction(int a_NumResults) { ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first @@ -1539,6 +1560,18 @@ cLuaState::cRef::cRef(cLuaState & a_LuaState, int a_StackPos) : +cLuaState::cRef::cRef(cRef && a_FromRef): + m_LuaState(a_FromRef.m_LuaState), + m_Ref(a_FromRef.m_Ref) +{ + a_FromRef.m_LuaState = nullptr; + a_FromRef.m_Ref = LUA_REFNIL; +} + + + + + cLuaState::cRef::~cRef() { if (m_LuaState != nullptr) diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 0dcd248fe..f68b022ea 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -60,6 +60,7 @@ class cHopperEntity; class cBlockEntity; class cBoundingBox; class cLuaTCPLink; +class cLuaServerHandle; typedef cBoundingBox * pBoundingBox; typedef cWorld * pWorld; @@ -84,6 +85,10 @@ public: /** Creates a reference in the specified LuaState for object at the specified StackPos */ cRef(cLuaState & a_LuaState, int a_StackPos); + + /** Moves the reference from the specified instance into a newly created instance. + The old instance is then "!IsValid()". */ + cRef(cRef && a_FromRef); ~cRef(); @@ -203,6 +208,7 @@ public: void Push(cHopperEntity * a_Hopper); void Push(cItem * a_Item); void Push(cItems * a_Items); + void Push(cLuaServerHandle * a_ServerHandle); void Push(cLuaTCPLink * a_TCPLink); void Push(cMonster * a_Monster); void Push(cPickup * a_Pickup); @@ -242,6 +248,9 @@ public: /** Retrieve value at a_StackPos, if it is a valid cWorld class. If not, a_Value is unchanged */ void GetStackValue(int a_StackPos, pWorld & a_Value); + + /** Store the value at a_StackPos as a reference. */ + void GetStackValue(int a_StackPos, cRef & a_Ref); /** Call the specified Lua function. Returns true if call succeeded, false if there was an error. diff --git a/src/Bindings/LuaTCPLink.cpp b/src/Bindings/LuaTCPLink.cpp index f88aeff84..6b8395806 100644 --- a/src/Bindings/LuaTCPLink.cpp +++ b/src/Bindings/LuaTCPLink.cpp @@ -5,6 +5,7 @@ #include "Globals.h" #include "LuaTCPLink.h" +#include "LuaServerHandle.h" @@ -14,6 +15,47 @@ cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos): m_Plugin(a_Plugin), m_Callbacks(a_Plugin.GetLuaState(), a_CallbacksTableStackPos) { + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaTCPLink::cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_ServerHandle): + m_Plugin(a_Plugin), + m_Callbacks(std::move(a_CallbacksTableRef)), + m_Server(std::move(a_ServerHandle)) +{ + // Warn if the callbacks aren't valid: + if (!m_Callbacks.IsValid()) + { + LOGWARNING("cTCPLink in plugin %s: callbacks could not be retrieved", m_Plugin.GetName().c_str()); + cPluginLua::cOperation Op(m_Plugin); + Op().LogStackTrace(); + } +} + + + + + +cLuaTCPLink::~cLuaTCPLink() +{ + // If the link is still open, close it: + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + Link->Close(); + } + + Terminated(); } @@ -107,15 +149,14 @@ UInt16 cLuaTCPLink::GetRemotePort(void) const void cLuaTCPLink::Shutdown(void) { - // Safely grab a copy of the link: + // Safely grab a copy of the link and shut it down: cTCPLinkPtr Link = m_Link; - if (Link == nullptr) + if (Link != nullptr) { - return; + Link->Shutdown(); } - // Shutdown: - Link->Shutdown(); + Terminated(); } @@ -124,17 +165,48 @@ void cLuaTCPLink::Shutdown(void) void cLuaTCPLink::Close(void) { - // Safely grab a copy of the link: + // If the link is still open, close it: cTCPLinkPtr Link = m_Link; - if (Link == nullptr) + if (Link != nullptr) { - return; + Link->Close(); } - // Close the link: - Link->Close(); + Terminated(); } + + + + +void cLuaTCPLink::Terminated(void) +{ + // Disable the callbacks: + if (m_Callbacks.IsValid()) + { + m_Callbacks.UnRef(); + } + + // If the managing server is still alive, let it know we're terminating: + auto Server = m_Server.lock(); + if (Server != nullptr) + { + Server->RemoveLink(this); + } + + // If the link is still open, close it: + cTCPLinkPtr Link = m_Link; + if (Link != nullptr) + { + Link->Close(); + m_Link.reset(); + } +} + + + + + void cLuaTCPLink::OnConnected(cTCPLink & a_Link) { // Check if we're still valid: @@ -171,6 +243,8 @@ void cLuaTCPLink::OnError(int a_ErrorCode, const AString & a_ErrorMsg) m_Plugin.GetName().c_str(), a_ErrorCode, a_ErrorMsg.c_str() ); } + + Terminated(); } @@ -221,7 +295,8 @@ void cLuaTCPLink::OnRemoteClosed(void) { LOGINFO("cTCPLink OnRemoteClosed() callback failed in plugin %s.", m_Plugin.GetName().c_str()); } - m_Link.reset(); + + Terminated(); } diff --git a/src/Bindings/LuaTCPLink.h b/src/Bindings/LuaTCPLink.h index 125cc1b31..f2af911ec 100644 --- a/src/Bindings/LuaTCPLink.h +++ b/src/Bindings/LuaTCPLink.h @@ -16,6 +16,14 @@ +// fwd: +class cLuaServerHandle; +typedef WeakPtr cLuaServerHandleWPtr; + + + + + class cLuaTCPLink: public cNetwork::cConnectCallbacks, public cTCPLink::cCallbacks @@ -24,6 +32,11 @@ public: /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in a table at the specified stack pos. */ cLuaTCPLink(cPluginLua & a_Plugin, int a_CallbacksTableStackPos); + /** Creates a new instance of the link, attached to the specified plugin and wrapping the callbacks that are in the specified referenced table. */ + cLuaTCPLink(cPluginLua & a_Plugin, cLuaState::cRef && a_CallbacksTableRef, cLuaServerHandleWPtr a_Server); + + ~cLuaTCPLink(); + /** Sends the data contained in the string to the remote peer. Returns true if successful, false on immediate failure (queueing the data failed or link not available). */ bool Send(const AString & a_Data); @@ -60,6 +73,14 @@ protected: May be nullptr. */ cTCPLinkPtr m_Link; + /** The server that is responsible for this link, if any. */ + cLuaServerHandleWPtr m_Server; + + + /** Common code called when the link is considered as terminated. + Releases m_Link, m_Callbacks and this from m_Server, each when applicable. */ + void Terminated(void); + // cNetwork::cConnectCallbacks overrides: virtual void OnConnected(cTCPLink & a_Link) override; virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; diff --git a/src/Bindings/ManualBindings_Network.cpp b/src/Bindings/ManualBindings_Network.cpp index 3123ef885..24c8c73b8 100644 --- a/src/Bindings/ManualBindings_Network.cpp +++ b/src/Bindings/ManualBindings_Network.cpp @@ -10,6 +10,7 @@ #include "LuaState.h" #include "LuaTCPLink.h" #include "LuaNameLookup.h" +#include "LuaServerHandle.h" @@ -72,6 +73,7 @@ static int tolua_cNetwork_Connect(lua_State * L) +/** Binds cNetwork::HostnameToIP */ static int tolua_cNetwork_HostnameToIP(lua_State * L) { // Function signature: @@ -112,6 +114,7 @@ static int tolua_cNetwork_HostnameToIP(lua_State * L) +/** Binds cNetwork::IPToHostname */ static int tolua_cNetwork_IPToHostname(lua_State * L) { // Function signature: @@ -152,9 +155,66 @@ static int tolua_cNetwork_IPToHostname(lua_State * L) +/** Binds cNetwork::Listen */ +static int tolua_cNetwork_Listen(lua_State * L) +{ + // Function signature: + // cNetwork:Listen(Port, Callbacks) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cNetwork") || + !S.CheckParamNumber(2) || + !S.CheckParamTable(3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the plugin instance: + cPluginLua * Plugin = GetLuaPlugin(L); + if (Plugin == nullptr) + { + // An error message has been already printed in GetLuaPlugin() + S.Push(false); + return 1; + } + + // Read the params: + int Port; + S.GetStackValues(2, Port); + if ((Port < 0) || (Port > 65535)) + { + LOGWARNING("cNetwork:Listen() called with invalid port (%d), failing the request.", Port); + S.Push(false); + return 1; + } + UInt16 Port16 = static_cast(Port); + + // Create the LuaTCPLink glue class: + auto Srv = std::make_shared(Port16, *Plugin, 3); + + // Listen: + Srv->SetServerHandle(cNetwork::Listen(Port16, Srv), Srv); + + // Register the server to be garbage-collected by Lua: + tolua_pushusertype(L, Srv.get(), "cServerHandle"); + tolua_register_gc(L, lua_gettop(L)); + + // Return the server handle wrapper: + S.Push(Srv.get()); + return 1; +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cTCPLink bindings (routed through cLuaTCPLink): +/** Binds cLuaTCPLink::Send */ static int tolua_cTCPLink_Send(lua_State * L) { // Function signature: @@ -193,6 +253,7 @@ static int tolua_cTCPLink_Send(lua_State * L) +/** Binds cLuaTCPLink::GetLocalIP */ static int tolua_cTCPLink_GetLocalIP(lua_State * L) { // Function signature: @@ -226,6 +287,7 @@ static int tolua_cTCPLink_GetLocalIP(lua_State * L) +/** Binds cLuaTCPLink::GetLocalPort */ static int tolua_cTCPLink_GetLocalPort(lua_State * L) { // Function signature: @@ -259,6 +321,7 @@ static int tolua_cTCPLink_GetLocalPort(lua_State * L) +/** Binds cLuaTCPLink::GetRemoteIP */ static int tolua_cTCPLink_GetRemoteIP(lua_State * L) { // Function signature: @@ -292,6 +355,7 @@ static int tolua_cTCPLink_GetRemoteIP(lua_State * L) +/** Binds cLuaTCPLink::GetRemotePort */ static int tolua_cTCPLink_GetRemotePort(lua_State * L) { // Function signature: @@ -325,6 +389,90 @@ static int tolua_cTCPLink_GetRemotePort(lua_State * L) +//////////////////////////////////////////////////////////////////////////////// +// cServerHandle bindings (routed through cLuaServerHandle): + +/** Called when Lua destroys the object instance. +Close the server and let it deallocate on its own (it's in a SharedPtr). */ +static int tolua_collect_cServerHandle(lua_State * L) +{ + cLuaServerHandle * Srv = static_cast(tolua_tousertype(L, 1, nullptr)); + Srv->Release(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::Close */ +static int tolua_cServerHandle_Close(lua_State * L) +{ + // Function signature: + // ServerInstance:Close() + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:Close(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast(lua_touserdata(L, 1)); + + // Close it: + Srv->Close(); + return 0; +} + + + + + +/** Binds cLuaServerHandle::IsListening */ +static int tolua_cServerHandle_IsListening(lua_State * L) +{ + // Function signature: + // ServerInstance:IsListening() -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserType(1, "cServerHandle") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the server handle: + cLuaServerHandle * Srv; + if (lua_isnil(L, 1)) + { + LOGWARNING("cServerHandle:IsListening(): invalid server handle object. Stack trace:"); + S.LogStackTrace(); + return 0; + } + Srv = *static_cast(lua_touserdata(L, 1)); + + // Close it: + S.Push(Srv->IsListening()); + return 1; +} + + + + + //////////////////////////////////////////////////////////////////////////////// // Register the bindings: @@ -335,15 +483,15 @@ void ManualBindings::BindNetwork(lua_State * tolua_S) tolua_cclass(tolua_S, "cNetwork", "cNetwork", "", nullptr); tolua_usertype(tolua_S, "cTCPLink"); tolua_cclass(tolua_S, "cTCPLink", "cTCPLink", "", nullptr); + tolua_usertype(tolua_S, "cServerHandle"); + tolua_cclass(tolua_S, "cServerHandle", "cServerHandle", "", tolua_collect_cServerHandle); // Fill in the functions (alpha-sorted): tolua_beginmodule(tolua_S, "cNetwork"); tolua_function(tolua_S, "Connect", tolua_cNetwork_Connect); tolua_function(tolua_S, "HostnameToIP", tolua_cNetwork_HostnameToIP); tolua_function(tolua_S, "IPToHostname", tolua_cNetwork_IPToHostname); - /* tolua_function(tolua_S, "Listen", tolua_cNetwork_Listen); - */ tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cTCPLink"); @@ -353,6 +501,11 @@ void ManualBindings::BindNetwork(lua_State * tolua_S) tolua_function(tolua_S, "GetRemoteIP", tolua_cTCPLink_GetRemoteIP); tolua_function(tolua_S, "GetRemotePort", tolua_cTCPLink_GetRemotePort); tolua_endmodule(tolua_S); + + tolua_beginmodule(tolua_S, "cServerHandle"); + tolua_function(tolua_S, "Close", tolua_cServerHandle_Close); + tolua_function(tolua_S, "IsListening", tolua_cServerHandle_IsListening); + tolua_endmodule(tolua_S); } diff --git a/src/Globals.h b/src/Globals.h index 654ede95f..29eaac871 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -381,6 +381,7 @@ void inline LOG(const char * a_Format, ...) // Unified shared ptr from before C++11. Also no silly undercores. #define SharedPtr std::shared_ptr +#define WeakPtr std::weak_ptr diff --git a/src/OSSupport/Network.h b/src/OSSupport/Network.h index cdf6ba0e9..e883dfb29 100644 --- a/src/OSSupport/Network.h +++ b/src/OSSupport/Network.h @@ -90,6 +90,9 @@ public: Sends the RST packet, queued outgoing and incoming data is lost. */ virtual void Close(void) = 0; + /** Returns the callbacks that are used. */ + cCallbacksPtr GetCallbacks(void) const { return m_Callbacks; } + protected: /** Callbacks to be used for the various situations. */ cCallbacksPtr m_Callbacks; diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp index 5f4b7651d..49fb89122 100644 --- a/tests/Network/EchoServer.cpp +++ b/tests/Network/EchoServer.cpp @@ -119,6 +119,7 @@ void DoTest(void) LOG("Server terminating."); Server->Close(); ASSERT(!Server->IsListening()); + Server.reset(); LOGD("Server has been closed."); }