C 언어를 이용한 소켓 프로그래밍과 클라이언트-서버 모델

C 언어를 이용한 소켓 프로그래밍과 클라이언트-서버 모델 

컴퓨터 통신에서 기본이되며, TCP 통신에서 쌍을 이루어 존재하는 모델인 클라이언트/서버 모델을 알아보고자 합니다. 클라이언트의 코드 방식과 서버에서 선언되는 코드 방식을 각각 구분하여 설명하여 보겠습니다. 네트워크 프로그래밍을 처음 접하는 개발자들에게 쉽게 접근할 수 있도록 상세하게 설명하도록 하겠습니다.

C 언어에서 소켓(Socket)과 그 사용 방법에 대한 개요

C 언어에서 소켓(Socket)은 네트워크 통신을 위한 추상화된 개념으로, 인터넷 프로토콜을 사용하여 데이터를 주고받는 데 사용됩니다. 소켓은 송신자와 수신자 간에 데이터를 교환할 수 있는 양방향 통신 채널을 제공합니다. 이러한 통신 채널은 네트워크 상에서 프로세스 간에 통신할 수 있도록 연결을 제공하며, 송신자는 소켓을 통해 데이터를 전송하고, 수신자는 소켓을 통해 데이터를 수신합니다. C 언어에서 소켓을 사용하기 위해서는 먼저 소켓을 생성해야 합니다. 소켓 생성에는 socket() 함수를 사용하며, 소켓 해지에는 close() 함수를 사용합니다. 이러한 함수들은 <sys/socket.h> 헤더 파일에 선언되어 있습니다. C 언어에서 소켓의 생성과 해제는 다음과 같이 수행됩니다.

    1. 소켓 생성

    • socket() 함수를 사용하여 소켓을 생성합니다. 이 함수는 IP 주소 체계와 프로토콜을 지정하여 소켓을 생성합니다. 예를 들어, IPv4와 TCP 프로토콜을 사용하는 소켓을 생성하려면 다음과 같이 호출합니다.

            int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    • 여기서 sockfd는 소켓 파일 디스크립터를 나타내며, AF_INET은 IPv4 체계를 의미하고, SOCK_STREAM은 TCP 프로토콜을 의미합니다.
    • C 언어에서 소켓을 생성하는 함수인 socket() 함수를 호출하는 코드입니다. 이 때, 함수에 전달되는 세 개의 파라미터는 다음과 같은 의미를 갖습니다.
      • AF_INET: 소켓이 사용할 프로토콜 체계를 지정합니다. AF_INET은 IPv4 인터넷 프로토콜을 사용하겠다는 의미입니다.
      • SOCK_STREAM: 소켓이 사용할 소켓 유형을 지정합니다. SOCK_STREAM은 TCP를 사용하는 연결 지향형 소켓을 사용하겠다는 의미입니다.
      • 0: 프로토콜 체계에 따라 사용할 프로토콜을 선택합니다. 일반적으로 0 값을 사용하여 기본값을 지정합니다.
    • 위의 코드는 IPv4 인터넷 프로토콜을 사용하고, TCP를 사용하는 연결 지향형 소켓을 생성하며, 프로토콜 체계에 따라 기본값을 사용한다는 의미입니다.
    • 소켓 프로토콜 패밀리는 소켓의 주소 체계를 결정하는 상수 값으로, 다음과 같은 종류가 있습니다.
      • AF_INET : IPv4 인터넷 프로토콜을 사용하는 주소 체계
      • AF_INET6 : IPv6 인터넷 프로토콜을 사용하는 주소 체계
      • AF_UNIX : 유닉스 프로세스간 통신을 위한 로컬 주소 체계
      • AF_IPX : IPX 네트워크 프로토콜을 사용하는 주소 체계
      • AF_NETLINK : 커널과 통신하기 위한 프로토콜을 사용하는 주소 체계
      • AF_PACKET : 네트워크 인터페이스를 통해 직접적인 패킷 송수신을 위한 주소 체계
    • 소켓의 type 종류는 크게 두 가지로 구분할 수 있습니다.
      • SOCK_STREAM (스트림 소켓): 양방향, 연결 지향적인 전송 방식. 데이터를 연속적인 데이터 스트림으로 취급하며, 오류 검사와 재전송을 제공합니다. TCP 소켓과 대응됩니다.
      • SOCK_DGRAM (데이터그램 소켓): 데이터그램 단위로 전송하는 방식. 신뢰성 보다는 속도와 실시간 처리에 초점을 둡니다. UDP 소켓과 대응됩니다.
    2. 소켓 해제

    • 소켓을 사용한 후에는 close() 함수를 사용하여 소켓을 해제해야 합니다. 이 함수는 소켓 파일 디스크립터를 인자로 받아 해당 소켓을 해제합니다. 예를 들어, sockfd가 소켓 파일 디스크립터인 경우 다음과 같이 호출합니다.

            close(sockfd);

C 언어에서 socket() 함수의 반환값과 소켓 디스크립터의 역할

socket() 함수의 반환값은 소켓 디스크립터(socket descriptor)입니다. 소켓 디스크립터는 이후에 소켓 함수를 호출할 때 소켓을 식별하는 역할을 합니다. 따라서 소켓 함수를 호출하기 전에 socket() 함수를 호출하여 소켓 디스크립터를 먼저 생성해야 합니다. 소켓 디스크립터는 양수의 정수값을 가지며, 0 또는 음수값을 반환하는 경우는 소켓 생성에 실패했을 때입니다. 소켓 생성이 실패했을 때, socket() 함수는 -1을 반환합니다. 이때 errno 변수에 실패 원인을 나타내는 정수값이 설정됩니다. 실패 원인은 perror() 함수를 통해 확인할 수 있습니다. errno의 값은 다음과 같습니다.

  • EACCES: 작업 수행 권한 없음
  • EAFNOSUPPORT: 지정된 주소 체계 지원 안됨
  • EINVAL: 잘못된 인자 전달
  • EMFILE: 프로세스의 소켓 생성 제한 초과
  • ENFILE: 시스템의 소켓 생성 제한 초과
  • ENOBUFS, ENOMEM: 메모리 부족
  • EPROTONOSUPPORT: 지원하지 않는 프로토콜 타입 지정
  • EFAULT: 인자로 전달된 포인터가 유효하지 않음

C 언어에서 sockaddr 구조체와 소켓 주소의 역할

sockaddr은 소켓 주소를 저장하기 위한 구조체입니다. 일반적으로 IPv4의 경우 sockaddr_in 구조체, IPv6의 경우 sockaddr_in6 구조체를 사용합니다. 이 구조체는 소켓 주소를 표현하기 위해 IP 주소, 포트 번호 등의 정보를 저장합니다. 이 구조체는 소켓 주소를 설정하는데 사용되며, 네트워크 통신에서 필수적인 요소 중 하나입니다. sockaddr_in 구조체는 다음과 같은 멤버를 가집니다.

        struct sockaddr_in {

            short sin_family; // 주소 체계

            unsigned short sin_port; // 포트 번호

            struct in_addr sin_addr; // IP 주소

            char sin_zero[8]; // 채움

        };

  • sin_family: 주소 체계를 지정합니다. AF_INET은 IPv4 주소 체계를, AF_INET6은 IPv6 주소 체계를 나타냅니다.
  • sin_port: 포트 번호를 저장합니다. htons() 함수를 사용하여 호스트 바이트 순서를 네트워크 바이트 순서로 변환해야 합니다.
  • sin_addr: IP 주소를 저장합니다. in_addr 구조체를 사용합니다.
  • sin_zero: 구조체 크기를 맞추기 위해 채워진 8바이트 필드입니다.

sockaddr_in6 구조체는 다음과 같은 멤버를 가집니다.

        struct sockaddr_in6 {

            uint16_t sin6_family; // 주소 체계

            uint16_t sin6_port; // 포트 번호

            uint32_t sin6_flowinfo; // 흐름 정보

            struct in6_addr sin6_addr; // IPv6 주소

            uint32_t sin6_scope_id; // 범위 식별자

        };

  • sin6_family: 주소 체계를 지정합니다. AF_INET6은 IPv6 주소 체계를 나타냅니다.
  • sin6_port: 포트 번호를 저장합니다. htons() 함수를 사용하여 호스트 바이트 순서를 네트워크 바이트 순서로 변환해야 합니다.
  • sin6_flowinfo: 흐름 정보를 저장합니다.
  • sin6_addr: IPv6 주소를 저장합니다. in6_addr 구조체를 사용합니다.
  • sin6_scope_id: 범위 식별자를 저장합니다.

이러한 구조체는 네트워크 통신에서 소켓 주소를 설정하는 데 사용됩니다. 소켓 주소는 IP 주소와 포트 번호의 조합으로 이루어지며, 이를 통해 데이터를 송수신하는 대상을 명확히 식별할 수 있습니다.

IPv4와 IPv6에 대한 비교

IPv4와 IPv6는 인터넷 프로토콜(IP) 주소 체계의 버전을 나타냅니다. 다음은 IPv4와 IPv6의 주요 비교 요소입니다.

  • 주소 길이: IPv4 주소는 32비트로 구성되어 있어 총 4,294,967,296(약 43억)개의 주소를 표현할 수 있습니다. 반면, IPv6 주소는 128비트로 구성되어 있어 총 340,282,366,920,938,463,463,374,607,431,768,211,456(약 3.4 × 10^38)개의 주소를 표현할 수 있습니다. 이는 IPv6가 주소 부족 문제를 해결하기 위해 개발되었음을 의미합니다.
  • 주소 형식: IPv4 주소는 "x.x.x.x" 형식으로 4개의 8비트 숫자로 표현됩니다. 예를 들면, "192.168.0.1"과 같은 형태입니다. 반면, IPv6 주소는 "x:x:x:x:x:x:x:x" 형식으로 8개의 16비트 숫자로 표현됩니다. 또는 긴 주소의 경우, 16진수로 압축하여 "::"로 표시할 수도 있습니다. 예를 들면, "2001:0db8:85a3::0370:7334"와 같은 형태입니다.
  • 주소 할당: IPv4 주소는 보통 동적으로 할당되거나 정적으로 구성됩니다. 대부분의 IPv4 주소는 인터넷 서비스 제공자(ISP)에 의해 관리되며, 주소 부족 문제로 인해 고갈될 수 있습니다. IPv6 주소는 보통 프리픽스로부터 자동으로 생성되며, 더 많은 주소 공간을 활용할 수 있습니다.
  • 보안과 인증: IPv6는 기본적으로 IPsec(인터넷 프로토콜 보안)을 지원하여 데이터의 안전한 전송을 촉진합니다. 이는 IPv6에서 더욱 향상된 보안 기능을 제공한다는 의미입니다. 반면, IPv4는 별도의 구성이 필요한 경우에만 IPsec을 지원합니다.
  • 네트워크 구조: IPv6는 주소 부족 문제를 해결하기 위해 개발되었으며, 효율적인 라우팅, 네트워크 구성, 자동 구성 등의 기능을 갖추고 있습니다. IPv6는 IPv4에 비해 개선된 QoS(Quality of Service) 지원과 멀티캐스트 기능도 제공합니다.
  • 호환성: IPv6는 IPv4와 호환성을 유지하면서 전환할 수 있는 메커니즘을 제공합니다. 이를 통해 IPv4 네트워크와 IPv6 네트워크가 동시에 운영될 수 있습니다.

IPv4와 IPv6는 현재 인터넷에서 병행해서 사용되고 있으며, IPv6의 도입이 점차 증가하고 있습니다. IPv6는 주소 공간 부족 문제를 해결하고 미래의 인터넷 확장을 위해 중요한 역할을 합니다.

클라이언트 서버 모델과 소켓을 이용한 통신 방법에 대한 설명

클라이언트와 서버는 소켓을 사용하여 통신합니다. 일반적으로 클라이언트는 서버에 연결 요청을 보내고, 서버는 이 요청을 받아들여 연결을 맺습니다. 이후에 클라이언트와 서버는 소켓을 통해 데이터를 주고받습니다. 소켓은 네트워크 통신을 위한 인터페이스를 제공하며, 송신자와 수신자 간의 양방향 통신 채널을 제공합니다. 클라이언트와 서버는 각각 소켓을 생성하여 통신을 수행합니다. 클라이언트 소켓은 서버에 연결 요청을 보내고, 서버 소켓은 이 요청을 받아들여 클라이언트 소켓과 연결을 맺습니다. 이후에 클라이언트 소켓과 서버 소켓은 데이터를 주고받을 수 있습니다. 소켓을 이용한 클라이언트 서버 생성 과정은 다음과 같습니다.

  1. 서버 소켓 생성: 서버는 클라이언트의 접속 요청을 받기 위해 먼저 소켓을 생성합니다. 이때 생성되는 소켓을 서버 소켓이라고 합니다. 서버 소켓은 클라이언트의 연결 요청을 대기하기 위한 상태로 설정되며, 클라이언트의 접속 요청을 받기 위해 listen() 함수를 호출합니다.
  2. 클라이언트 소켓 생성: 클라이언트는 서버와 통신하기 위해 소켓을 생성합니다. 이때 생성되는 소켓을 클라이언트 소켓이라고 합니다. 클라이언트는 connect() 함수를 호출하여 서버에 접속합니다.
  3. 연결 수립: 클라이언트 소켓이 서버 소켓에 접속 요청을 보내면, 서버 소켓은 accept() 함수를 호출하여 클라이언트의 접속 요청을 수락합니다. 이렇게 서버 소켓과 클라이언트 소켓 간에 연결이 수립되면, 서버와 클라이언트는 데이터를 주고받을 수 있게 됩니다.
  4. 데이터 송수신: 연결이 수립된 후에는, 클라이언트와 서버는 send() 함수와 recv() 함수를 사용하여 데이터를 송수신할 수 있습니다.
  5. 연결 해제: 데이터 송수신이 모두 완료되면, 클라이언트 또는 서버는 close() 함수를 사용하여 소켓을 해제합니다. 이때, 상대방 소켓과의 연결도 끊어지게 됩니다.

클라이언트 서버 모델의 예로는 웹 브라우저와 웹 서버 간의 통신이 있습니다. 웹 브라우저는 클라이언트 역할을 하며, 웹 서버는 서버 역할을 합니다. 웹 브라우저는 HTTP 요청을 보내고, 웹 서버는 HTTP 응답을 반환합니다. 이러한 요청과 응답 과정에서 클라이언트와 서버는 소켓을 사용하여 통신합니다. 또한, 클라이언트 서버 모델은 다른 많은 네트워크 응용 프로그램에서도 사용됩니다.

클라이언트 서버 모델의 예시

클라이언트-서버 모델은 네트워크 상에서 서비스 제공자인 서버와 이 서비스를 요청하는 클라이언트 간의 상호작용을 기반으로 합니다. 다양한 분야에서 클라이언트-서버 모델이 사용되며, 아래는 다섯 가지 예시입니다.

  • 웹 서버: 클라이언트(웹 브라우저)가 웹 페이지를 요청하면 서버가 해당 웹 페이지를 제공하는 모델입니다. 클라이언트는 HTTP 요청을 보내고, 서버는 해당 요청을 처리하여 클라이언트에게 웹 페이지를 응답합니다.
  • 이메일 서버: 클라이언트(이메일 클라이언트)가 이메일을 보내거나 받기 위해 이메일 서버와 통신하는 모델입니다. 클라이언트는 SMTP(Simple Mail Transfer Protocol)를 사용하여 이메일을 서버로 전송하고, POP3(Post Office Protocol 3) 또는 IMAP(Internet Message Access Protocol)을 사용하여 서버에서 이메일을 가져옵니다.
  • 파일 서버: 클라이언트가 파일을 업로드하거나 다운로드하기 위해 파일 서버와 통신하는 모델입니다. 클라이언트는 파일 전송 프로토콜(예: FTP, SFTP)를 사용하여 서버와 파일을 주고받습니다.
  • 데이터베이스 서버: 클라이언트가 데이터를 조회, 추가, 수정, 삭제하기 위해 데이터베이스 서버와 상호작용하는 모델입니다. 클라이언트는 SQL(Structured Query Language) 쿼리를 사용하여 데이터베이스 서버에 요청을 보내고, 서버는 해당 요청을 처리하여 결과를 클라이언트에게 반환합니다.
  • 게임 서버: 멀티플레이어 게임에서 클라이언트들이 게임 서버와 통신하여 게임을 진행하는 모델입니다. 클라이언트는 게임 서버와 네트워크를 통해 상호작용하고, 서버는 게임의 규칙 및 상태를 관리하고 클라이언트들 간의 통신을 조율합니다.

이는 클라이언트-서버 모델의 일반적인 예시 중 일부이며, 다양한 분야에서 이 모델이 적용될 수 있습니다.

클라이언트-서버 모델의 단점: 중앙 집중화, 확장성 제한, 네트워크 부하, 상태 관리, 의존성

클라이언트-서버 모델은 널리 사용되는 네트워크 아키텍처입니다. 하지만 몇 가지 단점도 있습니다. 주요한 클라이언트-서버 모델의 단점은 다음과 같습니다.

  • 단일 장애 지점: 클라이언트-서버 모델에서는 서버가 중앙 집중화되어 있으므로, 서버에 장애가 발생하면 전체 시스템이 영향을 받을 수 있습니다. 단일 서버의 고장은 서비스의 가용성을 저하시킬 수 있습니다. 따라서, 서버의 장애 대비를 위해 백업 서버, 로드 밸런싱 등의 조치가 필요합니다.
  • 확장성 제한: 클라이언트-서버 모델에서는 서버가 중앙 집중화되어 있으므로, 대량의 클라이언트 요청이 동시에 발생하면 서버의 처리 능력에 한계가 있을 수 있습니다. 이로 인해 확장성이 제한될 수 있으며, 클라이언트 수가 증가하면 서버 리소스를 적절히 조정해야 합니다.
  • 네트워크 부하: 클라이언트-서버 모델에서는 클라이언트가 서버에 요청을 보내고, 서버는 클라이언트에 응답을 반환합니다. 이 과정에서 네트워크 트래픽이 발생하며, 대량의 클라이언트 요청이 동시에 발생하면 네트워크 부하가 증가할 수 있습니다. 이는 대역폭 문제나 지연 시간의 증가로 이어질 수 있습니다.
  • 상태 관리: 클라이언트-서버 모델에서는 서버가 상태를 관리하고 클라이언트는 서버에 요청하여 정보를 얻습니다. 이로 인해 서버 측에서 클라이언트의 상태를 추적하고 관리해야 하며, 클라이언트와 서버 간의 일관성 유지에 대한 문제가 발생할 수 있습니다.
  • 의존성: 클라이언트-서버 모델에서는 클라이언트와 서버 간에 의존성이 존재합니다. 클라이언트는 서버에 의존하여 서비스를 이용하며, 서버는 클라이언트의 요청에 응답합니다. 이로 인해 클라이언트와 서버 간의 상호작용이 중요하며, 한 측의 문제나 변경이 다른 측에 영향을 줄 수 있습니다.

이러한 단점들은 분산 시스템이나 마이크로서비스 아키텍처 등 다른 아키텍처 모델을 고려할 때 고려해야 할 요소입니다.

클라이언트와 서버 모델의 장점: 분리된 역할, 확장성, 중앙 집중화, 자원 공유, 유지보수 용이성, 플랫폼 독립성

클라이언트와 서버 모델은 다음과 같은 장점을 가지고 있습니다.

  • 분리된 역할: 클라이언트와 서버는 각각 명확히 정의된 역할을 수행합니다. 클라이언트는 사용자 인터페이스와 상호작용하고 요청을 생성하며, 서버는 클라이언트의 요청을 받아 처리하고 필요한 데이터나 서비스를 제공합니다. 이 역할 분리는 시스템의 구조와 유지보수를 간소화합니다.
  • 확장성: 클라이언트와 서버 모델은 수평 및 수직 확장이 가능합니다. 서버의 부하가 증가하면 추가 서버를 도입하여 부하를 분산시킬 수 있으며, 클라이언트 수도 유연하게 증가할 수 있습니다. 이로써 시스템은 더 많은 요청과 트래픽을 처리할 수 있게 됩니다.
  • 중앙 집중화: 서버는 데이터와 비즈니스 로직을 중앙에서 관리하므로 일관성과 보안을 유지할 수 있습니다. 중앙 집중화된 서버는 데이터베이스, 인증, 권한 부여 등을 관리하고, 클라이언트는 이러한 기능을 이용하여 보다 안정적이고 안전한 서비스를 제공받을 수 있습니다.
  • 자원 공유: 서버는 다수의 클라이언트에게 공통된 자원과 서비스를 제공할 수 있습니다. 예를 들어, 파일 서버는 여러 클라이언트가 파일을 공유하고 엑세스할 수 있도록 합니다. 이를 통해 자원의 효율적인 활용과 데이터의 일관성을 유지할 수 있습니다.
  • 유지보수 및 업그레이드 용이성: 클라이언트와 서버는 독립적으로 개발, 업데이트, 유지보수할 수 있습니다. 새로운 기능이나 버그 수정이 필요한 경우, 서버 측에서 업데이트를 수행하고 클라이언트는 업데이트된 서비스에 접속하여 이점을 활용할 수 있습니다.
  • 플랫폼 독립성: 클라이언트와 서버는 독립적으로 운영되므로, 클라이언트는 다양한 플랫폼에서 실행될 수 있습니다. 이는 다양한 기기와 운영 체제를 사용하는 사용자들에게 서비스를 제공하는 데 유리합니다

마무리

클라이언트/서버 모델의 개념과 실제 개발에서 코드 구성의 방법이 어떻게 이루어지는지, 실제로 사용되는 예시를 들어 이해를 쉽게 해봤습니다. 이 모델을 이용했을 때의 장점도 여러가지을 살펴봤는데, 실제 개발 환경에서 유용하고 의미 있게 사용을 했으면 하는 바램입니다.

댓글 쓰기

다음 이전