我花了很多时间去组装一个完整的服务器程序,其中包括了命令行解析,日志系统,TCP,UDP,HTTP(HTTPS),WEBSOCKET,DATABASE等子模块,INI,JSON,XML等文件格式的解析。其实,自己摸索一遍之后总结一个框架出来也是不错的,但是,自己写太费事,而用开源组装,面对浩如烟海,各种风格理念不一,半生不死的开源库,又实在难爱的起来。翻来覆去,还是POCO好。
1.POCO安装
自从有了vcpkg,安装第三方库不知道有多方便了(第二步编译出vcpkg 程序)
(1)git clone https://github.com/Microsoft/vcpk
(2)bootstrap-vcpkg.bat
(3)vcpkg install poco:x64-windows
2.POCO网站
3.命令行解析,日志系统初始化,Application子系统启用
main.cpp
// Matchmaker.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <Poco/Util/Option.h>
#include <Poco/Util/OptionSet.h>
#include <Poco/File.h>
#include <Poco/Logger.h>
#include <Poco/ConsoleChannel.h>
#include <Poco/FileChannel.h>
#include <Poco/SplitterChannel.h>
#include <Poco/FormattingChannel.h>
#include <Poco/PatternFormatter.h>
#include <Poco/AutoPtr.h>
#include <Poco/Util/HelpFormatter.h>
#include <Poco/Util/IniFileConfiguration.h>
#include <Poco/Util/ServerApplication.h>
#include "cirrusserver.h"
#include "matchmaker.h"
class App : public Poco::Util::ServerApplication
{
public:
App() { }
const char* name() const override
{
return "Matchmaker";
}
void initialize(Application& self) override
{
//Create Log Directory
Poco::File logFile("log");
if (!logFile.exists())
{
logFile.createDirectory();
}
//Log Channel
Poco::AutoPtr<Poco::SplitterChannel> splitterChannel(new Poco::SplitterChannel);
Poco::AutoPtr<Poco::ConsoleChannel> consoleChannel(new Poco::ConsoleChannel);
splitterChannel->addChannel(consoleChannel);
Poco::AutoPtr<Poco::FileChannel> fileChannel(new Poco::FileChannel);
fileChannel->setProperty("path", "log/Matchmaker.log");
fileChannel->setProperty("archive", "timestamp");
splitterChannel->addChannel(fileChannel);
Poco::AutoPtr<Poco::PatternFormatter> patternFormatter(new Poco::PatternFormatter);
patternFormatter->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
Poco::AutoPtr<Poco::FormattingChannel> formattingChannel(new Poco::FormattingChannel(patternFormatter, splitterChannel));
Poco::Logger::root().setChannel(formattingChannel);
//init
Poco::Util::ServerApplication::initialize(self);
}
void uninitialize() override
{
Poco::Util::ServerApplication::uninitialize();
}
void reinitialize(Application& app) override
{
Poco::Util::ServerApplication::reinitialize(app);
}
void defineOptions(Poco::Util::OptionSet& options) override
{
Poco::Util::ServerApplication::defineOptions(options);
setUnixOptions(true);
options.addOption(
Poco::Util::Option("help", "h", "display help information on command line arguments", false)
.repeatable(false)
.callback(Poco::Util::OptionCallback <App>(this, &App::handleOptionHelp)));
options.addOption(
Poco::Util::Option("config", "c", "config file path", false)
.repeatable(false)
.argument("path", true)
.callback(Poco::Util::OptionCallback<App>(this, &App::handleOptionConfig)));
}
void handleOptionHelp(const std::string& name, const std::string& value)
{
Poco::Util::HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.format(std::cerr);
stopOptionsProcessing();
isExit = true;
}
void handleOptionConfig(const std::string& name, const std::string& value)
{
Poco::File iniFile(value);
if (iniFile.exists())
{
loadConfiguration(value);
}
else
{
std::cerr << "File Not Found:" << value;
isExit = true;
}
}
int main(const std::vector<std::string>& args) override
{
if (!isExit)
{
//Server
CirrusServer::instance().Init(config());
CirrusServer::instance().Start();
MatchMaker matchMaker;
matchMaker.Init(config());
matchMaker.Start();
waitForTerminationRequest();
matchMaker.Stop();
CirrusServer::instance().Stop();
}
return Poco::Util::ServerApplication::EXIT_OK;
}
private:
bool isExit = false;
};
POCO_SERVER_MAIN(App);
4.HTTP
matchmaker.h
#ifndef MATCHMAKER_H
#define MATCHMAKER_H
#include <string>
#include <memory>
#include "cirrusserver.h"
#include <Poco/SharedPtr.h>
#include <Poco/Net/HTTPServer.h>
#include <Poco/Util/Application.h>
class MatchMaker
{
public:
explicit MatchMaker();
bool Init(const Poco::Util::LayeredConfiguration& config);
bool Start();
void Stop();
public:
Poco::SharedPtr<Poco::Net::HTTPServer> _server;
std::string _address;
int _port;
};
#endif // MATCHMAKER_H
matchmaker.cpp
#include "matchmaker.h"
#include "cirrusserver.h"
#include <Poco/Format.h>
#include <Poco/Exception.h>
#include <Poco/RegularExpression.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
class RootRequestHandler : public Poco::Net::HTTPRequestHandler
{
public:
void handleRequest(Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response)
{
CirrusClient cirrusClient;
if (CirrusServer::instance().GetAvailableCirrusServer(cirrusClient))
{
std::string url = Poco::format("http://%s:%d/", cirrusClient.address, cirrusClient.port);
response.redirect(url.c_str());
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information("Redirect to " + url);
}
else
{
response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
response.setContentType("text/plain");
std::ostream& out = response.send();
out << "No Cirrus servers are available";
}
}
};
class CustomRequestHandler : public Poco::Net::HTTPRequestHandler
{
public:
void handleRequest(Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response)
{
CirrusClient cirrusClient;
if (CirrusServer::instance().GetAvailableCirrusServer(cirrusClient))
{
std::string url = Poco::format("http://%s:%d/%s", cirrusClient.address, cirrusClient.port, request.getURI());
response.redirect(url.c_str());
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information("Redirect to " + url);
}
else
{
response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
response.setContentType("text/plain");
std::ostream& out = response.send();
out << "No Cirrus servers are available";
}
}
};
class NotFoundRequestHandler : public Poco::Net::HTTPRequestHandler
{
public:
void handleRequest(Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response)
{
response.setStatus(Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
response.setContentType("text/plain");
std::ostream& out = response.send();
out << "404 Not Found";
}
};
class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
public:
RequestHandlerFactory() {};
~RequestHandlerFactory() {};
public:
virtual Poco::Net::HTTPRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request)
{
if (request.getURI() == "/")
{
return new RootRequestHandler();
}
else if(Poco::RegularExpression("(/custom_html/(.*))").match(request.getURI()))
{
return new CustomRequestHandler();
}
else
{
return new NotFoundRequestHandler();
}
};
};
MatchMaker::MatchMaker():_port(9999)
{
}
bool MatchMaker::Init(const Poco::Util::LayeredConfiguration& config)
{
try
{
_address = config.getString("http.address", "0.0.0.0");
_port = config.getInt("http.port", 9999);
Poco::AutoPtr<Poco::Net::HTTPServerParams> params(new Poco::Net::HTTPServerParams);
params->setMaxQueued(64);
params->setMaxThreads(16);
_server = Poco::SharedPtr<Poco::Net::HTTPServer >(new Poco::Net::HTTPServer(new RequestHandlerFactory(), Poco::Net::ServerSocket(_port), params));
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information(Poco::format("Init Http(*:%d) Succeed", _port));
return true;
}
catch(Poco::Exception* ex)
{
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.error(ex->message());
}
return false;
}
bool MatchMaker::Start()
{
if (_server)
{
try
{
_server->start();
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information(Poco::format("Start Http(*:%d) Succeed", _port));
return true;
}
catch (Poco::Exception * ex)
{
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.error(ex->message());
}
}
return false;
}
void MatchMaker::Stop()
{
if (_server)
{
_server->stop();
}
}
5.TCP
cirrusserver.h
#ifndef TCP_SERVER_H_
#define TCP_SERVER_H_
#include <list>
#include <mutex>
#include <Poco/AutoPtr.h>
#include <Poco/SharedPtr.h>
#include <Poco/Net/TCPServer.h>
#include <Poco/SingletonHolder.h>
#include <Poco/Util/LayeredConfiguration.h>
class ServerConnection;
struct CirrusClient
{
std::string address;
int port;
int numConnectedClients;
};
class CirrusServer
{
public:
CirrusServer();
~CirrusServer();
static CirrusServer& instance()
{
static Poco::SingletonHolder<CirrusServer> sh;
return *sh.get();
}
bool Init(const Poco::Util::LayeredConfiguration& config);
bool Start();
void Stop();
bool GetAvailableCirrusServer(CirrusClient& cirrusClient)
{
std::lock_guard lock(_mutex);
if (_users.empty())
{
return false;
}
cirrusClient = _users.front();
_users.pop_front();
return true;
}
void AddCirrusServer(const CirrusClient& cirrusClient)
{
std::lock_guard lock(_mutex);
_users.push_back(cirrusClient);
}
void RemoveCirrusServer(const CirrusClient& cirrusClient)
{
std::lock_guard lock(_mutex);
for(auto it = _users.begin(); it != _users.end(); it++)
{
if ((it->address == cirrusClient.address) && (it->port == cirrusClient.port))
{
_users.erase(it);
return;
}
}
}
void IncrementCirrusServer(const CirrusClient& cirrusClient)
{
std::lock_guard lock(_mutex);
for (auto it = _users.begin(); it != _users.end(); it++)
{
if ((it->address == cirrusClient.address) && (it->port == cirrusClient.port))
{
it->numConnectedClients++;
return;
}
}
}
void DecrementCirrusServer(const CirrusClient& cirrusClient)
{
std::lock_guard lock(_mutex);
for (auto it = _users.begin(); it != _users.end(); it++)
{
if ((it->address == cirrusClient.address) && (it->port == cirrusClient.port))
{
it->numConnectedClients--;
return;
}
}
}
private:
Poco::SharedPtr<Poco::Net::TCPServer> _server;
std::list<CirrusClient> _users;
std::string _address;
int _port;
std::mutex _mutex;
std::thread _thread;
};
#endif
cirrusserver.cpp
#include "cirrusserver.h"
#include <array>
#include <Poco/Array.h>
#include <Poco/Logger.h>
#include <Poco/Net/TCPServerConnection.h>
#include <Poco/Net/TCPServerConnectionFactory.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Dynamic/Var.h>
#include <Poco/Dynamic/Pair.h>
#include <Poco/Dynamic/VarIterator.h>
#include <Poco/JSON/Parser.h>
#include <Poco/JSON/Object.h>
class ServerConnection : public Poco::Net::TCPServerConnection
{
public:
ServerConnection(const Poco::Net::StreamSocket& streamSocket) :TCPServerConnection(streamSocket)
{
}
void run()
{
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information("New connection from: " + socket().peerAddress().host().toString());
bool isOpen = true;
Poco::Timespan timeOut(10, 0);
Poco::Array<char, 8192> incommingBuffer;
while (isOpen)
{
if (socket().poll(timeOut, Poco::Net::Socket::SELECT_READ))
{
int nBytes = -1;
try
{
nBytes = socket().receiveBytes(incommingBuffer.data(), incommingBuffer.size());
}
catch (Poco::Exception & exc)
{
isOpen = false;
CirrusServer::instance().RemoveCirrusServer(_cirrusClient);
logger.error("Network Error: " + exc.displayText());
}
if (nBytes == 0)
{
isOpen = false;
CirrusServer::instance().RemoveCirrusServer(_cirrusClient);
logger.warning("Client Closes Connection!");
}
else
{
Handle(std::string(incommingBuffer.data(), nBytes));
}
}
}
logger.information("Connection finished!");
}
void Handle(const std::string& command)
{
Poco::JSON::Parser parse;
Poco::JSON::Object j = *parse.parse(command).extract<Poco::JSON::Object::Ptr>();
std::string type = j.get("type").toString();
if ("connect" == type)
{
_cirrusClient.address = j.get("address").toString();
_cirrusClient.port = j.get("port").convert<int>();
CirrusServer::instance().AddCirrusServer(_cirrusClient);
}
else if ("clientConnected" == type)
{
CirrusServer::instance().IncrementCirrusServer(_cirrusClient);
}
else if ("clientDisconnected" == type)
{
CirrusServer::instance().DecrementCirrusServer(_cirrusClient);
}
else
{
CirrusServer::instance().RemoveCirrusServer(_cirrusClient);
}
}
private:
CirrusClient _cirrusClient;
};
CirrusServer::CirrusServer() :_port(9999)
{
}
CirrusServer::~CirrusServer()
{
}
bool CirrusServer::Init(const Poco::Util::LayeredConfiguration& config)
{
try
{
_address = config.getString("tcp.address", "127.0.0.1");
_port = config.getInt("tcp.port", 90);
Poco::Net::TCPServerParams::Ptr param = new Poco::Net::TCPServerParams;
param->setMaxQueued(64);
param->setMaxThreads(16);
_server = Poco::SharedPtr<Poco::Net::TCPServer >(new Poco::Net::TCPServer(new Poco::Net::TCPServerConnectionFactoryImpl<ServerConnection>(), Poco::Net::ServerSocket(_port)));
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information(Poco::format("Init Tcp(*:%d) Succeed", _port));
return true;
}
catch (Poco::Exception * ex)
{
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.error(ex->message());
}
return false;
}
bool CirrusServer::Start()
{
if (_server)
{
try {
_server->start();
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.information(Poco::format("Start Tcp(*:%d) Succeed", _port));
return true;
}
catch (Poco::Exception * ex)
{
Poco::Logger& logger = Poco::Logger::get("Matchmaker");
logger.error(ex->message());
}
}
return false;
}
void CirrusServer::Stop()
{
if (_server)
{
_server->stop();
std::lock_guard lock(_mutex);
_users.clear();
}
}
网友评论