TCP server with dispatcher
In order to handle sockets quickly, accepting new connections should be done in a separate thread. The accepted connections are stored in a queue and processed each in its own thread. As an example of such approach, a chat server is constructed. The given code is written using POCO library.
Each TCP connection is observed as a session with ID, status and corresponding socket. Session status is determined on both TCP and processor level (for instance, if user logouts, then the session must be destroyed).
#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 }; 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 "Session.hpp" 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; }
Client request and server response are chat lines with a content. Each chat line contains a command which determines does it login, logout, receive or send a chat. With each command, other specific parameters are given in the format of URL query string.
#ifndef CHATLINE_HPP #define CHATLINE_HPP #include <string> #include <map> using std::string; using std::map; /** 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 = "=";
Message is the basic element of keeping data, related to a particular session. Each request/response is transformed into a message.
#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; }
Dispatcher keeps new connections in a queue. When one is ready to read, dispatcher handles it in a separate thread.
#ifndef DISPATCHER_HPP #define DISPATCHER_HPP #include <deque> #include <Poco/Runnable.h> #include <Poco/Mutex.h> #include <Poco/ThreadPool.h> #include <Poco/Condition.h> #include "Message.hpp" using std::deque; using Poco::Runnable; using Poco::FastMutex; using Poco::ThreadPool; using Poco::Condition; /** Determines clients ready to handle. **/ class Dispatcher : public Runnable { public: static const short CHUNK_SIZE = 50; Dispatcher(); void run(); void enqueue(Message* message); private: Condition _condQueue; FastMutex _mutexQueue; deque<Message*> _queue; void dequeue(map<StreamSocket, Message*>& messages); }; #endif // DISPATCHER_HPP
#include <Poco/Net/Socket.h> #include <Poco/Net/StreamSocket.h> #include <Poco/Exception.h> #include <Poco/Net/NetException.h> #include <Poco/Logger.h> #include <Poco/Format.h> #include <Poco/Timespan.h> #include "Dispatcher.hpp" #include "Handler.hpp" using Poco::Logger; using Poco::NoThreadAvailableException; using Poco::Net::ConnectionResetException; using Poco::Net::Socket; using Poco::Net::StreamSocket; using Poco::Timespan; Dispatcher::Dispatcher() { } void Dispatcher::run() { Logger& log = Logger::get("server"); const Timespan TIMEOUT(0, 100000); while (true) { map<StreamSocket, Message*> messages; dequeue(messages); for (map<StreamSocket, Message*>::iterator it = messages.begin(); it != messages.end(); ++it) { if (it->first.poll(TIMEOUT, Socket::SELECT_READ)) { try { Message* msg = messages[it->first]; Handler* hnd = new Handler(msg); // if no thread is available, then put into queue and try later try { log.debug("Dispatcher::run(): starting processor for session " + Poco::format("%ld", msg->session()->id())); ThreadPool::defaultPool().start(*hnd); } catch (NoThreadAvailableException& exc) { log.debug("Dispatcher::run(): cannot start processor for session " + Poco::format("%ld", msg->session()->id())); enqueue(msg); delete hnd; } } catch (ConnectionResetException& exc) { log.debug("Dispatcher::run(): connection reset"); } } else { log.debug("Dispatcher::run(): socket not readable, enqueuing it"); enqueue(it->second); } } } } void Dispatcher::enqueue(Message* message) { FastMutex::ScopedLock lock(_mutexQueue); _queue.push_back(message); _condQueue.signal(); } void Dispatcher::dequeue(map<StreamSocket, Message*>& messages) { FastMutex::ScopedLock lock(_mutexQueue); while (_queue.empty()) _condQueue.wait(_mutexQueue); for (short i = 1; i <= CHUNK_SIZE; ++i) if (!_queue.empty()) { Message* msg = _queue.front(); _queue.pop_front(); messages[*(msg->session()->socket())] = msg; } else break; }
Handlers deals with a single connection until it's end.
#ifndef HANDLER_CPP #define HANDLER_CPP #include <string> #include <vector> #include <map> #include <Poco/Runnable.h> #include <Poco/Mutex.h> #include "Message.hpp" #include "Server.hpp" using std::string; using std::vector; using std::map; using Poco::Runnable; using Poco::FastMutex; /** Handles a client from the beginning until the end of session. **/ class Handler : public Runnable { 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(Message* message); ~Handler(); void run(); /** Username and password for each registered user. **/ static map<string, string> _members; private: static const short BUF_SIZE = 128; bool receive(); bool send(); void process(); void removeUser(long sessionId); Message* _message; static FastMutex _mutexUsers; /** Maps session to username. **/ static map<long, string> _users; static FastMutex _mutexInbox; /** Inbox with messages for a specific user sent by an user. **/ static map<string, vector<From> > _inbox; }; #endif // HANDLER_CPP
#include <Poco/Logger.h> #include <Poco/Format.h> #include <Poco/Thread.h> #include <Poco/Net/NetException.h> #include "Handler.hpp" using Poco::Thread; using Poco::Logger; using Poco::Net::ConnectionResetException; map<string, string> Handler::_members; FastMutex Handler::_mutexUsers; map<long, string> Handler::_users; FastMutex Handler::_mutexInbox; map<string, vector<Handler::From> > Handler::_inbox; Handler::Handler(Message* message) : _message(message) { } Handler::~Handler() { } void Handler::run() { Logger& log = Logger::get("server"); try { while (true) { if (receive()) break; process(); if (send()) break; } } catch (ConnectionResetException& exc) { log.debug("Handler::run(): connection reset"); } delete this; } bool Handler::receive() { bool done = false; Logger& log = Logger::get("server"); if (_message->session()->status() != Session::DONE) { log.debug("Handler::receive(): reading session " + Poco::format("%ld", _message->session()->id())); char buf[BUF_SIZE]; int result = _message->session()->socket()->receiveBytes(reinterpret_cast<void*>(buf), BUF_SIZE - 1); if (result == 0) { _message->session()->status(Session::DONE); log.debug("Handler::receive(): cannot read session " + Poco::format("%ld", _message->session()->id())); } else { buf[result] = '\0'; string request = buf; ChatLine* line = new ChatLine; line->parse(request); _message->chatLine(line); log.debug("Handler::receive(): message processed"); } } else { Message::destroy(_message); log.information("Handler::receive(): destroying session " + Poco::format("%ld", _message->session()->id())); done = true; } return done; } bool Handler::send() { bool done = false; Logger& log = Logger::get("server"); if (_message->session()->status() == Session::DONE) { log.information("Handler::send(): session " + Poco::format("%ld", _message->session()->id()) + " destroyed"); Message::destroy(_message); done = true; } else { string response = _message->chatLine()->format(); int result = _message->session()->socket()->sendBytes(reinterpret_cast<const void*>(response.c_str()), response.size()); if (result == 0) { removeUser(_message->session()->id()); log.information("Handler::send(): cannot send, session " + Poco::format("%ld", _message->session()->id()) + " destroyed"); Message::destroy(_message); } } return done; } void Handler::process() { Logger& log = Logger::get("server"); log.debug("Handler::process(): processing session " + Poco::format("%ld", _message->session()->id())); if (_message->session()->status() == Session::DONE) { removeUser(_message->session()->id()); log.debug("Handler::process(): session done, removing user"); } // 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::process(): session=" + 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::process(): session=" + 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::process(): session=" + Poco::format("%ld", _message->session()->id()) + ", cannot login"); _message->chatLine()->command("cannot login"); } } else if (_message->chatLine()->command() == "logout") { log.debug("Handler::process(): session=" + 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::process(): session=" + 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::process(): session=" + 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::process(): session=" + 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::process(): session=" + 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::process(): session=" + 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::process(): session=" + Poco::format("%ld", _message->session()->id()) + ", receive ok, from=" + Poco::format("%s", fm.from) + ", chat=" + Poco::format("%s", fm.chatLine)); } } else { log.debug("Handler::process(): session=" + 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::process(): session=" + 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(); }
Server accepts new connections and enqueues them so the dispatcher could process them.
#ifndef SERVER_HPP #define SERVER_HPP #include <map> #include <deque> #include <vector> #include <Poco/Net/Socket.h> #include <Poco/Net/ServerSocket.h> #include <Poco/Runnable.h> #include <Poco/Mutex.h> #include <Poco/ThreadPool.h> #include "Dispatcher.hpp" using Poco::Net::Socket; using Poco::Net::ServerSocket; using Poco::Runnable; using Poco::FastMutex; using Poco::ThreadPool; /** Accepts new clients. **/ class Server : public Runnable { public: Server(Dispatcher* dispatcher); void run(); private: static const short PORT = 7000; static const short BUF_SIZE = 128; ServerSocket _serverSocket; Dispatcher* _dispatcher; }; #endif // SERVER_HPP
#include <Poco/Net/Socket.h> #include <Poco/Net/StreamSocket.h> #include <Poco/Logger.h> #include <Poco/Format.h> #include <Poco/Timespan.h> #include "Server.hpp" #include "ChatLine.hpp" using Poco::Net::Socket; using Poco::Net::StreamSocket; using Poco::Logger; using Poco::Timespan; Server::Server(Dispatcher* dispatcher) : _serverSocket(PORT), _dispatcher(dispatcher) { _serverSocket.listen(); Logger& log = Logger::get("server"); log.debug("Server::Server(): listening"); } void Server::run() { Logger& log = Logger::get("server"); const Timespan TIMEOUT(0, 100000); while (true) { if (_serverSocket.poll(TIMEOUT, Socket::SELECT_READ)) { StreamSocket client = _serverSocket.acceptConnection(); Message* msg = Message::create(new StreamSocket(client)); msg->session()->status(Session::NEW); log.information("Server::run(): session " + Poco::format("%ld", msg->session()->id()) + " created"); _dispatcher->enqueue(msg); } } }
Server is started inside main function.
/* 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/ThreadPool.h> #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 "Server.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; 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"; Dispatcher dispatcher; ThreadPool::defaultPool().start(dispatcher); Server server(&dispatcher); ThreadPool::defaultPool().start(server); while (true) ; fileLogger.information("ending server"); } catch (exception& exc) { cout << "main(): " << exc.what() << endl; } return EXIT_SUCCESS; }
To test the server, telnet could be used or a client similar to this one.
#include <iostream> #include <string> #include <cstring> #include <vector> #include <utility> #include <cstdlib> #include <exception> #include <cassert> #include <Poco/Exception.h> #include <Poco/Net/StreamSocket.h> #include <Poco/Net/SocketAddress.h> #include <Poco/ThreadPool.h> #include <Poco/Thread.h> #include <Poco/Runnable.h> #include <Poco/Mutex.h> using std::cout; using std::endl; using std::string; using std::vector; using std::pair; using std::make_pair; using std::exception; using Poco::Exception; using Poco::NoThreadAvailableException; using Poco::Net::StreamSocket; using Poco::Net::SocketAddress; using Poco::ThreadPool; using Poco::Thread; using Poco::Runnable; using Poco::Mutex; /** Makes random requests to the server. **/ class Client : public Runnable { public: Client() { _commands.push_back("login"); _commands.push_back("logout"); _commands.push_back("send"); _commands.push_back("receive"); _commands.push_back("foobar"); _users.push_back(make_pair("peter", "password")); _users.push_back(make_pair("pierre", "chiffre")); _users.push_back(make_pair("pera", "lozinka")); _users.push_back(make_pair("foo", "bar")); _chats.push_back("hello world"); _chats.push_back("foo bar"); _chats.push_back("yabadabadoo"); _chats.push_back("opa bato"); _chats.push_back("yeah"); _chats.push_back("wow"); _chats.push_back("bye"); } void run() { StreamSocket client(SocketAddress("localhost:7000")); _mutex.lock(); int userNo = rand() % _users.size(); pair<string, string> userPass = _users[userNo]; _mutex.unlock(); string request = "command=login&username=" + userPass.first + "&password=" + userPass.second; int len = client.sendBytes(request.c_str(), request.length()); assert(len == request.length()); if (len == 0) { client.close(); return; } const short RESPONSE_LEN = 128; char response[RESPONSE_LEN]; ::memset((void*)(response), '\0', RESPONSE_LEN); len = client.receiveBytes((void*)(response), RESPONSE_LEN - 2); if (len == 0) { client.close(); return; } response[len] = '\0'; unsigned int counter = 0; while (counter++ < 50) { _mutex.lock(); int commandNo = rand() % _commands.size(); string command = _commands[commandNo]; int userNo = rand() % _users.size(); pair<string, string> userPass = _users[userNo]; _mutex.unlock(); if (command == "send") { _mutex.lock(); int userTo = rand() % _users.size(); int chatNo = rand() % _chats.size(); request = "command=send&to=" + _users[userTo].first + "&chat=" + _chats[chatNo] + "&from=" + userPass.first; _mutex.unlock(); } else if (command == "receive") request = "command=receive"; else if (command == "foobar") request = "command=foobar"; int len = client.sendBytes(request.c_str(), request.length()); assert(len == request.length()); if (len == 0) { client.close(); return; } const short RESPONSE_LEN = 128; char response[RESPONSE_LEN]; ::memset((void*)(response), '\0', RESPONSE_LEN); len = client.receiveBytes((void*)(response), RESPONSE_LEN - 2); if (len == 0) { client.close(); return; } response[len] = '\0'; } request = "command=logout"; len = client.sendBytes(request.c_str(), request.length()); assert(len == request.length()); if (len == 0) { client.close(); return; } ::memset((void*)(response), '\0', RESPONSE_LEN); len = client.receiveBytes((void*)(response), RESPONSE_LEN - 2); if (len == 0) { client.close(); return; } response[len] = '\0'; } private: Mutex _mutex; vector<string> _commands; vector<pair<string, string> > _users; vector<string> _chats; }; int main() { int counter = 0; while (counter++ < 50) { ThreadPool pool(1, 100); Client c; try { for (int j = 1; j <= 100; j++) pool.start(c); pool.joinAll(); } catch (NoThreadAvailableException& exc) { cout << "main(): NoThreadAvailableException=" << exc.what() << ", pool.used()=" << pool.used() << endl; } catch (Exception& exc) { cout << "main(): Exception=" << exc.what() << ", pool.used()=" << pool.used() << endl; } cout << "main(): finishing: counter=" << counter << 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 Server.cpp Handler.cpp Dispatcher.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.