From 6139b74876a5c8417d75c2759ce0baec4ae0e171 Mon Sep 17 00:00:00 2001 From: Joursoir Date: Wed, 25 Nov 2020 19:04:49 +0300 Subject: add second (work with chat) level of abstraction --- .gitignore | 3 + Makefile | 14 ---- src/const_vars.hpp | 16 ++++ src/server/Makefile | 18 +++++ src/server/chat.cpp | 196 +++++++++++++++++++++++++++++++++++++++++++++++++ src/server/chat.hpp | 60 +++++++++++++++ src/server/rooms.cpp | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ src/server/rooms.hpp | 33 +++++++++ src/server/server.cpp | 6 +- src/server/sockets.cpp | 3 +- 10 files changed, 524 insertions(+), 17 deletions(-) delete mode 100644 Makefile create mode 100644 src/const_vars.hpp create mode 100644 src/server/Makefile create mode 100644 src/server/chat.cpp create mode 100644 src/server/chat.hpp create mode 100644 src/server/rooms.cpp create mode 100644 src/server/rooms.hpp diff --git a/.gitignore b/.gitignore index aea0610..4d946e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ architecture/ +src/client/client +src/server/server +src/server/hash* *.o \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 26ef7b4..0000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -CPP = g++ -CPPFLAGS = -Wall -g -SOURCES = src/client/client.cpp -OBJECTS = client.o - -.PHONY: all clean client - -all: client - -client: - $(CPP) $(CPPFLAGS) $(SOURCES) -o client - -clean: - rm -rf src/client/client.o client \ No newline at end of file diff --git a/src/const_vars.hpp b/src/const_vars.hpp new file mode 100644 index 0000000..9c7b1c5 --- /dev/null +++ b/src/const_vars.hpp @@ -0,0 +1,16 @@ +#ifndef CONSTVARS_H +#define CONSTVARS_H + +/* + * In chat messages look like that: + * Knight385: Hello, guys! + * +*/ + +const int max_name_len = 18; +const int oneline_len = 57; +const int max_usermsg_len = oneline_len * 3 - max_name_len - 2; // ": " = 2 + // 57 * 3 - 18 - 2 = 151 +const int max_msg_len = oneline_len * 3; // 57*3 = 171 + +#endif \ No newline at end of file diff --git a/src/server/Makefile b/src/server/Makefile new file mode 100644 index 0000000..be312d7 --- /dev/null +++ b/src/server/Makefile @@ -0,0 +1,18 @@ +CPP = g++ +CPPFLAGS = -Wall -g +SOURCES = server.cpp sockets.cpp chat.cpp rooms.cpp +OBJECTS = $(SOURCES:.cpp=.o) +EXECUTABLE = server + +.PHONY: all clean + +all: $(EXECUTABLE) + +clean: + rm -rf $(OBJECTS) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CPP) $(CPPFLAGS) -o $(EXECUTABLE) $(OBJECTS) + +$(OBJECTS): + $(CPP) -c $(CPPFLAGS) $(SOURCES) \ No newline at end of file diff --git a/src/server/chat.cpp b/src/server/chat.cpp new file mode 100644 index 0000000..a9ecda1 --- /dev/null +++ b/src/server/chat.cpp @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include + +#include "chat.hpp" +#include "rooms.hpp" + +static const int qlen_for_listen = 6; + +void ChatSession::Send(const char *msg) +{ + CONSOLE_LOG("%s", msg); + write(GetFd(), msg, strlen(msg)); +} + +void ChatSession::Handle(bool r, bool w) +{ + if(!r) + return; + + // this functions create for support any client, not one WantChat client: + if(buf_used >= (int)sizeof(buffer)) { + buf_used = 0; + ignoring = true; + } + if(ignoring) { + CONSOLE_LOG("Ignore the message, it's so big\n"); + ReadAndIgnore(); + } + else + ReadAndCheck(); +} + +void ChatSession::ReadAndIgnore() +{ + int rc = read(GetFd(), buffer, sizeof(buffer)); + CONSOLE_LOG("readI return %d bytes\n", rc); + if(rc < 1) { + the_master->CloseSession(this); + return; + } + + for(int i = 0; i < rc; i++) + if(buffer[i] == '\n') + { // stop ignoring! + CONSOLE_LOG("ReadAndIgnore: find \\n\n"); + int rest = rc - i - 1; + if(rest > 0) + memmove(buffer, buffer + i + 1, rest); + buf_used = rest; + ignoring = 0; + CheckLines(); + } +} + +void ChatSession::ReadAndCheck() +{ + int rc = read(GetFd(), buffer+buf_used, sizeof(buffer)-buf_used); + CONSOLE_LOG("readC return %d bytes\n", rc); + if(rc < 1) { + the_master->LeaveMessage(this); + the_master->CloseSession(this); + return; + } + buf_used += rc; + CheckLines(); +} + +void ChatSession::CheckLines() +{ + if(buf_used <= 0) + return; + + for(int i = 0; i < buf_used; i++) { + if(buffer[i] == '\n') { + CONSOLE_LOG("[CheckLines] buffer[i] == \\n i = %d\n", i); + buffer[i] = 0; + if(i > 0 && buffer[i-1] == '\r') + buffer[i-1] = 0; + + CONSOLE_LOG("printed: %s\n", buffer); + the_master->HandleMessage(this, buffer); + + int rest = buf_used - i - 1; + memmove(buffer, buffer + i + 1, rest); + buf_used = rest; + CONSOLE_LOG("[CheckLines] new buf_used = %d\n", buf_used); + CheckLines(); + return; + } + } +} + +void ChatSession::ChangeName(const char *n_name) +{ + for(int i = 0; i < max_name_len; i++) + name[i] = 0; + + strcpy(name, n_name); + char *msg = new char[max_msg_len]; + sprintf(msg, "Now your name is %s\n", name); + + this->Send(msg); + + delete[] msg; +} + +////////////////////////////////////////////////////////////// + +Server::Server(EventSelector *sel, int fd) + : FdHandler(fd), the_selector(sel) +{ + the_selector->Add(this); + lobby = new ChatRoom(this, true); +} + +Server::~Server() +{ + if(room) + delete[] room; + + the_selector->Remove(this); +} + +Server *Server::Start(EventSelector *sel, int port) +{ + int in_d = socket(AF_INET, SOCK_STREAM, 0); + if(in_d == -1) + return 0; + + int opt = 1; + setsockopt(in_d, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + int res = bind(in_d, (struct sockaddr*) &addr, sizeof(addr)); + if(res == -1) + return 0; + + res = listen(in_d, qlen_for_listen); + if(res == -1) + return 0; + + return new Server(sel, in_d); +} + +void Server::Handle(bool r, bool w) +{ + if(!r) + return; + + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + int sd = accept(GetFd(), (struct sockaddr*) &addr, &len); + if(sd == -1) + return; + + ChatSession *s = new ChatSession(lobby, sd); + lobby->AddSession(s); + the_selector->Add(s); +} + +int Server::AddRoom() +{ + if(!room) { + room_len = 16; + room = new ChatRoom*[room_len]; + for(int i = 0; i < room_len; i++) + room[i] = 0; + } + + int id = -1; + for(int i = 0; i < room_len; i++) { + if(room[i] == 0) { + id = 0; + room[i] = new ChatRoom(this, false); + } + } + + if(id == -1) { + ChatRoom **tmp = new ChatRoom*[room_len+1]; + for(int i = 0; i < room_len; i++) { + tmp[i] = room[i]; + } + tmp[room_len] = new ChatRoom(this, false); + room_len += 1; + } + + return id; +} \ No newline at end of file diff --git a/src/server/chat.hpp b/src/server/chat.hpp new file mode 100644 index 0000000..ca22519 --- /dev/null +++ b/src/server/chat.hpp @@ -0,0 +1,60 @@ +#ifndef CHATREALIZATION_H +#define CHATREALIZATION_H + +#include "sockets.hpp" +#include "../const_vars.hpp" + +#define CONSOLE_LOG(f_, ...) printf((f_), ##__VA_ARGS__) + +class ChatRoom; +class Server; + +class ChatSession : FdHandler { + friend class Server; + + char name[max_name_len]; + char buffer[max_msg_len]; + int buf_used; + bool ignoring; + + ChatRoom *the_master; + + ChatSession(ChatRoom *i_master, int i_fd) : FdHandler(i_fd), + buf_used(0), ignoring(false), the_master(i_master) {} + ~ChatSession() {} + + virtual void Handle(bool r, bool w); + void ReadAndIgnore(); + void ReadAndCheck(); + void CheckLines(); +public: + const char *GetName() const { return name; } + + void ChangeName(const char *n_name); + void Send(const char *msg); +}; + +class Server : public FdHandler { + EventSelector *the_selector; + ChatRoom **room; + ChatRoom *lobby; + + int room_len; + + Server(EventSelector *sel, int fd); +public: + ~Server(); + + static Server *Start(EventSelector *sel, int port); + + int AddRoom(); + // RemoveRoom(); + // void AddSessionToRoom(ChatSession *s, int id); + void CloseConnection(ChatSession *s) + { the_selector->Remove(s); delete s; } +private: + virtual void Handle(bool r, bool w); +}; + + +#endif \ No newline at end of file diff --git a/src/server/rooms.cpp b/src/server/rooms.cpp new file mode 100644 index 0000000..624eb88 --- /dev/null +++ b/src/server/rooms.cpp @@ -0,0 +1,192 @@ +#include +#include + +#include "rooms.hpp" +#include "chat.hpp" + +// hash of commands: +#define CMD_NAME 210647350421 +#define CMD_CREATE 229394553991880 + +const int cmd_num = 2; +const char *commands[cmd_num] = { + "/name", + "/create" + // /rooms - print all public rooms +}; + +char **ParseCommand(const char *input, int &arrc); +unsigned long hash(const char *str); + +ChatRoom::~ChatRoom() +{ + while(first) { + item *tmp = first; + first = first->next; + the_server->CloseConnection(tmp->s); + delete tmp; + } +} + +void ChatRoom::SendAll(const char *msg, ChatSession *except) +{ + CONSOLE_LOG("Send message all: %s\n", msg); + item *p; + for(p = first; p; p = p->next) + if(p->s != except) + p->s->Send(msg); +} + +void ChatRoom::LeaveMessage(ChatSession *except) +{ + if(it_lobby) + return; + + const char left_msg[] = " has left the chat"; + const char *name = except->GetName(); + + int len = strlen(name); + char *lmsg = new char[len + sizeof(left_msg) + 2]; + sprintf(lmsg, "%s%s\n", name, left_msg); + this->SendAll(lmsg, except); + delete[] lmsg; +} + +void ChatRoom::HandleMessage(ChatSession *ses, const char *str) +{ + if(str[0] == '/') { // if user sent a command + int argc = 0; + char **argv = 0; + argv = ParseCommand(str, argc); + + this->HandleCommand(ses, argc, argv); + } + else if(!it_lobby) { + char *msg = new char[max_msg_len]; + sprintf(msg, "%s: %s\n", ses->GetName(), str); + + this->SendAll(msg); + + delete[] msg; + } +} + +void ChatRoom::HandleCommand(ChatSession *s, int cmd_counter, + char **commands) +{ + unsigned long cmd = -1; + cmd = hash(commands[0]); + + switch(cmd) + { + case CMD_NAME: { + if(!it_lobby) { + s->Send("You can use this command only in lobby\n"); + break; + } + + if(cmd_counter == 1) { + s->Send("Usage: /name your_good_name\n"); + break; + } + + int len = strlen(commands[1]); + if(len > max_name_len || len < 3) { + s->Send("Incorrect name. Name length from 3 to 18 chars\n"); + break; + } + + s->ChangeName(commands[1]); + break; + } + case CMD_CREATE: { + if(!it_lobby) { + s->Send("You can use this command only in lobby\n"); + break; + } + if(!s->GetName()) { + s->Send("Before you can use this command, use /name\n"); + break; + } + + //int id = the_s->AddRoom(); + //the_s->AddSessionToRoom(s, id); + + break; + } + + default: { + s->Send("Unknown command. Use: /help\n"); + break; + } + } +} + +void ChatRoom::AddSession(ChatSession *ses) +{ + item *p = new item; + p->next = first; + p->s = ses; + first = p; +} + +void ChatRoom::RemoveSession(ChatSession *ses) +{ + item **p; + for(p = &first; *p; p = &((*p)->next)) { + if( ((*p)->s) == ses ) { + item *tmp = *p; + *p = tmp->next; + // not delete ChatSession! + delete tmp; + return; + } + } +} + +void ChatRoom::CloseSession(ChatSession *ses) +{ + this->RemoveSession(ses); + the_server->CloseConnection(ses); +} + +////////////////////////////////////////////////////////////// + +char **ParseCommand(const char *input, int &arrc) +{ + int max_argv = 5; + arrc = 0; + char **arr = new char*[max_argv]; + + int start = 0; + for(int i = 0; i < (int) strlen(input)+1; i++) { + if(input[i] == ' ' || input[i] == '\0' || input[i] == '\n') { // end + if(start == -1) + continue; + + int size = i - start; + arr[arrc] = new char[size]; + memcpy(arr[arrc], input + start, sizeof(char) * size); + + start = -1; + arrc++; + if(arrc == max_argv) + break; + } + else if(start == -1) + start = i; + } + + return arr; +} + +unsigned long hash(const char *str) +{ + unsigned long hash = 5381; + char c; + + while( (c = *str++) ) + hash = ((hash << 5) + hash) + c; + + return hash; +} \ No newline at end of file diff --git a/src/server/rooms.hpp b/src/server/rooms.hpp new file mode 100644 index 0000000..08fffff --- /dev/null +++ b/src/server/rooms.hpp @@ -0,0 +1,33 @@ +#ifndef ROOMREALIZATION_H +#define ROOMREALIZATION_H + +class Server; +class ChatSession; + +class ChatRoom { + Server *the_server; + const bool it_lobby; + + struct item { + ChatSession *s; + item *next; + }; + item *first; +public: + ChatRoom(Server *i_server, bool i_lobby) + : the_server(i_server), it_lobby(i_lobby), first(0) {} + ~ChatRoom(); + + void SendAll(const char *msg, ChatSession *except = 0); + void LeaveMessage(ChatSession *except); + + void HandleMessage(ChatSession *ses, const char *str); + void HandleCommand(ChatSession *s, int cmd_counter, + char **commands); + + void AddSession(ChatSession *ses); + void RemoveSession(ChatSession *ses); + void CloseSession(ChatSession *ses); +}; + +#endif \ No newline at end of file diff --git a/src/server/server.cpp b/src/server/server.cpp index 6834ceb..ab7c420 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -2,6 +2,8 @@ #include "chat.hpp" +const int port = 3030; + int main(int argc, char *argv[]) { /* Event-driven programming */ @@ -10,11 +12,11 @@ int main(int argc, char *argv[]) 2) event handling */ EventSelector *selector = new EventSelector; - /*ChatServer *serv = ChatServer::Start(selector, port); + Server *serv = Server::Start(selector, port); if(!serv) { perror("server"); return 1; - }*/ + } selector->Run(); return 0; } \ No newline at end of file diff --git a/src/server/sockets.cpp b/src/server/sockets.cpp index 1a52786..7c995f0 100644 --- a/src/server/sockets.cpp +++ b/src/server/sockets.cpp @@ -1,4 +1,5 @@ #include +#include #include "sockets.hpp" @@ -78,7 +79,7 @@ void EventSelector::Run() { exit_flag = false; do { - int res = poll(fds, max_fd, -1); + int res = poll(fds, max_fd+1, -1); if(res < 0) { if(errno == EINTR) -- cgit v1.2.3-18-g5258