/* * NMSTL, the Networking, Messaging, Servers, and Threading Library for C++ * Copyright (c) 2002 Massachusetts Institute of Technology * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include "chatproto.h" using namespace nmstl; using namespace std; // N.B.: Read through chatclient.cc first! // The event loop that will dispatch all network and terminal I/O events. io_event_loop loop; // A handler for terminal input and output. term_handler term(loop, "chatserver> "); // The password that clients need to know to log in to // the server. const char *the_password = "foobar"; /* * A handler for a single connection to a chat client. * * In this application, chat_server clients are created * by the tcp_acceptor handler (see the main() * function below). * */ class chat_server : public msg_handler { // A set of *all* the chat_server objects with authenticated // users. Static, so it's shared between all chat_server objects. static set chats; // The username of the connected user, if he's authenticated // (otherwise, blank). string user; public: chat_server(io_event_loop& loop, iohandle ioh) : msg_handler(loop, ioh) { term << "Accepted connected from " << get_socket().getpeername() << " on " << get_socket().getsockname() << endl; } ~chat_server() { if (user.empty()) { term << "Unauthenticated user has logged out." << endl; } else { chats.erase(this); term << "User " << user << " has logged out." << endl; shout("system", user + " has left the building."); } } // Shout: write a message to all authenticated users. // user is the name of the user *sending* the message. void shout(string user, string msg) { for (set::iterator i = chats.begin(); i != chats.end(); ++i) (*i)->write(SHOUT, omessage() << user << msg); } protected: // Handle an incoming message. void incoming_message(const chat_header& header, constbuf data) { // The only message we allow from a non-logged-in user // is LOGIN. if (user.empty() && header.type != LOGIN) return; switch (header.type) { case LOGIN: { // Already authenticated! Skip it. if (!user.empty()) break; string try_user; string try_password; // Read in the user and password provided by the client. imessage(data) >> try_user >> try_password; if (!try_user.empty() && try_password == the_password) { // Non-empty username and matching password. // Let 'em in! user = try_user; chats.insert(this); // Print a status message to the terminal. term << "User " << user << " has logged in" << endl; // Write a positive acknowledgement to the remote // user. write(chat_header(AUTH_ACK)); shout("system", "Everyone say hello to " + user); } else { // Nope! Get lost. term << "Failed login attempt from user " << try_user << endl; write(chat_header(AUTH_NAK)); } return; } case SHOUT: { string msg; if (!(imessage(data) >> msg)) goto bad_message; term << "User " << user << " shouts: \"" << msg << "\"" << endl; shout(user, msg); return; } case WHISPER: { string target; string msg; if (!(imessage(data) >> target >> msg)) goto bad_message; term << "User " << user << " whispers to " << target << ": \"" << msg << "\"" << endl; for (set::iterator i = chats.begin(); i != chats.end(); ++i) if ((*i)->user == target) { (*i)->write(WHISPER, omessage() << user << msg); return; } // No such user! write(WHISPER, omessage() << "system" << target + " is not logged in."); return; } } return; bad_message: term << " - ill-formed message" << endl; } }; set chat_server::chats; int main() { // Listen for connections on port 8002. Whenever a connection // is accepted, tcp_acceptor basically does // // new chat_server(loop, some_iohandle); // // (The chat_server object will be automatically deleted when // the connection closes or the event loop terminates.) tcp_acceptor< chat_server > server(loop, 8002); term << "Welcome. Listening on port 8002. Type \"help\" for help." << endl; // Run the event loop. Returns only when // io_event_loop::terminate is invoked (because the // user types "quit" or "exit" or control-D). loop.run(); }