[DEVELOG] 콘솔 채팅 프로그램 2 - Client 코딩

이성훈·2023년 3월 31일
0

DEVELOG

목록 보기
14/14

이번에는 Server프로그램에 맞춰서 Client프로그램을 만들어보자.
이번에도 아래의 5가지 단계를 거쳐서 코드를 짜게 된다.

윈속초기화 -> 소켓생성 -> 통신 -> 소켓닫기 -> 윈속종료

먼저 필요한 라이브러리를 링크해주자.

#include <iostream>
#include <thread>
#include <string>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

또, 서버 프로그래밍에서 했듯이 사용할 객체를 선언해주고 초기화해주자.

WSADATA wsa;
SOCKET server_socket;
sockaddr_in server_addr;

if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){
    return 1;
}

그리고 서버와 연결할 소켓을 설정해주자.

server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_socket == INVALID_SOCKET){
    return 1;
}

server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //서버 주소
server_addr.sin_port = htons(8888); //서버 포트

다음으로 서버에 연결요청을 보낸다.

if (connect(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR){
    printf("서버 연결 실패\n");
    return 1;
}

std::string username;
printf("서버에서 사용할 닉네임: ");
std::getline(std::cin, username);

//서버로부터 메시지를 받도록 스레드 생성
std::thread t(receive_thread, server_socket);

//사용자로부터 메시지 입력을 받음
while(true){
...
}

t.join(); //스레드 실행
closesocket(server_socket);
WSACleanup();

return 0;

클라이언트는 서버에 연결성공시, 사용할 닉네임을 지정가능하다.
(아래 코드에서 메시지를 전송할때 사용할예정)
또 서버 프로그램에서는 클라이언트로부터 메시지를 받는 함수를 독립적인 스레드로 실행시켰는데(자동소멸되도록 detach로 제어권한을 해제시켰음)

이번에는 join을 이용하여 서버로부터 메시지를 받는(브로드캐스팅) 스레드의 제어권한을 메인스레드가 갖도록 하고 있다.
나중에 필요한 에러제어 기능이있다면 추가할예정이다.


그럼 사용자로 부터 메시지 입력받는 무한루프 부분을 살펴보자.

std::getline(std::cin, message);//메시지 입력

if (message == "/quit"){//종료 메시지 입력시 종료
        break;
}

//유저이름을 포함하여 서버로 전달
message = "[" + username + "]: " + message; 
send(server_socket, message.c_str(), message.size(), 0);

좀전에 입력한 유저이름을 포함해서 서버소켓을 통해
send함수로 정보를 전송하는 모습이다.
다만 현재는 '/quit'입력을 받으면 종료시키는데, 이후에 명령어기능을 추가할 예정이다.

마지막으로 서버로부터 메시지를 받는 함수를 살펴보자.

void receive_thread(SOCKET server_socket){
    char buffer[BUFFER_SIZE];
    int recv_size;

    while (true){
        recv_size = recv(server_socket, buffer, BUFFER_SIZE, 0);
        if (recv_size == 0 || recv_size == SOCKET_ERROR){
            break;
        }

        buffer[recv_size] = '\0';
        printf("%s\n", buffer);
    }
}

이 함수의 모습도 서버프로그래밍에서 다룬것과 유사하다.

서버프로그램과 클라이언트 프로그램을 작동시키면 아래와 같다.


실제로 ./quit을 입력하면 무한루프가 멈춰서 더이상의 메시지입력은 안받는데 실제 프로그램은 종료되지않는다. 아마도 return 0 으로 간단하게 바꾸면 될듯하다.

앞으로 추가할 기능들에는 다음과 같다.

  • 공백 메시지는 표기 X
  • 클라이언트 연결종료 표시
  • 채팅시간 로그 제공
  • 하나의 PC에서 여러대 접속 불가능 등등

이런 기능들을 하나씩 추가하면서 간단하지만 쓸모있는? 윈도우소켓 프로그램을 완성시킬 예정이다!

아래는 클라이언트 프로그램의 전체 소스코드이다.

#include <iostream>
#include <thread>
#include <string>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

const int BUFFER_SIZE = 1024;

void receive_thread(SOCKET server_socket){
    char buffer[BUFFER_SIZE];
    int recv_size;

    while (true){
        recv_size = recv(server_socket, buffer, BUFFER_SIZE, 0);
        if (recv_size == 0 || recv_size == SOCKET_ERROR){
            break;
        }

        buffer[recv_size] = '\0';
        printf("%s\n", buffer);
    }
}

int main(){
    //사용할 객체들
    WSADATA wsa;
    SOCKET server_socket;
    sockaddr_in server_addr;

    //소켓통신을 위한 윈속 라이브러리 초기화
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){
        printf("윈속 연결 실패\n");
        return 1;
    }printf("기본 설정 완료\n");

    //서버와 연결할 소켓 생성(클라이언트 소켓이지)
    server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket == INVALID_SOCKET){
        printf("소켓 연결 실패\n");
        return 1;
    }printf("소켓 연결 성공\n");

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //서버 주소
    server_addr.sin_port = htons(8888); //서버 포트

    //서버에 연결요청
    if (connect(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR){
        printf("서버 연결 실패\n");
        return 1;
    }printf("서버 연결 성공\n");

    std::string username;
    printf("서버에서 사용할 닉네임: ");
    std::getline(std::cin, username);

    //서버로부터 메시지를 받도록 스레드 생성
    std::thread t(receive_thread, server_socket);

    printf("채팅을 시작합니다.\n");
    std::string message;

    //사용자로부터 메시지 입력을 받음
    while (true){
        std::getline(std::cin, message);//메시지 입력

        if (message == "/quit"){//종료 메시지 입력시 종료
            return 0;
        }

        //유저이름을 포함하여 서버로 전달
        message = "[" + username + "]: " + message; 
        send(server_socket, message.c_str(), message.size(), 0);
    }

    t.join(); //스레드 실행

    //클라이언트 종료시
    closesocket(server_socket);
    WSACleanup();

    return 0;
}
profile
I will be a socially developer

0개의 댓글