25.07.18 개발일지 / 채팅 프로그램 5팀

2025. 7. 29. 18:57·LMS 7/개발일지

DB 관련 서버함수


1. DB 연결, 해제 함수

void db_connect() {
    // db 초기화 및 연결
    conn = mysql_init(NULL);
    if (!(mysql_real_connect(conn, DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT, DB_SOCKET, DB_CLIENT_FLAGS))) {
        fprintf(stderr, "MySQL 연결 실패: %s\n", mysql_error(conn));
        exit(EXIT_FAILURE);
    }

    // 연결 문자 집합을 UTF-8로 설정하여 한글 깨짐 방지
    if (mysql_set_character_set(conn, "utf8mb4")) { // utf8mb4는 이모지 등 더 넓은 범위의 유니코드 지원
        fprintf(stderr, "mysql_set_character_set 실패: %s\n", mysql_error(conn));
        mysql_close(conn);
        exit(EXIT_FAILURE);
    }
    printf("MySQL DB 연결 성공\n");

    // 공개 채팅방 존재 확인 및 생성 (서버 시작 시 한 번)
    int public_chat_room_id_from_db = -1;
    char query[256];
    snprintf(query, sizeof(query), "SELECT room_id FROM chatroom WHERE room_name = 'Public Chat' AND room_type = 0");
    if (mysql_query(conn, query)) {
        fprintf(stderr, "쿼리 실패: %s\n", mysql_error(conn));
        exit(EXIT_FAILURE);
    }
    MYSQL_RES *res = mysql_store_result(conn);
    // 공개 채팅방 존재 여부 확인
    if (res && mysql_num_rows(res) > 0) {
        MYSQL_ROW row = mysql_fetch_row(res);
        public_chat_room_id_from_db = atoi(row[0]);
        printf("공개 채팅방 발견: ID %d\n", public_chat_room_id_from_db);
    } else {
        printf("공개 채팅방을 새로 생성...\n");
        // 공개 채팅방 생성
        if (mysql_query(conn, "INSERT INTO chatroom (room_name, room_type, creator_id) VALUES ('Public Chat', 0, NULL)")) {
            fprintf(stderr, "공개 채팅방 생성 실패: %s\n", mysql_error(conn));
            exit(EXIT_FAILURE);
        }
        public_chat_room_id_from_db = mysql_insert_id(conn); // 새로 생성된 ID 가져오기
        printf("공개 채팅방 생성 완료: ID %d\n", public_chat_room_id_from_db);
    }
    if (res) mysql_free_result(res);
    g_public_chat_room_id = public_chat_room_id_from_db; // 전역 변수에 저장
}
void db_disconnect() {
    if (conn) {
        mysql_close(conn);
        printf("MySQL DB 연결 해제 완료.\n");
    }
}

2. 회원가입 함수

int db_register_user(const char* id, const char* password, const char* nickname, int* pk_id_out) {
    char query[BUFFER_SIZE * 2]; // 쿼리 버퍼

    // id 중복 확인
    snprintf(query, sizeof(query), "SELECT id FROM users WHERE id = '%s'", id);
    if (mysql_query(conn, query)) {
        fprintf(stderr, "사용자 ID 중복 확인 쿼리 실패: %s\n", mysql_error(conn));
        return -1; // DB 오류
    }
    MYSQL_RES *res = mysql_store_result(conn);
    if (res && mysql_num_rows(res) > 0) {
        mysql_free_result(res);
        return -2; // 사용자 ID 중복
    }
    if (res) mysql_free_result(res);

    // 닉네임 중복 확인
    snprintf(query, sizeof(query), "SELECT nickname FROM users WHERE nickname = '%s'", nickname);
    if (mysql_query(conn, query)) {
        fprintf(stderr, "닉네임 중복 확인 쿼리 실패: %s\n", mysql_error(conn));
        return -1; // DB 오류
    }
    res = mysql_store_result(conn);
    if (res && mysql_num_rows(res) > 0) {
        mysql_free_result(res);
        return -3; // 닉네임 중복
    }
    if (res) mysql_free_result(res);

    // 사용자 정보 저장
    snprintf(query, sizeof(query), 
             "INSERT INTO users (id, password, nickname) VALUES ('%s', '%s', '%s')",
             id, password, nickname);
    if (mysql_query(conn, query)) {
        fprintf(stderr, "사용자 등록 쿼리 실패: %s\n", mysql_error(conn));
        return -1;
    }
    
    // AUTO_INCREMENT ID 가져오기
    if (pk_id_out) {
        *pk_id_out = mysql_insert_id(conn);
    }

    return 0; // 성공
}

3. 로그인 함수

int db_authenticate_user(const char* id, const char* password, int* pk_id_out, char* nickname_out) {
    char query[BUFFER_SIZE * 2]; // 쿼리 버퍼
    int pk_id_temp; // 인증된 사용자의 pk_id를 반환
    char nickname_temp[USERNAME_SIZE]; // 인증된 사용자의 nickname을 반환

    snprintf(query, sizeof(query), "SELECT pk_id, nickname FROM users WHERE id = '%s' AND password = '%s'", id, password);
    
    if (mysql_query(conn, query)) {
        fprintf(stderr, "사용자 인증 쿼리 실패: %s\n", mysql_error(conn));
        return 0; // 인증 실패 (DB 오류)
    }

    MYSQL_RES *res = mysql_store_result(conn);
    if (res == NULL) {
        fprintf(stderr, "인증 결과 저장 실패: %s\n", mysql_error(conn));
        return 0; // 인증 실패 (DB 오류)
    }

    // 인증 성공시
    if (mysql_num_rows(res) == 1) {
        MYSQL_ROW row = mysql_fetch_row(res);
        if (row) {
            pk_id_temp = atoi(row[0]);
            strncpy(nickname_temp, row[1], USERNAME_SIZE - 1);
            nickname_temp[USERNAME_SIZE - 1] = '\0'; // 널 종료

            if (pk_id_out) *pk_id_out = pk_id_temp; // pk_id 저장
            if (nickname_out) {
                strncpy(nickname_out, nickname_temp, USERNAME_SIZE - 1);
                nickname_out[USERNAME_SIZE - 1] = '\0';
            }  // nickname 임시저장
            mysql_free_result(res);
            return 1; // 인증 성공
        }
    }

    mysql_free_result(res);
    return 0; // 인증 실패 (사용자 없거나 비밀번호 불일치)
}

4. 사용자 목록 확인 함수

void list_users(int client_index) {
    char user_list_content[BUFFER_SIZE]; // 전송할 사용자 목록 저장 버퍼
    user_list_content[0] = '\0';         

    // 클라이언트 배열 접근 시 동시성 문제를 방지하기 위한 mutex
    pthread_mutex_lock(&clients_mutex);
    
    // 실제로 목록에 추가된 사용자 수
    int active_users_count = 0;

    char query[BUFFER_SIZE]; // 쿼리 버퍼
    // users 테이블에서 모든 닉네임, pk_id 를 조회
    // 결과는 닉네임의 오름차순으로 정렬
    // pk_id는 현재 서버에 접속 중인 클라이언트와 DB 사용자를 매핑하는 데 사용
    snprintf(query, sizeof(query), "SELECT nickname, pk_id FROM users ORDER BY nickname ASC");

    MYSQL_RES *res;   // SQL 쿼리 실행 결과(Result Set)를 저장할 포인터
    MYSQL_ROW row;    // 결과 집합의 각 행(레코드)을 저장할 포인터

    // mysql_query() 함수를 사용하여 SQL 쿼리를 실행
    // 실행에 실패하면 오류 메시지를 표준 에러 스트림(stderr)에 출력하고,
    // 클라이언트에게 오류 메시지를 전송 후 함수 종료
    if (mysql_query(conn, query)) {
        fprintf(stderr, "사용자 목록 쿼리 실패: %s\n", mysql_error(conn));
        send_message_to_client(client_index, MSG_ERROR, " 사용자 목록 조회에 실패했습니다.", "서버");
        pthread_mutex_unlock(&clients_mutex); // 락 해제
        return;
    }
    
    // mysql_store_result() 함수를 사용하여 쿼리 결과를 클라이언트 메모리로 가져옴
    // 결과가 NULL이면 (예: 쿼리는 성공했지만 결과가 없거나, 메모리 부족 등) 오류 처리
    res = mysql_store_result(conn);
    if (res == NULL) {
        fprintf(stderr, "사용자 목록 결과 저장 실패: %s\n", mysql_error(conn));
        send_message_to_client(client_index, MSG_ERROR, " 사용자 목록 조회에 실패했습니다.", "서버");
        pthread_mutex_unlock(&clients_mutex); // 락 해제
        return;
    }

    // mysql_fetch_row() 함수를 사용하여 결과 집합에서 한 행씩 데이터를 가져옴
    while ((row = mysql_fetch_row(res)) != NULL) {
        char status[20];       // 사용자의 접속 상태
        int logged_in_flag = 0; // 현재 사용자가 서버에 로그인되어 있는지
        
        // 현재 서버에 접속 중인 모든 클라이언트를 순회
        // 클라이언트의 소켓이 활성화되어 있고, 로그인 상태이며, DB에서 가져온 사용자의 pk_id와 일치하는 클라이언트를 찾음
        // row[0]은 nickname, row[1]은 pk_id
        for (int i = 0; i < MAX_CLIENTS; i++) {
            if (clients[i].socket != -1 && clients[i].logged_in && 
                clients[i].user_pk_id == atoi(row[1])) {
                logged_in_flag = 1; // 일치하는 클라이언트를 찾으면 접속 중 표시
                break;              
            }
        }
        
        // logged_in_flag 값에 따라 접속 상태를 표시
        if (logged_in_flag) {
            strcpy(status, "(접속 중)");
        } else {
            strcpy(status, "(오프라인)");
        }
        
        // user_list_content 버퍼에 현재 사용자의 정보를 추가하기 전
        // 버퍼 오버플로우가 발생하지 않도록 남은 공간을 확인
        // 현재 버퍼의 길이 + 닉네임 길이 + 상태 문자열 길이 + 여유 공간(공백, 괄호, 개행 문자 등)
        if (strlen(user_list_content) + strlen(row[0]) + strlen(status) + 5 > BUFFER_SIZE) { 
            // 버퍼가 가득 찼으면 목록이 잘렸음을 알리는 메시지를 추가하고 루프를 종료
            strcat(user_list_content, "...(목록이 너무 길어 일부만 표시됩니다.)\n");
            break; 
        }
        
        // 현재 사용자의 닉네임, 공백, 상태 문자열, 개행 문자를 버퍼에 이어 붙임
        strcat(user_list_content, row[0]); // 닉네임 (DB에서 가져온 nickname)
        strcat(user_list_content, " ");    // 닉네임과 상태 사이 공백
        strcat(user_list_content, status); // 접속 상태
        strcat(user_list_content, "\n");   // 다음 사용자를 위함
        active_users_count++;              // 목록에 추가된 사용자 수 증가
    }
    mysql_free_result(res); // 메모리 해제

    // 조회된 사용자 수가 0이면, 등록된 사용자가 없음을 알리는 메시지를 설정
    if (active_users_count == 0) {
        strcpy(user_list_content, "현재 등록된 사용자가 없습니다.");
    }

    // 최종적으로 완성된 사용자 목록 문자열을 요청한 클라이언트에게 전송
    send_message_to_client(client_index, MSG_LIST_USERS, user_list_content, "서버");
    
    // 서버에 사용자 목록 전송 완료 메시지를 출력
    printf("사용자 목록 전송 완료 (요청: %s)\n", clients[client_index].nickname);
    
    // mutex 해제
    pthread_mutex_unlock(&clients_mutex);
}

'LMS 7 > 개발일지' 카테고리의 다른 글

25.07.22 학습일지 / C++  (3) 2025.07.29
25.07.21 개발일지 / 채팅 프로그램 5팀  (1) 2025.07.29
25.07.17 개발일지 / 채팅 프로그램 5팀  (0) 2025.07.29
25.07.16 개발일지 / 채팅 프로그램 5팀  (0) 2025.07.29
2025.07.15 개발일지 [채팅 프로그램 5팀 / 테이블 명세서 / ERD]  (0) 2025.07.29
'LMS 7/개발일지' 카테고리의 다른 글
  • 25.07.22 학습일지 / C++
  • 25.07.21 개발일지 / 채팅 프로그램 5팀
  • 25.07.17 개발일지 / 채팅 프로그램 5팀
  • 25.07.16 개발일지 / 채팅 프로그램 5팀
m_Dev
m_Dev
  • m_Dev
    m_Dev
    m_Dev
  • 전체
    오늘
    어제
    • 분류 전체보기
      • MAIN STUDY
        • 정보보안
        • 빅데이터
        • 정보처리
        • 컴퓨터 구조
        • 기타
      • JOB
        • Study
        • Project
      • LMS 7
        • 개발일지
      • FRAMEWORK
        • Qt
        • MFC
        • Winform
        • WPF
        • MAUI
      • NETWORK
        • Study
        • Assignment
      • PYTHON
        • Set
        • Study
        • Assignment
        • Project
      • C
        • Set
        • Study
        • Assignment
        • Project
      • C++
        • Set
        • Study
        • Assignment
        • Project
      • C#
        • Set
        • Study
        • Assignment
        • Project
      • DATABASE
        • MySQL
        • SQLite
      • IDE
        • VisualStudioCode
        • VisualStudio
        • Pycharm
        • Colab
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
m_Dev
25.07.18 개발일지 / 채팅 프로그램 5팀
상단으로

티스토리툴바