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 |