Poco TCPServer class
POCO library implements class TCPServer
which dispatches connections as described
here. Most of the classes for these examples are common:
#ifndef SESSION_hpp #define SESSION_hpp #include <Poco/Net/StreamSocket.h> #include <Poco/Mutex.h> using Poco::Net::StreamSocket; using Poco::Mutex; /** Describes a client session (established via TCP). It has unique ID, last activity time, corresponding socket. **/ class Session { public: enum Status { NONE, // default NEW, // new conected client ACTIVE, // request/response generated DONE, // processing finished on TCP and/or chat level DESTROY // ready to destroy message }; Session(/*StreamSocket* socket*/); Session(long sessionId, time_t lastActivity, /*StreamSocket* socket,*/ Status status); ~Session(); long id(); //StreamSocket* socket(); //void socket(StreamSocket* s); time_t lastActivity(); void lastActivityNow(); Status status(); void status(Status s); private: static long nextSessionCounter(); static Mutex _mutexCounter; static long _sessionCounter; long _id; time_t _lastActivity; //StreamSocket* _socket; Status _status; }; #endif // SESSION_hpp
#include <Poco/Logger.h> #include <Poco/Format.h> #include "Session.hpp" using Poco::Logger; Mutex Session::_mutexCounter; long Session::_sessionCounter = 0; Session::Session(/*StreamSocket* socket*/) : /*_socket(socket),*/ _status(NONE) { _id = Session::nextSessionCounter(); time_t t; time(&t); _lastActivity = t; } Session::Session(long id, time_t lastActivity, /*StreamSocket* socket,*/ Status status) : _id(id), _lastActivity(lastActivity), /*_socket(socket),*/ _status(status) { } Session::~Session() { /* _socket->close(); delete _socket; */ _id = 0; _lastActivity = 0; } long Session::id() { return _id; } /* StreamSocket* Session::socket() { return _socket; } void Session::socket(StreamSocket* s) { _socket = s; } */ time_t Session::lastActivity() { return _lastActivity; } void Session::lastActivityNow() { time_t t; time(&t); _lastActivity = t; } Session::Status Session::status() { return _status; } void Session::status(Session::Status s) { _status = s; } long Session::nextSessionCounter() { _mutexCounter.lock(); long id = ++Session::_sessionCounter; _mutexCounter.unlock(); return id; }
#ifndef CHATLINE_HPP #define CHATLINE_HPP #include <string> #include <map> #include <Poco/Mutex.h> using std::string; using std::map; using Poco::Mutex; /** Chat content, parser and formatter. A chat is sent in the format command=login&username=petar&password=lozinka **/ class ChatLine { private: map<string, string> _params; public: static const string PARAM_SEP; static const string NAME_VALUE_SEP; ChatLine(); string command(); void command(string cmd); string username(); void username(string user); string password(); void password(string pass); void to(string t); string to(); void from(string f); string from(); void chatLine(string line); string chatLine(); bool empty(); string toString(); bool parse(string request); string format(); }; #endif // CHATLINE_HPP
#include <Poco/Logger.h> #include <Poco/StringTokenizer.h> #include <Poco/String.h> #include "ChatLine.hpp" using Poco::Logger; using Poco::StringTokenizer; ChatLine::ChatLine() : _params() { } string ChatLine::command() { if (_params.count("command") == 0) return ""; return _params["command"]; } void ChatLine::command(string cmd) { _params["command"] = cmd; } string ChatLine::username() { if (_params.count("username") == 0) return ""; return _params["username"]; } void ChatLine::username(string user) { _params["username"] = user; } string ChatLine::password() { if (_params.count("password") == 0) return ""; return _params["password"]; } void ChatLine::password(string pass) { _params["password"] = pass; } void ChatLine::to(string t) { _params["to"] = t; } string ChatLine::to() { if (_params.count("to") == 0) return ""; return _params["to"]; } void ChatLine::from(string f) { _params["from"] = f; } string ChatLine::from() { if (_params.count("from") == 0) return ""; return _params["from"]; } void ChatLine::chatLine(string line) { _params["chat"] = line; } string ChatLine::chatLine() { if (_params.count("chat") == 0) return ""; return _params["chat"]; } bool ChatLine::empty() { return _params.empty(); } string ChatLine::toString() { string result; for (map<string, string>::const_iterator it = _params.begin(); it != _params.end(); it++) result += it->first + "=" + it->second + ","; return result; } bool ChatLine::parse(string request) { StringTokenizer tokens(request, ChatLine::PARAM_SEP); for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); it++) { StringTokenizer nameValueToken(*it, ChatLine::NAME_VALUE_SEP); if (nameValueToken.count() % 2 != 0) { return false; } string name = Poco::trim<string>(nameValueToken[0]); string value = Poco::trim<string>(nameValueToken[1]); _params[name] = value; } return true; } string ChatLine::format() { string str; for (map<string, string>::const_iterator it = _params.begin(); it != _params.end(); it++) { if (str == "") str = it->first + ChatLine::NAME_VALUE_SEP + it->second; else str += ChatLine::PARAM_SEP + it->first + ChatLine::NAME_VALUE_SEP + it->second; } return str; } const string ChatLine::PARAM_SEP = "&"; const string ChatLine::NAME_VALUE_SEP = "=";
#ifndef MESSAGE_HPP #define MESSAGE_HPP #include "Session.hpp" #include "ChatLine.hpp" /** Stores chat content and session status. Only one message/session exist for a particular socket. **/ class Message { public: static Message* create(/*StreamSocket* socket*/); static Message* create(/*StreamSocket* socket,*/ ChatLine* line); static void destroy(Message* t); Message(); ~Message(); Session* session(); void session(Session* s); ChatLine* chatLine(); void chatLine(ChatLine* line); private: Session* _session; ChatLine* _chatLine; }; #endif // MESSAGE_HPP
#include <cassert> #include "Message.hpp" Message* Message::create(/*StreamSocket* socket*/) { Message* msg = new Message; msg->session(new Session(/*socket*/)); msg->chatLine(NULL); return msg; } Message* Message::create(/*StreamSocket* socket,*/ ChatLine* line) { Message* msg = new Message; msg->session(new Session(/*socket*/)); msg->chatLine(line); return msg; } void Message::destroy(Message* msg) { if (msg != NULL) delete msg; } Message::Message() : _session(NULL), _chatLine(NULL) { } Message::~Message() { if (_session != NULL) delete _session; if (_chatLine != NULL) delete _chatLine; } Session* Message::session() { return _session; } void Message::session(Session* s) { if (_session != NULL) delete _session; _session = s; } ChatLine* Message::chatLine() { return _chatLine; } void Message::chatLine(ChatLine* line) { if (_chatLine != NULL) delete _chatLine; _chatLine = line; }
Handler is quite similar:
#ifndef HANDLER_HPP #define HANDLER_HPP #include <string> #include <vector> #include <map> #include <Poco/Runnable.h> #include <Poco/Mutex.h> #include "Message.hpp" using std::string; using std::vector; using std::map; using Poco::Mutex; class Handler { public: /** Chat line with 'from' address. **/ struct From { string from; string chatLine; From() : from(), chatLine() { } From(string frm, string line) : from(frm), chatLine(line) { } bool empty() { if (from == "" && chatLine == "") return true; return false; } }; Handler(); void run(Message* msg); /** Username and password for each registered user. **/ static map<string, string> _members; private: void removeUser(long sessionId); Mutex _mutexUsers; /** Maps session to username. **/ map<long, string> _users; Mutex _mutexInbox; /** Inbox with messages for a specific user sent by an user. **/ map<string, vector<From> > _inbox; }; #endif // HANDLER_HPP
#include <cassert> #include <Poco/Thread.h> #include <Poco/Logger.h> #include <Poco/Format.h> #include "Handler.hpp" using Poco::Thread; using Poco::Logger; map<string, string> Handler::_members; Handler::Handler() { } void Handler::run(Message* message) { Logger& log = Logger::get("server"); // DONE status is signaled by closing socket if (message->session()->status() == Session::DONE) { removeUser(message->session()->id()); message->session()->status(Session::DESTROY); } // check commands and their proper usage else if (message->chatLine()->command() == "login") { if (message->session()->status() == Session::NEW) { string username = message->chatLine()->username(); string password = message->chatLine()->password(); if (Handler::_members.count(username) > 0 && Handler::_members[username] == password) { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", login ok"); message->session()->status(Session::ACTIVE); _mutexUsers.lock(); _users[message->session()->id()] = username; _mutexUsers.unlock(); message->chatLine()->command("login ok"); } else { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", login failed"); message->session()->status(Session::DONE); removeUser(message->session()->id()); message->chatLine()->command("login failed"); } } else { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", cannot login"); message->chatLine()->command("cannot login"); } } else if (message->chatLine()->command() == "logout") { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", logout"); message->session()->status(Session::DONE); removeUser(message->session()->id()); message->chatLine()->command("logout ok"); } else if (message->chatLine()->command() == "send") { if (message->session()->status() == Session::ACTIVE) { if (message->chatLine()->to() == "") { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", send failed, no 'to' field"); message->chatLine()->command("send failed, no 'to' field"); } else if (message->chatLine()->chatLine() == "") { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", send failed, no 'chat' field"); message->chatLine()->command("send failed, no 'chat' field"); } else { _mutexUsers.lock(); string userFrom = _users[message->session()->id()]; _mutexUsers.unlock(); _mutexInbox.lock(); _inbox[message->chatLine()->to()].push_back(From(userFrom, message->chatLine()->chatLine())); _mutexInbox.unlock(); log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", send ok, from=" + Poco::format("%s", message->chatLine()->from()) + ", chat=" + Poco::format("%s", message->chatLine()->chatLine())); message->chatLine()->command("send ok"); } } else { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", send failed, not authorized"); message->session()->status(Session::DONE); removeUser(message->session()->id()); message->chatLine()->command("send failed, not authorized"); } } else if (message->chatLine()->command() == "receive") { if (message->session()->status() == Session::ACTIVE) { _mutexUsers.lock(); string username = _users[message->session()->id()]; _mutexUsers.unlock(); _mutexInbox.lock(); From fm; if (_inbox.count(username) > 0 && _inbox[username].size() > 0) { fm = _inbox[username].back(); _inbox[username].pop_back(); } _mutexInbox.unlock(); if (fm.empty()) { message->chatLine()->command("receive ok, no chatLine"); log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", receive ok, no chatLine"); } else { message->chatLine()->command("receive ok"); message->chatLine()->chatLine(fm.chatLine); message->chatLine()->from(fm.from); log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", receive ok, from=" + Poco::format("%s", fm.from) + ", chat=" + Poco::format("%s", fm.chatLine)); } } else { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", receive failed, not authorized"); message->session()->status(Session::DONE); removeUser(message->session()->id()); message->chatLine()->command("receive failed, not authorized"); } } else { log.debug("Handler::run(): session id=" + Poco::format("%ld", message->session()->id()) + ", unknown command"); message->chatLine()->command("unknown command"); } } /** Removes user's session. **/ void Handler::removeUser(long sessionId) { _mutexUsers.lock(); _users.erase(sessionId); _mutexUsers.unlock(); }
To imlement a server connection, one needs to derive from the TCPServerConnection
:
#ifndef SERVERCONNECTION_HPP #define SERVERCONNECTION_HPP #include <Poco/Net/StreamSocket.h> #include <Poco/Net/ServerSocket.h> #include <Poco/Net/TCPServerConnection.h> #include "Session.hpp" #include "Message.hpp" #include "Handler.hpp" using Poco::Net::StreamSocket; using Poco::Net::ServerSocket; using Poco::Net::TCPServerConnection; class ServerConnection : public TCPServerConnection { public: ServerConnection(const StreamSocket& client); void run(); private: static const short CHUNK_SIZE = 5; static Handler _handler; }; #endif // SERVERCONNECTION_HPP
#include "ServerConnection.hpp" #include <Poco/Format.h> #include <Poco/Logger.h> using Poco::Logger; Handler ServerConnection::_handler; ServerConnection::ServerConnection(const StreamSocket& client) : TCPServerConnection(client) { } void ServerConnection::run() { Logger& log = Logger::get("server"); Message* message = Message::create(); message->session()->status(Session::NEW); log.information("ServerConnection::run(): session " + Poco::format("%ld", message->session()->id()) + " created"); while (true) { const short BUF_SIZE = 128; char buf[BUF_SIZE]; int result = this->socket().receiveBytes(reinterpret_cast<void*>(buf), BUF_SIZE - 1); if (result == 0) { message->session()->status(Session::DONE); } else { buf[result] = '\0'; string request = buf; ChatLine* line = new ChatLine; line->parse(request); message->chatLine(line); } ServerConnection::_handler.run(message); if (message->session()->status() == Session::DESTROY) break; string response = message->chatLine()->format(); result = this->socket().sendBytes(reinterpret_cast<const void*>(response.c_str()), response.size()); if (result == 0) { // have to set DONE status so processor could remove the logged user break; } } log.information("ServerConnection::run(): session " + Poco::format("%ld", message->session()->id()) + " destroyed"); Message::destroy(message); }
To start the server an instance of TCPServer
is created using the binded server socket:
/* protocol: client: command=login&username=petar&password=lozinka server: command=login_ok server: command=login_failed server: command=login_notwice client: command=logout server: command=logout_ok client: command=send&to=pierre&message=hello world server: command=send_ok server: command=send_no_user server: command=send_not_authorized client: command=receive server: command=receive_ok&from=peter&message=hello world server: command=receive_not_authorized server: command=receive_no_messages client: command=foobar server: command=unknown command */ #include <iostream> #include <stdexcept> #include <string> #include <Poco/Logger.h> #include <Poco/FormattingChannel.h> #include <Poco/PatternFormatter.h> #include <Poco/Message.h> #include <Poco/FileChannel.h> #include <Poco/LogStream.h> #include <Poco/Net/TCPServer.h> #include <Poco/Net/TCPServerConnectionFactory.h> #include "ServerConnection.hpp" #include "Handler.hpp" using std::string; using std::cout; using std::endl; using std::exception; using Poco::ThreadPool; using Poco::Logger; using Poco::FormattingChannel; using Poco::PatternFormatter; using Poco::FileChannel; using Poco::LogStream; using Poco::Net::TCPServer; using Poco::Net::TCPServerConnectionFactoryImpl; int main() { try { string logFileName = "server.log"; FormattingChannel* channel = new FormattingChannel(new PatternFormatter("[%d-%m-%Y %H:%M:%S: %p]: %t")); channel->setChannel(new FileChannel(logFileName)); channel->open(); Logger& fileLogger = Logger::create("server", channel, Poco::Message::PRIO_INFORMATION); fileLogger.information("starting server"); Handler::_members["peter"] = "password"; Handler::_members["pierre"] = "chiffre"; Handler::_members["pera"] = "lozinka"; ServerSocket socket(7000); TCPServer server(new TCPServerConnectionFactoryImpl<ServerConnection>, socket); server.start(); while (true) ; fileLogger.information("ending server"); } catch (exception& exc) { cout << "main(): " << exc.what() << endl; } return EXIT_SUCCESS; }
Server is tested under Linux 2.6.37 64bit/gcc 4.5.2/Poco 1.4.2p1, FreeBSD 8.0 64bit/gcc 4.2.1/Poco 1.4.2p1, Windows 7/VS 2010/Poco 1.4.2p1,
compiled as specified in the Makefile
.
CC = gcc CXX = g++ INC = -I/usr/local/poco-1.4.2p1-all/include/ LFLAGS = -g LIBS = -L/usr/local/poco-1.4.2p1-all/lib/ -lPocoFoundation -lPocoNet -lPocoUtil -lPocoXML SRC = Message.cpp Session.cpp ChatLine.cpp ServerConnection.cpp Handler.cpp main.cpp SERVER = server CLIENT = client all: Makefile $(SERVER) $(CLIENT) $(SERVER): $(CXX) $(INC) $(LIBS) $(SRC) -o $(SERVER) $(CLIENT): $(CXX) $(INC) $(LIBS) client.cpp -o $(CLIENT)
The presented code can be downloaded as an archive.