CSIS 440 chat client/server project.

SircServer.cpp 13KB

    /* Author: Matt Kava Asmt: CSIS 440, Spring 2010, Assignment 6 :: IRC-Like Client/Server Description: Sirc Server class implementation - The server processes */ #include "SircServer.h" #include "User.h" #include <vector> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <unistd.h> #include <string> #include <iostream> #include <ctype.h> using namespace std; SircServer::SircServer() { _wkPort = DEFAULT_WKS_PORT; _maxConnectedUsersCount = MAX_USER_COUNT; _bufferSize = BUFFER_SIZE; _maxNickLength = MAX_NICK_LENGTH; _EOL = DEFAULT_EOL; _maxMessageLength = MAX_MESSAGE_LENGTH; _connectedUsersCount = 0; _wkSocket = -1; FD_ZERO(&_activeFDset); for(int i = 0; i < MAX_USER_COUNT; i++) { Users()[i] = new User(); } } SircServer::SircServer(unsigned short wkport, int maxConnectedUsers) { _wkPort = wkport; _maxConnectedUsersCount = maxConnectedUsers; _bufferSize = BUFFER_SIZE; _maxNickLength = MAX_NICK_LENGTH; _EOL = DEFAULT_EOL; _maxMessageLength = MAX_MESSAGE_LENGTH; _connectedUsersCount = 0; _wkSocket = -1; FD_ZERO(&_activeFDset); for(int i = 0; i < maxConnectedUsers; i++) { Users()[i] = new User(); } } SircServer::~SircServer() { // due to use of an array... no known maximum size of Users to delete } int SircServer::Startup() { // create the well-known socket if((_wkSocket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { // error occured... return 1 return 1; } // bind the wkPort to the socket sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(_wkPort); if( (bind(_wkSocket, (struct sockaddr*) &addr, sizeof(addr))) < 0 ) { // error occured, return 2 return 2; } // turn the socket to passive using listen listen(_wkSocket, QUEUE_LENGTH); // add a new socketfd to the active fd set FD_SET(_wkSocket, &_activeFDset); // add stdin to the active fd set FD_SET(0, &_activeFDset); _readyToRun = true; // wkSocket is now ready for Accept // no problem, return 0 return 0; } int SircServer::Shutdown() { // for_each connected client for(int i = 0; i < MaxConnectedUsersCount(); i++) { if(Users()[i] != NULL && Users()[i]->Validated()) { // send shutdown message and... Users()[i]->Send( FormatMessage("Server is shutting down...") ); // destroy the User (ungraceful disconnect) delete Users()[i]; Users()[i] = new User(); } } } void SircServer::Run() { if( ! ReadyToRun() ) { cerr << "Run() :: This server is not ready to run. Must do Startup() first!\n"; return; } // set the maximum number of sockets to the maximum connected users count // include two more, one for wksocket and another for stdin _numberFD = MaxConnectedUsersCount() + 2; // the file descriptor set to read fd_set readFDSet; // the never ending loop of execution... oh, the madness! while(1) { // copy the active list of file descriptors into the set of file descriptors to watch with SELECT memcpy(&readFDSet, &_activeFDset, sizeof(readFDSet)); cout << "Connected User Count [" << ConnectedUsersCount() << "/" << MaxConnectedUsersCount() << "]\n"; // start the blocking SELECT call that monitors all socketfds in the read fd set if( select(_numberFD, &readFDSet, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0) { // select error cout << "Select() :: Select error.\n"; } // check stdin if(FD_ISSET(0, &readFDSet)) { // this cannot handle control characters, such as Ctrl+D (^D or End-of-transmission character). // stdin will always appear to be busy and the select will keep popping... // stdin has something // get the line off of stdin string stdin = ""; getline(cin, stdin); // Close the server if 'die' or 'DIE' if(stdin == "die" || stdin == "DIE") { cout << "Received DIE command from keyboard. Terminating.\n"; return; } else { cout << "From stdin: \'" << stdin << "\'\n"; } } // loop through sockets for(int fd = 3; fd < _numberFD+3; fd++) { if(FD_ISSET(fd, &readFDSet)) { // if wk socket... if(fd == _wkSocket) { // this is on the well-known socket.. handle new connection AcceptNew(fd); } // its a connected user else { // it's not on the well-known socket.. must be a client HandleUser( Users(fd) ); } } } for(int i = 0; i < MaxConnectedUsersCount(); i++) { if(Users()[i] != NULL && Users()[i]->Validated()) { cerr << "\tNick: " << Users()[i]->Nick() << " [" << Users()[i]->SockFD() << "] - " << Users()[i]->IP() << ":" << Users()[i]->Port() << endl; } } } } void SircServer::HandleUser(User* user) { if(user == NULL) { return; } // get the message string msg = user->Recv(); // Cases: // 1) EOF // 2) Not validated, it's a nick // 3) Validated, it's a message // 1) EOF // NUL or ^D or ^C or ^Z if(msg == "\0" || msg == "\x04" || msg == "\x03" || msg == "\x1A") { // the message says the client is disconnecting.. // send a quit message to everyone about the User // send this message ONLY if it the user is validated // make the message... string quit = "QUIT:" + user->Nick() + ":" + user->IP() + ":" + user->Port() + _EOL; bool validated = user->Validated(); // remove the user and disconnect them (this is done prior to sending the message, in case the socket is broken now...) RemoveUser(user); // send it if(validated) { BroadcastMessage( FormatMessage(quit) ); } return; } if(msg.size() == 1) { // this means the message is empty.. don't forward it or handle it further return; } // 3) Message // if the user has been validated, it must be a message if(user->Validated()) { // purify it msg = Purify(msg); msg = user->Nick() + ":" + user->IP() + ":" + user->Port() + ":" + msg; cerr << "@User Message: \'" << msg << "\'\n"; // broadcast it BroadcastMessage( FormatMessage(msg) ); } // 2) Nick // if the user is not validated, it must be a nick else { // purify the nick msg = Purify(msg, true); // get the nick // truncate it, if needed if(msg.size() > _maxNickLength) { // notify the user that the nick is invalid user->Send( FormatMessage("Provided nick is invalid (too long). Please try again.") ); return; } else if(msg.size() == 0) { // notify the user that the nick is invalid user->Send( FormatMessage("Provided nick is invalid (too short). A nickname must have a length greater than 0. Please try again.") ); return; } string nick = msg; // check if it is valid // method returns TRUE if it is valid, false it not if( !CompareAllNicks(nick) ) { // notify the user that the nick is invalid user->Send( FormatMessage("Provided nick is invalid (likely in use). Please try again.") ); return; } // if valid, set it to the Nick of the user and validate the user user->Nick(nick); user->Validated(true); // load the IP and Port for the user user->LoadIpPort(); // broadcast a JOIN message to everyone for the new client string join = "JOIN:" + user->Nick() + ":" + user->IP() + ":" + user->Port() + _EOL; BroadcastMessage( FormatMessage(join) ); // tell the new client all of the other clients through JOINs for(int i = 0; i < MaxConnectedUsersCount(); i++) { if(Users()[i] != user && Users()[i]->Validated()) { user->Send(join = "JOIN:" + Users()[i]->Nick() + ":" + Users()[i]->IP() + ":" + Users()[i]->Port() + _EOL); } } cerr << "New User: " << user->Nick() << " from " << user->IP() << ":" << user->Port() << endl; } } void SircServer::BroadcastMessage(string message) { message = Purify(message); // for_each connected client for(int i = 0; i < ConnectedUsersCount(); i++) { if( Users()[i] != NULL && Users()[i]->SockFD() > -1) Users()[i]->Send( FormatMessage(message) ); } } void SircServer::AcceptNew(int masterSocket) { // make a new User (do NOT add it to user list yet) User* user = new User(); struct sockaddr_in clientAddr; memset(&clientAddr, 0, sizeof(clientAddr)); // accept the connection int slaveSocket = accept(masterSocket, NULL, 0); if(slaveSocket < 0) { cerr << "AcceptNew(int) :: Bad socket fd.\n"; return; } // set the sockfd on the User to the new socket user->SockFD(slaveSocket); // check if max user count has been reached // if reached, send -Full- message, kill user, and return if( ConnectedUsersCount() >= MaxConnectedUsersCount() ) { cerr << "AcceptNew(int) :: Full server.\n"; user->Send( FormatMessage("Server is full. Disconnecting.") ); delete user; return; } // add new user to user list, its a valid user now.. not in chat, but connected AddUser(user); } string SircServer::Purify(string orig, bool isNickname) { string pure = ""; if(!isNickname) { // purify a message int size = orig.size(); for(int i = 0; i < size; i++) { int c = (int)orig[i]; if( c >= LOWEST_VALID_ASCII && c <= HIGHEST_VALID_ASCII ) { pure += orig[i]; } } } else { // purify a Nickname int size = orig.size(); for(int i = 0; i < size; i++) { int c = (int)orig[i]; // 0-9 if( c >= 48 && c <= 57 ) { pure += orig[i]; } // A-Z else if( c >= 65 && c <= 90 ) { pure += orig[i]; } //. a-z else if( c >= 97 && c <= 122 ) { pure += orig[i]; } } } return pure; } string SircServer::FormatMessage(string orig) { // search original for EOL characters // if not present, tack it on the end. // If the length will be too long (greater than _maxMessageLength), replace from the end as necessary.... (This should NOT happen) string::size_type loc = orig.find(_EOL); if(loc == string::npos) { orig += _EOL; } // truncate the message if needed... this method can be used in various locations // this is just a safety precaution if(orig.size() > MaxMessageLength()) { orig = orig.substr(0, MaxMessageLength()); } return orig; } void SircServer::AddUser(User* user) { if(user == NULL) return; int loc = -1; // find the first available position in the container for the new user to go into for(int i = 0; i < MaxConnectedUsersCount(); i++) { if( Users()[i] != NULL && Users()[i]->SockFD() < 0 ) { loc = i; break; } // if there is a NULL in the userlist, fix it else if( Users()[i] == NULL) { Users()[i] = new User(); } } // add new user to user list, its a valid user now.. not in chat, but connected Users()[loc] = user; // add sockfd to active FD list, for same reason FD_SET(user->SockFD(), &_activeFDset); // increment ConnectedUsersCount _connectedUsersCount++; } void SircServer::RemoveUser(User* user) { int sockfd = user->SockFD(); int pos = -1; for(int i = 0; i < MaxConnectedUsersCount(); i++) { if( Users()[i] != NULL && Users()[i]->SockFD() == sockfd) { pos = i; break; } } // if no user with that SockFD was found, return if(pos == -1) return; // remove sockfd to active FD list FD_CLR(user->SockFD(), &_activeFDset); // remove user from user list delete Users()[pos]; // use the default constructor on it so there is no NULL in the user list Users()[pos] = new User(); // decrement ConnectedUsersCount _connectedUsersCount--; } bool SircServer::CompareAllNicks(string nick) { if(ConnectedUsersCount() == 1) true; int size = nick.size(); // search through all possible users for(int i = 0; i < MaxConnectedUsersCount(); i++) { // if they have the same size, they may be the same! if( size == Users()[i]->Nick().size() ) { string nick2 = Users()[i]->Nick(); string ourNick = ""; string theirNick = ""; // convert all the characters in the names to uppercase.. // uppercase removes any chance that "Bob" and "bob" can be allowed // at the same time, which is the desired effect. for(int j = 0; j < size; j++) { ourNick += (char)toupper( (int)nick[j] ); theirNick += (char)toupper( (int)nick2[j] ); } // if the nicks are the same.. the nick isn't valid.. if(ourNick == theirNick) { return false; } } } // no nicks matched, the nickname is valid return true; } /** Public Methods that deal with Private Members **/ User** SircServer::Users() { return _users; } User* SircServer::Users(int sockfd) { User* usr = NULL; // search through all the possible users that has the requested sockfd for(int i = 0; i < MaxConnectedUsersCount(); i++) { if( sockfd == Users()[i]->SockFD() ) { // found the user, set the pointer and stop looking // you always find what you are looking for in the last place you look, right? usr = Users()[i]; break; } } return usr; } void SircServer::WkPort(unsigned short newPort) { _wkPort = newPort; } unsigned short SircServer::WkPort() { return _wkPort; } int SircServer::ConnectedUsersCount() { return _connectedUsersCount; } int SircServer::MaxConnectedUsersCount() { return _maxConnectedUsersCount; } int SircServer::MaxNickLength() { return _maxNickLength; } int SircServer::BufferSize() { return _bufferSize; } string SircServer::EOL() { return _EOL; } int SircServer::MaxMessageLength() { return _maxMessageLength; } bool SircServer::ReadyToRun() { return _readyToRun; } /** END Public Methods that deal with Private Members **/