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.
volatile vs. volatile
What does the volatile keyword mean? How should you use it? Confusingly, there are two common answers, because depending on the language you use volatile supports one or the other of two different programming techniques: lock-free programming, and dealing with 'unusual' memory.
Read more
volatile: The Multithreaded Programmer's Best Friend
The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events.
Read more
Software and the Concurrency Revolution
Concurrency has long been touted as the "next big thing" and "the way of the future," but for the past 30 years, mainstream software development has been able to ignore it. Our parallel future has finally arrived: new machines will be parallel machines, and this will require major changes in the way we develop software.
Read more
