[포스코x코딩온] 스마트팩토리 7, 8주차 회고 | C++ 프로젝트 회고

이남웅·2023년 5월 13일
0

7, 8주차 기간동안에는 지금까지 배운 SQL과 C++을 활용하여 2명씩 팀을 구성하여 채팅 프로그램을 만드는 프로젝트를 진행하였다.

1. 데이터 베이스 구성

먼저 채팅 프로그램의 사용자를 구분하기 위해 유저 테이블을 구성하였다. 로그인에 필요한 ID 와 패스워드 그리고 채팅 프로그램에서 보여줄 닉네임과 현재 로그인 여부를 스키마로 구성하였다.

2. 매커니즘 구성

Client가 Server를 통해 채팅 프로그램에 접속하기 위해서는 사용자의 ID와 패스워드를 가지고 로그인을 시도하거나, 회원가입을 하여 SQL에 사용자를 추가한 후 로그인을 시도해야 한다. Client가 Server로 ID/PW를 전송하면 Server는 SQL에서 Client로 받은 ID/PW를 검색하고 ID/PW가 있다면 Server는 Client로 로그인 승인 명령을 내린다.
이후 Client는 채팅방에 입장하고 채팅방에 보낼 메세지를 입력한다. Client는 Server로 메세지를 송신하고 Server는 채팅 프로그램에 들어와있는 모든 사용자에게 수신한 메세지는 송신한다. 이후 Server는 수신받은 메세지를 SQL에 저장한다.

3. 코드 구현

https://superficial-yamamomo-7e5.notion.site/Welcome-Chatting-Server-d33b62952dda41c68c34558d009e3e06

작성한 전체코드와 각 매커니즘을 코드로 구현한 부분을 notion에 정리하였다. 여기서는 일부분의 코드만 보여지므로 전체적인 코드를 보고 싶다면 notion을 참고하면 좋을 것 같다.

회원가입

Server

if (recv(new_client.sck, buf, MAX_SIZE, 0) > 0)

Client

send(client_sock, "login", MAX_SIZE, 0);

Client는 Server와 통신을 성공하면 Client는 회원가입과 로그인 중 하나를 선택해야 한다. Server는 recv()함수를 통해 Client와 연결상태를 확인함과 동시에 Client에서 보낸 메세지를 받는다. Server는 Client와 통신을 성공한 이후 로그인, 회원가입 중 하나이므로 받는 메세지를 바탕으로 로그인, 회원가입을 진행하는 준비작업을 한다.

Server

else if (string(buf) == "signin")
            {
                ZeroMemory(&buf, MAX_SIZE);
                cout << "[Log] Unknown " << un_client << " is trying sign in" << endl;
                while (1)
                {
                    user_id = recv_send_msg(1, new_client.sck, "");
                    if (find_account(1, user_id, ""))
                        recv_send_msg(2, new_client.sck, "useit");
                    else
                    {
                        recv_send_msg(2, new_client.sck, "usenotit");
                        break;
                    }
                }
                user_pw = recv_send_msg(1, new_client.sck, "");
                nickname = recv_send_msg(1, new_client.sck, "");
                in_account(user_id, user_pw, nickname);
                cout << "[Log] Unknown " << un_client << " Creat ID" << nickname << "(" << user_id << ")" << endl;
            }

Client

else if (option == 2)
            {
                send(client_sock, "signin", MAX_SIZE, 0);
                while (1)
                {
                    cout << "ID: ";
                    cin >> user_id;
                    send(client_sock, user_id.c_str(), MAX_SIZE, 0);
                    recv(client_sock, buf, MAX_SIZE, 0);
                    if (string(buf) == "useit")
                        cout << "ID use it" << endl;
                    else if (string(buf) == "usenotit")
                        break;
                    buf_reset(buf);
                }
                buf_reset(buf);
                cout << "Password: ";
                string pw = "";
                send(client_sock, user_pw.c_str(), MAX_SIZE, 0);
                cout << "Nickname: ";
                cin >> nickname;
                send(client_sock, nickname.c_str(), MAX_SIZE, 0);
            }
        }

Client가 회원가입을 선택하였다면, Client는 Server로 "signin" 메세지를 보낸다. Server는 회원가입을 위한 준비를 하고 Client는 ID를 입력하여 Server로 보낸다. Server는 Client로 받은 ID를 SQL에 검색하여 ID가 사용중인지 여부를 확인한다. 사용중이라면 "useit" 메세지를 보내고 사용중이지 않다면 "usenotit" 메세지를 보낸다. Client는 ID 사용여부를 받아 ID가 사용중이지 않다면 패스워드와 닉네임을 Server로 추가적으로 보내 회원가입을 완료한다.

로그인

Server

if (option == 1)
            {
                send(client_sock, "login", MAX_SIZE, 0);
                while (1)
                {
                    cout << "ID: ";
                    cin >> user_id;
                    send(client_sock, user_id.c_str(), MAX_SIZE, 0);
                    cout << "Password: ";
                    cin >> user_pw;
                    send(client_sock, user_pw.c_str(), MAX_SIZE, 0);
                    recv(client_sock, buf, MAX_SIZE, 0);
                    if (string(buf) == "logsu")
                    {
                        cout << "Login success" << endl << endl;
                        recv(client_sock, buf, MAX_SIZE, 0);
                        nickname = string(buf);
                        cout << "Hello! " << nickname << endl;
                        break;
                    }
                    else if (string(buf) == "logfa")
                        cout << "Login fail" << endl;
                }
                buf_reset(buf);
                break;
            }

Client

if (option == 1)
            {
                send(client_sock, "login", MAX_SIZE, 0);
                while (1)
                {
                    cout << "ID: ";
                    cin >> user_id;
                    send(client_sock, user_id.c_str(), MAX_SIZE, 0);
                    cout << "Password: ";
                    cin >> user_pw;
                    send(client_sock, user_pw.c_str(), MAX_SIZE, 0);
                    recv(client_sock, buf, MAX_SIZE, 0);
                    if (string(buf) == "logsu")
                    {
                        cout << "Login success" << endl << endl;
                        recv(client_sock, buf, MAX_SIZE, 0);
                        nickname = string(buf);
                        cout << "Hello! " << nickname << endl;
                        break;
                    }
                    else if (string(buf) == "logfa")
                        cout << "Login fail" << endl;
                }
                buf_reset(buf);
                break;
            }

회원가입을 마친 사용자는 Server로 로그인을 진행한다는 메세지를 보낸다. Server는 로그인을 위한 준비를 하고 Client는 입력받은 ID와 패스워드를 Server쪽으로 데이터를 보낸다. Server는 받은 데이터를 가지고, SQL에 데이터를 검색한다. 사용자가 ID와 패스워드를 정상적으로 입력하고 로그인 상태가 아니라면, Server는 로그인 승인 메세지로 "logsu" (login success) 메세지를 Client로 보낸다. 반대로, 사용자가 ID와 패스워드가 옳지 않거나, 누군가 로그인 하였다면 Server는 로그인 실패 메세지로 "logfa" (login fail) 메세지를 Client로 보낸다. Client는 Server로 받은 승인 메세지를 바탕으로 채팅방에 입장하게 된다.

메세지 송수신

Client

string get_time()
{
    using namespace std::chrono;
    system_clock::time_point tp = system_clock::now();
    std::stringstream str;
    __time64_t t1 = system_clock::to_time_t(tp);
    system_clock::time_point t2 = system_clock::from_time_t(t1);
    if (t2 > tp)
        t1 = system_clock::to_time_t(tp - seconds(1));

    tm tm{};
    localtime_s(&tm, &t1);

    str << std::put_time(&tm, "%Y%m%d%H%M%S") << std::setfill('0') << std::setw(3)
        << (std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count() % 1000);

    return str.str();
}

채팅방에 입장한 Client는 메세지를 입력하게 될 경우 현재시간을 측정한다. get_time() 함수를 통해 현재시간을 밀리세컨드까지 측정한다. 밀리세컨드까지 측정하는 이유는 메세지를 저장하는 데이터베이스에서 기본키로 설정하고, 초까지만 측정하여 저장할 경우 중복되는 경우가 늘어나기 때문이다. get_time()함수는 macos에서 작동되지 않을 가능성이 많다. 오직 Visual Studio 2022에서 사용하기 위해 작성한 코드로 만약 macos에서 밀리세컨드를 측정하기 위한 함수가 필요하면 밑에의 함수를 사용하면 된다.

mac용 밀리세컨드 측정 함수

#include <iostream>
#include <string>
#include <chrono>

using namespace std;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::seconds;
using std::chrono::system_clock;

class NowTime
{
    struct timespec specific_time;
    struct tm *now;

    string time_sort(int time)
    {
        char buf[3] = {00};

        buf[0] = time / 10 + '0';
        buf[1] = time % 10 + '0';

        return string(buf);
    }

public:
    string get_time_sql()
    {
        clock_gettime(CLOCK_REALTIME, &specific_time);
        now = localtime(&specific_time.tv_sec);

        string msg = to_string(1900 + now->tm_year) + time_sort(now->tm_mon + 1) + time_sort(now->tm_mday) + 
        time_sort(now->tm_hour) + time_sort(now->tm_min) + to_string(now->tm_sec) + to_string(specific_time.tv_nsec / 100000000);

        return msg;
    }

    string get_time_pri()
    {
        clock_gettime(CLOCK_REALTIME, &specific_time);
        now = localtime(&specific_time.tv_sec);

        string msg = to_string(1900 + now->tm_year) + "/" + time_sort(now->tm_mon + 1) + "/" + time_sort(now->tm_mday) + " " + 
        time_sort(now->tm_hour) + ":" + time_sort(now->tm_min) + ":" +  to_string(now->tm_sec);

        return msg;
    }
};

int main() 
{
    NowTime nowtime;
    cout << nowtime.get_time_sql() << endl;
    cout << nowtime.get_time_pri() << endl;

    return 0;
}

Server

string in_chat_log(string user_id, char* chat_log) //chat = chat + time(ex: 20230507094224828)
{
    char* chat_log_p = &*chat_log;
    int j = 0;
    string time = "";
    string time_log = "";
    while (*chat_log)
        chat_log++;
    chat_log -= 17;

    while (*chat_log)
    {
        time.push_back(*chat_log);
        time_log.push_back(*chat_log);
        *chat_log = 0;
        chat_log++;
        switch (j)
        {
        case 3:
            time.push_back('-');
            break;
        case 5:
            time.push_back('-');
            break;
        case 7:
            time.push_back(' ');
            break;
        case 9:
            time.push_back(':');
            break;
        case 11:
            time.push_back(':');
            break;
        case 12:
        case 13:
        case 14:
            time.pop_back();
            break;
        }
        j++;
    }
    string sql_chat_log = string(chat_log_p);
    vector<string> v = { time, user_id, sql_chat_log };
    chat_log_list.push_back(v);

    return time;  //2023-05-07 09:42:24
}

Client는 측정한 시간을 메세지 뒤에 합쳐서 Server로 보낸다. Server는 받은 메세지와 시간을 분리하기 위해 in_chat_log() 함수를 거친다. 매개변수를 포인트 변수로 받고, 포인트 변수의 주소값을 맨끝으로 이동시킨 후 시간값만큼의 길이를 앞으로 이동 시킨 후, 시간을 추출하는 방식을 사용하였다. in_chat_log() 함수를 거치면서 사용자가 시간을 보기 좋은 형태로 바꾼후 time 변수에 저장하고, 따로 후처리하지 않은 시간을 SQL에 저장해준다. 이후 입력받은 데이터에서 시간 데이터를 제거해준다.

이렇게 하면 사용자는 자신이 입력한 메세지를 Server로 보내고 정상적인 과정을 거치면 Client에서 "(시간) (닉네임) (메세지)" 형태로 메세지를 받게 된다.

4. 마무리

이번 프로젝트를 진행하면서 여러모로 많은 것을 느낀 시간이었던 것 같다. 기능을 추가하기 위해서는 고려해야할 조건이 정말 많았다. 예를들어 메세지에 시간을 추가하기 위해서는 현재 사용하는 프로그램에서 내가 작성하는 코드의 호환성을 고려해야 하고, Client와 Server간의 시간 데이터를 어떻게 처리를 해야할지와 출력을 어떻게 하면 좋을지, 어떤 형태로 시간을 저장하면 좋을지 등을 고려를 해야했다.
처음에 작성한 시간 측정 함수는 mac용 밀리세컨드 함수이다. 처음에 mac에서 사용하는 Visual Studio Code에서 작성하다가, Visual Studio 2022에서 작동되지 않아 다시 코드를 작성해야 했다. 다음부터는 현재 자신이 사용하려고 하는 라이브러리가 현재 사용중인 프로그램에서 잘 작동하는지 꼭 확인하고 코드를 작성해야 할 것 같다.

0개의 댓글