Putty를 활용한 서버 내 채팅 기능 구현

2024. 7. 25. 15:17---포트폴리오---/텀 프로젝트

728x90

- 동작원리

(1) 클라이언트가 서버에 접속하면 서버에서 사용할 고유 ID 입력.

(2) 각 클라이언트가 서버에 접속하면 서버 화면에서 클라이언트 접속 알림.

(3) 각 클라이언트가 채팅을 입력하면 상대 클라이언트에게 채팅 입력시간과 상대 클라이언트 고유 ID 및 채팅 출력.

 

- 클라이언트 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //유닉스 표준 함수 라이브러리
#include <pthread.h> //POSIX 스레드 라이브러리
#include <arpa/inet.h> //인터넷 프로토콜 주소 라이브러리
#include <time.h> //시간 관련 함수 라이브러리

#define MAX_MESSAGE_LENGTH 1024 //메시지 최대 길이 정의

void *handle_server_message(void *arg); //서버로부터 메시지를 처리하는 스레드 함수 선언

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "사용법: %s <서버 IP> <포트 번호>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char *server_ip = argv[1]; //서버 IP 주소
    int port = atoi(argv[2]);  //포트 번호를 정수로 변환

    //클라이언트 소켓 생성
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("소켓 생성 실패");
        exit(EXIT_FAILURE);
    }

    //서버 주소 구조체 설정
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(server_ip);
    server_address.sin_port = htons(port);

    //서버 연결
    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("서버 연결 실패");
        exit(EXIT_FAILURE);
    }

    //사용자 이름 입력
    printf("사용자 이름을 입력하세요: ");
    char client_id[20];
    fgets(client_id, sizeof(client_id), stdin);
    client_id[strcspn(client_id, "\n")] = '\0';  

    //서버로 사용자 이름 전송
    send(client_socket, client_id, strlen(client_id), 0);

   //서버로부터 사용자 이름 수신
    char received_id[20];
    int bytes_received = recv(client_socket, received_id, sizeof(received_id), 0);
    if (bytes_received <= 0) {
        perror("서버로부터 사용자 이름 받기 실패");
        close(client_socket);
        exit(EXIT_FAILURE);
    }
    received_id[bytes_received] = '\0';
    printf("서버에 연결되었습니다. 대화를 시작하세요.\n");

   //서버 메시지 처리 스레드 생성
    pthread_t server_thread;
    if (pthread_create(&server_thread, NULL, handle_server_message, (void *)&client_socket) != 0) {
        perror("스레드 생성 실패");
        close(client_socket);
        exit(EXIT_FAILURE);
    }

    //사용자가 메시지를 입력하고 전송하는 루프
    while (1) {
        char message[MAX_MESSAGE_LENGTH];
        printf("%s: ", client_id);
        fgets(message, sizeof(message), stdin);

        if (strcmp(message, "Q\n") == 0 || strcmp(message, "quit\n") == 0) {
            break;
        }

        //현재 시간 가져오기
        time_t raw_time;
        struct tm *time_info;
        time(&raw_time);
        time_info = localtime(&raw_time);

        char formatted_time[20];
        strftime(formatted_time, sizeof(formatted_time), "[%H:%M:%S]", time_info);

        
        char formatted_message[MAX_MESSAGE_LENGTH + 30];
        sprintf(formatted_message, "%s %s: %s", formatted_time, clients, message);
        
        //서버로 메시지 전송
        send(client[i].client_socket, formatted_message, strlen(formatted_message), 0);
    }

    
    pthread_join(server_thread, NULL);

    close(client_socket);
    return 0;
}

//서버로부터 메시지를 수신하여 출력하는 함수
void *handle_server_message(void *arg) {
    int client_socket = *((int *)arg);
    char message[MAX_MESSAGE_LENGTH];

    while (1) {
        //서버로부터 메시지 수신
        int bytes_received = recv(client_socket, message, sizeof(message), 0);
        if (bytes_received <= 0) {
            break;  
        }

        message[bytes_received] = '\0';
        //수신한 메시지 출력
        printf("%s", message);
    }

    pthread_exit(NULL);
}

 

- 서버 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <time.h>

#define MAX_MESSAGE_LENGTH 1024
#define MAX_CLIENTS 2  //최대 클라이언트 수 정의

//클라이언트 정보를 담는 구조체
typedef struct {
    int client_socket; //클라이언트 소켓 파일 디스크립터
    char client_id[20]; //클라이언트 ID
} Client;

Client clients[MAX_CLIENTS]; //클라이언트 배열
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //뮤택스 초기화

void *handle_client(void *arg); //클라이언트 메시지를 처리하는 함수의 선언
void broadcast(Client *clients, char *message, int sender_socket);

//서버 소켓 생성
int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "사용법: %s <포트 번호>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int port = atoi(argv[1]);
    
    //서버 주소 구조체 설정
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("소켓 생성 실패");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(port);

    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("바인드 실패");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    //클라이언트 연결 대기
    if (listen(server_socket, 5) == -1) {
        perror("리스닝 실패");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("서버가 클라이언트를 대기 중입니다...\n");

    while (1) {
    	//클라이언트 연결 수락
        struct sockaddr_in client_address;
        socklen_t client_address_len = sizeof(client_address);
        int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_address_len);
        if (client_socket == -1) {
            perror("클라이언트 연결 수락 실패");
            continue;
        }

        //클라이언트 ID 수신
        char client_id[20];
        int bytes_received = recv(client_socket, client_id, sizeof(client_id), 0);
        if (bytes_received <= 0) {
            perror("사용자 이름 받기 실패");
            close(client_socket);
            continue;
        }
        client_id[bytes_received] = '\0';

        //클라이언트 ID 전송
        send(client_socket, client_id, strlen(client_id), 0);

        printf("클라이언트 %s가 연결되었습니다.\n", client_id);

        //클라이언트 정보를 배열에 저장
        pthread_mutex_lock(&mutex);
        for (int i = 0; i < MAX_CLIENTS; ++i) {
            if (clients[i].client_socket == 0) {
                clients[i].client_socket = client_socket;
                strcpy(clients[i].client_id, client_id);
                break;
            }
        }
        pthread_mutex_unlock(&mutex);

  
        pthread_t client_thread;
        if (pthread_create(&client_thread, NULL, handle_client, (void *)&client_socket) != 0) {
            perror("스레드 생성 실패");
            close(client_socket);
            continue;
        }

        pthread_detach(client_thread);
    }

    close(server_socket);
    return 0;
}

void *handle_client(void *arg) {
    int client_socket = *((int *)arg);

    while (1) {
        char message[MAX_MESSAGE_LENGTH];

        int bytes_received = recv(client_socket, message, sizeof(message), 0);

        if (bytes_received <= 0) {

            pthread_mutex_lock(&mutex);
            for (int i = 0; i < MAX_CLIENTS; ++i) {
                if (clients[i].client_socket == client_socket) {
                    printf("클라이언트 %s가 연결을 종료했습니다.\n", clients[i].client_id);
                    clients[i].client_socket = 0;
                    break;
                }
            }
            pthread_mutex_unlock(&mutex);
            break;
        }

        message[bytes_received] = '\0';

        //메시지를 다른 클라이언트에 브로드캐스트
        broadcast(clients, message, client_socket);
    }

    close(client_socket);
    pthread_exit(NULL);
}

//메시지를 브로드캐스트하는 함수
void broadcast(Client *clients, char *message, int sender_socket) {
    pthread_mutex_lock(&mutex);


    for (int i = 0; i < MAX_CLIENTS; ++i) {
        if (clients[i].client_socket != 0 && clients[i].client_socket != sender_socket) {
            //현재 시간 출력
            time_t raw_time;
            struct tm *time_info;
            time(&raw_time);
            time_info = localtime(&raw_time);

            char formatted_time[20];
            strftime(formatted_time, sizeof(formatted_time), "[%H:%M:%S]", time_info);

            //포맷된 메시지 생성
            char formatted_message[MAX_MESSAGE_LENGTH + 30];
            sprintf(formatted_message, "%s %s", clients, message);
            
            //클라이언트로 메시지 전송
            send(clients[i].client_socket, formatted_message, strlen(formatted_message), 0);
        }
    }

    pthread_mutex_unlock(&mutex);
}

 

 

- 시연 영상

 

728x90