본문 바로가기
Linux

채팅 프로그램 (소켓 프로그래밍, C언어, 리눅스)

by hyonisera 2024. 10. 17.

Socket이란? TCP/IP를 쉽게 구현해주는 라이브러리 함수

 

VirtualBox 우분투 상에서 스레드를 사용하지 않고 소켓을 사용하여 C언어 채팅 프로그램 만들기

 

● 한 가상머신 내에서 서버와 클라이언트 1대1 통신

// server.c

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

#define PORT 8080
#define BUFFER_SIZE 1024

int new_socket;

void *handle_client(int socket) {
    char buffer[BUFFER_SIZE];

    while (1) {
        memset(buffer, 0, BUFFER_SIZE);
        // 클라이언트로부터 메시지 수신
        int bytes_received = recv(socket, buffer, BUFFER_SIZE, 0);
        if (bytes_received <= 0) {
            printf("\nClient disconnected.\n");
            break;
        }
        // 클라이언트 메시지 출력
        printf("\n%s", buffer);
    }
    return NULL;
}

int main() {
    int server_fd;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE];

    // 소켓 생성
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed");
        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");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 연결 대기
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Waiting for a connection...\n");

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

    printf("Connection established.\n");

    // 클라이언트 핸들링을 위한 프로세스 생성
    if (fork() == 0) {
        handle_client(new_socket);
        exit(0);
    }

    // 메인 서버 루프
    while (1) {
        memset(buffer, 0, BUFFER_SIZE);
        // 서버 메시지 입력
        fgets(buffer, BUFFER_SIZE, stdin);

        // 클라이언트로 메시지 전송
        send(new_socket, buffer, strlen(buffer), 0);
    }

    close(new_socket);
    close(server_fd);
    return 0;
}
// client.c

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

#define PORT 8080
#define BUFFER_SIZE 1024

void *receive_messages(int sock) {
    char buffer[BUFFER_SIZE];

    while (1) {
        memset(buffer, 0, BUFFER_SIZE);
        // 서버로부터 메시지 수신
        int bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
        if (bytes_received <= 0) {
            printf("\nServer disconnected.\n");
            break;
        }
        // 서버 메시지 출력
        printf("\n%s", buffer);
    }
    return NULL;
}

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

    // 소켓 생성
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    // 서버 주소 설정
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 서버 IP 주소 설정 (로컬호스트)
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 서버에 연결
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    printf("Connected to the server.\n");

    // 서버로부터 메시지를 수신하는 프로세스 생성
    if (fork() == 0) {
        receive_messages(sock);
        exit(0);
    }

    // 채팅 시작
    while (1) {
        // 클라이언트 메시지 입력
        fgets(buffer, BUFFER_SIZE, stdin);

        // 서버로 메시지 전송
        send(sock, buffer, strlen(buffer), 0);
    }

    close(sock);
    return 0;
}

 

VirtualBox 우분투 설정에서 호스트 전용 어댑터 활성화

 

gedit server.c

gcc -o server server.c

./server

 

(다른 터미널에서)

gedit client.c

gcc -o client client.c

./client

실행화면

● 옆 컴퓨터와 통신

VirtualBox 우분투 설정에서 어댑터에 브리지 활성화

windows defender 방화벽 → 고급설정 → 인바운드 규칙 → 파일 및 프린터 공유(에코 요청 - ICMPv4-In) 사용 예로 바꾸기(= 규칙 사용)

 

(ping을 찍어서 윈도우와 연결이 되는지 확인)

sudo ifconfig enp0s9 192.168.22.13

ping 192.168.22.13

 

VirtualBox 우분투는 서버, 윈도우는 클라이언트로 동작하게 하려면 우선 gcc 컴파일러를 윈도우 상에서 실행하기 위해 MinGW를 설치한 후, Visual Studio에서 client.c를 윈도우 상에서 실행되도록 winsock 헤더파일을 사용하는 등 변경하고 IP 주소도 변경해야 한다.

// client.c (Windows)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#define PORT 8080
#define BUFFER_SIZE 1024

void* receive_messages(SOCKET sock) {
    char buffer[BUFFER_SIZE];

    while (1) {
        memset(buffer, 0, BUFFER_SIZE);

        int bytes_received = recv(sock, buffer, BUFFER_SIZE, 0);
        if (bytes_received <= 0) {
            printf("Server disconnected.\n");
            break;
        }

        printf("%s", buffer);
    }
    return NULL;
}

DWORD WINAPI thread_func(LPVOID param) {
    SOCKET sock = *(SOCKET*)param;
    receive_messages(sock);
    return 0;
}

int main() {
    WSADATA wsaData;
    SOCKET sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE];

    // Initialize Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed.\n");
        return -1;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        printf("Socket creation error\n");
        WSACleanup();
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr("192.168.56.103");  // 서버 IP 주소로 변경

    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
        printf("Connection Failed\n");
        closesocket(sock);
        WSACleanup();
        return -1;
    }

    printf("Connected to the server.\n");

    // Create a thread to receive messages
    HANDLE thread = CreateThread(NULL, 0, thread_func, &sock, 0, NULL);
    if (thread == NULL) {
        printf("Thread creation failed.\n");
        closesocket(sock);
        WSACleanup();
        return -1;
    }

    while (1) {
        fgets(buffer, BUFFER_SIZE, stdin);
        send(sock, buffer, strlen(buffer), 0);
    }

    closesocket(sock);
    WSACleanup();
    return 0;
}

윈도우 cmd창에서 컴파일 후 우분투 서버를 실행하고 윈도우 클라이언트 실행

gcc -o client client.c -lws2_32

client.exe

우분투 가상머신과 윈도우 간의 채팅 통신
우분투 가상머신과 라즈베리파이 가상머신 간의 채팅 통신