본문 바로가기
-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 26: C++로 네트워크 프로그래밍 (소켓 프로그래밍)

by cogito21_cpp 2024. 8. 1.
반응형

소켓 프로그래밍 소개

소켓 프로그래밍은 네트워크를 통해 통신하는 소프트웨어를 작성하는 기술입니다. C++에서는 Berkeley 소켓 API를 사용하여 소켓 프로그래밍을 구현할 수 있습니다. 오늘은 TCP/IP 소켓을 사용하여 간단한 클라이언트-서버 애플리케이션을 구현해보겠습니다.

 

TCP 서버 구현

1. 서버 소켓 설정

TCP 서버는 클라이언트 연결을 수락하고 데이터를 주고받습니다. 서버는 다음 단계를 통해 구현됩니다.

  1. 소켓 생성
  2. 소켓 바인딩
  3. 소켓 리스닝
  4. 클라이언트 연결 수락
  5. 데이터 송수신

tcp_server.cpp

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};

    // 1. 소켓 생성
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 소켓 바인딩
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 3. 소켓 리스닝
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    std::cout << "Server listening on port " << PORT << std::endl;

    // 4. 클라이언트 연결 수락
    if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 5. 데이터 송수신
    int valread = read(new_socket, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;
    const char* hello = "Hello from server";
    send(new_socket, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;

    close(new_socket);
    close(server_fd);
    return 0;
}

 

이 코드는 간단한 TCP 서버를 구현합니다. 클라이언트의 연결을 수락하고 메시지를 수신한 후 응답을 전송합니다.

 

TCP 클라이언트 구현

TCP 클라이언트는 서버에 연결하고 데이터를 주고받습니다.

 

tcp_client.cpp

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};

    // 1. 소켓 생성
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "Socket creation error" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 2. 서버 주소 설정
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported" << std::endl;
        return -1;
    }

    // 3. 서버에 연결
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection Failed" << std::endl;
        return -1;
    }

    const char* hello = "Hello from client";
    send(sock, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;

    int valread = read(sock, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;

    close(sock);
    return 0;
}

 

이 코드는 간단한 TCP 클라이언트를 구현합니다. 서버에 연결하여 메시지를 전송하고 응답을 수신합니다.

 

실습 문제

문제 1: 멀티클라이언트 서버 구현

여러 클라이언트의 연결을 동시에 처리할 수 있는 멀티클라이언트 서버를 구현하세요.

 

해설:

멀티클라이언트 서버는 여러 클라이언트의 요청을 동시에 처리하기 위해 멀티스레딩을 사용할 수 있습니다. 다음 코드는 멀티스레딩을 사용하여 여러 클라이언트를 처리하는 서버를 구현합니다.

 

tcp_multiclient_server.cpp

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>

#define PORT 8080

void handle_client(int new_socket) {
    char buffer[1024] = {0};
    int valread = read(new_socket, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;
    const char* hello = "Hello from server";
    send(new_socket, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;
    close(new_socket);
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    std::vector<std::thread> threads;

    // 1. 소켓 생성
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 소켓 바인딩
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 3. 소켓 리스닝
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    std::cout << "Server listening on port " << PORT << std::endl;

    while (true) {
        // 4. 클라이언트 연결 수락
        if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }
        // 5. 새로운 스레드에서 클라이언트 처리
        threads.emplace_back(handle_client, new_socket);
    }

    // 모든 스레드 종료 대기
    for (auto& th : threads) {
        if (th.joinable()) {
            th.join();
        }
    }

    close(server_fd);
    return 0;
}

 

이 코드는 클라이언트의 연결을 수락하고, 각각의 클라이언트를 처리하기 위해 새로운 스레드를 생성합니다. 여러 클라이언트를 동시에 처리할 수 있습니다.

 

TCP 서버와 클라이언트 빌드 및 실행

1. 서버 빌드 및 실행

g++ tcp_multiclient_server.cpp -o server -lpthread
./server

 

2. 클라이언트 빌드 및 실행

g++ tcp_client.cpp -o client
./client

 

여러 터미널에서 클라이언트를 실행하여 멀티클라이언트 서버의 동작을 확인할 수 있습니다.

 

이제 26일차의 학습을 마쳤습니다. C++로 소켓 프로그래밍을 사용하여 TCP 서버와 클라이언트를 구현하고, 멀티클라이언트를 처리하는 방법을 학습하였습니다.

 

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "C++에서의 데이터베이스 연동 (SQLite, MySQL)"에 대해 학습하겠습니다.

반응형