#include<iostream>
#include<list>
#include<map>
#include<queue>
#include<cstdlib>
#include<thread>
#include<algorithm>
#include<mutex>
#include<netinet/in.h>
#include<sys/socket.h>
#include <sys/types.h>
#include <fstream>
#include<vector>
#include <unistd.h>
#include <errno.h>
#define MAX 256
using namespace std;
/*
Struktura opisująca wątek klienta
*/
struct client_threads{
int id_soc; // Numer gniazda
int id_user; // Numer użytkownika
struct sockaddr_in addrx; // Adres Gniazda
};
/*
Struktura opisująca wiadomosc przesyłaną miedzy klientem a serwerem.
Podczas przesyłu znakiem konca wiadomosci jest ^
*/
struct mess{
int op; // numer operacji (3 pierwsze / znaki 0-2)
int id; // miejsce na id uzytkownika (3 nastepne /znaki 3-5)
string text; // wiadomosc (od 6 do znaku konca "^")
};
mutex mtx; //Semafor dostępu do listy klientów
mutex mtx_write; //Semafor do operacji na plikach
vector<client_threads*> client_list; //Lista klientów w systemie
vector<int> users_list; // Lista istniejacych użytkowników naszego chatu
int generateID();
void loadUsers();
void service(int my_soc);
void disconnect (int id);
string checkifDigit(string check);
string getHistory(string id);
int find_client(int id_name);
int find_clients(int id_name);
string getFriends(string id);
bool checkUser(string user);
string getPassword(string id);
struct mess recive(int client);
void set_name(int soc, int name);
bool checkFriend(string id,string id_friend);
void createUser(string id, string password);
void addFriend(string id, string id_friend);
string getHistory(string id_from,string id_to);
bool checkifFriend(string user_id, string friend_id);
void SaveAndclearFile(string id_from,string id_to,string text);
void send_message(int fd,string op, string id, string text);
void makeHistory(string id_from,string id_to, string text);
int main(int argc, char** argv){
struct sockaddr_in server_addr;
socklen_t socket_len;
int server_socket;
loadUsers();
// Tworzenie gniazda
if((server_socket = socket(PF_INET, SOCK_STREAM, 0)) != -1)
cout << "Utworzona gniazdo serwera... " << endl ;
else
cout << "Błąd tworzenia gniazda serwera... " << endl ;
int value = 1;
// Czy port jest zajety
if(setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int) ) < 0)
cout << "Błąd setsockopt..." << endl;
server_addr.sin_family = PF_INET;
server_addr.sin_port = htons(9004);
server_addr.sin_addr.s_addr = INADDR_ANY;
// Wązanie gniazda
if(bind (server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) != -1)
cout << "Powiązano gniazdo serwera... " << endl ;
else
cout << "Błąd powiązania gniazda serwera... " << endl ;
// Gniazdo oczekuje nanpolaczenia
if(listen(server_socket,10) != -1)
cout << "Gniazdo serwera w trybie pasywnym... " << endl ;
else
cout << "Błąd przechodzenia w stan pasywny gniazda serwera... " << endl ;
while(true){
//Tworzenie struktury nowego klienta i akceptacja połączenie
client_threads* new_client = new client_threads;
socket_len = sizeof(new_client->addrx);
if ((new_client->id_soc = accept(server_socket, (struct sockaddr*)&new_client->addrx, &socket_len)) != -1)
cout << "Zaakceptowano nowe połączenie... " << endl ;
else
cout << "Błąd nowego połączenia... " << endl ;
//Dosanie nowego klienta do listy klientów w systemie
mtx.lock();
client_list.push_back(new_client);
mtx.unlock();
//Procedura obsługi klienta
thread client_thread(service,new_client->id_soc);
client_thread.detach();
}
return EXIT_SUCCESS;
}
/*
Funkcja obsuługująca klientów
*/
void service(int my_soc){
string send_from; //Login użytkownika obsugiwanego przez wątek (jeśli jest zalogowany)
struct mess recived_message;//Wiadomość otrzymana od klienta
while(true){
recived_message = recive(my_soc);//Odczyt wiadomosci
if(recived_message.op == -1){//Zakończenie Połączenia
cout << "Brak Połączenia z użytownikiem" << endl;
break;
}
switch(recived_message.op){//Obsługa poszczególnych operacji
case 0 : { //Log in - Logowanie do serwisu
send_from = to_string(recived_message.id);
if((find_clients(recived_message.id) == -1) & (recived_message.text.compare(getPassword(to_string(recived_message.id))) == 0)){
set_name(my_soc, recived_message.id);
send_message(my_soc,"000",to_string(recived_message.id),"ok");
cout << "Pomyslne logowanie uzytkownika: " << recived_message.id << endl;
}
else{
send_message(my_soc,"000",to_string(recived_message.id),"0");
cout << "Błąd logowania uzytkownika: " << recived_message.id << endl;
}
break;
}
case 1 : { //Friends_Send - Wysłanie znajomych użytkownika
send_message(my_soc,"001",send_from, getFriends(to_string(recived_message.id)));
break;
}
case 2 : { //History_Send - Wysłanie historii konwersacji
send_message(my_soc,"002",to_string(recived_message.id), getHistory(send_from,to_string(recived_message.id)));
break;
}
/*
Wiadmość wysyłana jeśli obaj użytkownicy mają siebie w liście znajomych.
Jeśli znajomy jest niedostępny wiadomośc jest dopisywana do jego historii
*/
case 3 : { //Send Message - Przesłanie wiadomości
int send_to;
if(checkifFriend(send_from,to_string(recived_message.id)))
if((send_to = find_client(recived_message.id)) != -1)
send_message(send_to,"004",send_from, recived_message.text);
else
makeHistory(send_from, to_string(recived_message.id), send_from + ": " + recived_message.text);
else
send_message(my_soc,"004",to_string(recived_message.id), "SERVER: NIE MAM CIE W ZNAJOMYCH");
;
break;
}
case 4 : { //Add New User - Tworzy nowego uzytkownika
int i;
if ((i = generateID()) != -1){
createUser(to_string(i), recived_message.text);
send_message(my_soc,"004",to_string(i), recived_message.text);
}
else
send_message(find_client(recived_message.id),"004","0-1", "Brak Miejsca");
break;
}
case 5 : { // History_Make - Po przy zamykaniu konwersacji klient wysła cała historie która jest zapisywana
SaveAndclearFile(send_from, to_string(recived_message.id),recived_message.text);
break;
}
case 6 : { // Friend - Dodaj znajomego do listy
if(checkUser(to_string(recived_message.id))){
addFriend(send_from, to_string(recived_message.id));
send_message(my_soc,"006",to_string(recived_message.id), "1");
}
else
send_message(my_soc,"006",to_string(recived_message.id), "0");
break;
}
default : { // Not defined operation
cout << "Nieznany kod operacji" << endl;
break;
}
}
}
disconnect (my_soc);
close(my_soc);
}
/*
Nadpisuje całą romowy użytkownika id_from z użytkownikiem id_to
*/
void SaveAndclearFile(string id_from,string id_to, string text){
ofstream currfile;
mtx_write.lock();
currfile.open(id_from + "/" + id_to +".txt", ios_base::trunc);
currfile << text;
currfile.close();
mtx_write.unlock();
}
/*
Sprawdza czy użytkownik friend_id ma w znajomych użytkownika user_id
*/
bool checkifFriend(string user_id, string friend_id){
string line;
mtx_write.lock();
ifstream currfile(friend_id + "/Friends.txt");
while(currfile >> line)
if (user_id.compare(line) == 0){
cout << "foud user " << line << endl;
mtx_write.unlock();
return true;
}
currfile.close();
mtx_write.unlock();
return false;
}
/*
Sprawdza czy użytkownik istnieje
*/
bool checkUser(string user){
string line;
ifstream currfile("Users.txt");
while(currfile >> line)
if (user.compare(line) == 0){
cout << "found user " << line << endl;
return true;
}
currfile.close();
return false;
}
/*
Dodaj użutkownika id_friend do znajomych
*/
void addFriend(string id, string id_friend){
ofstream currfile;
mtx_write.lock();
currfile.open(id + "/Friends.txt", ios_base::app);
currfile << id_friend + "\n";
cout << "Added " << id_friend << " to " << id << "/Friends.txt" << endl;
currfile.close();
mtx_write.unlock();
}
/*
Dopisz wiadomość do historii użytkownika niedostępnego
*/
void makeHistory(string id_from,string id_to, string text){
ofstream currfile;
mtx_write.lock();
currfile.open(id_to + "/" + id_from +".txt", ios_base::app);
currfile << text << "\n";
currfile.close();
mtx_write.unlock();
}
/*
Uzyskaj historię rozmowy użytkonika id_form z użytkownikiem id_to
*/
string getHistory(string id_from,string id_to){
string history(id_from + "/" + id_to + ".txt"), text, line;
mtx_write.lock();
ifstream currfile(history);
while(getline(currfile,line))
text.append(line +"\n");
currfile.close();
mtx_write.unlock();
if(!text.empty())
return text;
else
return "0";
}
/*
Uzyskaj listę znajomych
*/
string getFriends(string id){
string friends(id + "/Friends.txt"), text, line;
ifstream currfile(friends);
while(currfile >> line)
text.append(line +" ");
currfile.close();
return text;
}
/*
Uzyskaj hasło
*/
string getPassword(string id){
string line;
string password(id + "/Haslo.txt");
ifstream currfile(password);
getline (currfile,line);
currfile.close();
return line;
}
/*
Stwórz nowego użytkownika
*/
void createUser(string id, string password){
string dir("mkdir " + id);
ofstream currfile;
system(dir.c_str());
currfile.open("Users.txt", ios_base::app);
currfile << id + "\n";
currfile.close();
currfile.open(id + "/Haslo.txt");
currfile << password;
currfile.close();
currfile.open(id + "/Friends.txt");
currfile.close();
}
/*
Wczytaj użytkowników
*/
void loadUsers(){
string line;
ifstream currfile("Users.txt");
while(currfile >> line)
users_list.push_back(stoi(line));
currfile.close();
}
/*
Generuj pierwsze wolne id z przedziału 100-998
*/
int generateID(){
for (int i = 100; i < 999; ++i)
if(!(find(users_list.begin(), users_list.end(), i) != users_list.end())){
users_list.push_back(i);
return i;
}
return -1;
}
/*
Usuń klienta z listy podłączonych klientów
*/
void disconnect (int soc) {
mtx.lock();
for(size_t i=0; i<client_list.size(); ++i)
if (client_list[i]->id_soc == soc){
delete client_list[i];
client_list.erase(client_list.begin() + i);
mtx.unlock();
return;
}
mtx.unlock();
}
/*
Znajdź numer gniazda pod którym znajduje się użytkownik o danym id
*/
int find_client(int id_name){
for(size_t i=0; i<client_list.size(); ++i)
if (client_list[i]->id_user == id_name){
return client_list[i]->id_soc;
}
return -1;
}
/*
Czy dany użytkownik jest zalogowany
*/
int find_clients(int id_name){
for(size_t i=0; i<client_list.size(); ++i)
if (client_list[i]->id_user == id_name){
return 1;
}
return -1;
}
/*
Logowanie - przypisanie użytkownika do danego gniazda
*/
void set_name(int soc, int name){
mtx.lock();
for(size_t i=0; i<client_list.size(); ++i)
if (client_list[i]->id_soc == soc){
client_list[i]->id_user = name;
mtx.unlock();
break;
}
mtx.unlock();
}
/*
Wysłanie wiadomości w ustalonym formacie
*/
void send_message(int fd,string op, string id, string text){
string message = op + id + text + "^";
cout << "Send: " << message << endl;
write(fd, message.c_str(), message.size());
}
/*
Odbiór wiadomości i przetworzenie jej do danej struktury
*/
struct mess recive(int client){
char bufor[MAX];
string zdanie;
size_t fnd;
struct mess recived;
int size;
do{
size = read(client, &bufor, 256);
if(size == 0){// Jeśli 0 to klient się rozłączył
recived.op = -1;
return recived;
}
if(size == -1){// Jeśli 0 to klient się rozłączył
recived.op = -1;
return recived;
}
//Jeśli znaleziono znak końca wiadomości to break i koniec czytania wiadomości
string wczytane(bufor);
fnd = wczytane.find_first_of("^");//fnd reprezentuje pozycję znaku w stringu
if(fnd != string::npos){// Jeśli nie znajdzie znaku w stringu przyjmuje wartość nops czyli wiadmość się nie skończyła
if (fnd == 0)
break;
wczytane = wczytane.substr(0,fnd);
zdanie.append(wczytane);
break;
}
zdanie.append(wczytane);
}while(fnd == string::npos);// Czytaj dopóki nie wytąpi znak końca
//Otrzymana wiadmość
cout << "Recv: " << zdanie << endl;
//Sprawdzanie czy wiadomość jest zgodna z ustaloną formą i przekształcenie do struktury
if (zdanie.size() >= 6){
recived.op = stoi(checkifDigit(zdanie.substr(0,3)));
recived.id = stoi(checkifDigit(zdanie.substr(3,3)));
recived.text = zdanie.substr(6);
}
else{
cout << "Nieodpowiednia ramka" << endl;
recived.op = -2;
return recived;
}
return recived;
}
/*
Sprawdza czy string składa się wyłącznie z cyfr jeśli nie to ustal na -1 tzn. rozłącz
*/
string checkifDigit(string check){
for(size_t i=0; i<check.size(); ++i)
if (!isdigit(check[i])){
return "0-1";
cout << "Złe Dane " << endl;
}
return check;
}