0200

학교

[컴퓨터 네트워크] Socket Programming (Part 1) - (2)

5. UDP Socket Programming

[1] UDP Server/Client Function Call

1) UDP Server

  1. socket() : 소켓 생성

  2. bind() : 소켓에 주소 할당

  3. recvfrom() / sendto() : 데이터 수신 및 송신

  4. close() : 소켓 닫기

2) UDP Client

  1. socket() : 소켓 생성

  2. recvfrom() / sendto() : 데이터 수신 및 송신

  3. close() : 소켓 닫기

아래는 요청하신 내용을 한국어로 번역하고 주석을 추가한 코드입니다.

[2] recvfrom()

code
#include <sys/socket.h>

// recvfrom 함수의 정의
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

1) 역할

: 소켓으로부터 메시지를 수신한다.

  • 데이터가 수신될 때까지 블록됨

2) 파라미터 설명

  • sockfd: 소켓 파일 디스크립터

  • buffer: 메시지를 저장할 버퍼를 가리키는 포인터

  • length: 버퍼에 최대 length 바이트까지 데이터를 수신

  • flags: 메시지 수신의 종류

    • 일반적인 경우 0

  • src_addr: 상대 소켓의 주소로 채워질 sockaddr 구조체에 대한 포인터

  • addrlen: 상대 주소의 실제 크기를 포함할 변수

3) 반환 값

  • 성공: 수신된 바이트 수

    • EOF의 경우 0

  • 오류: -1

4) 예제

code
// 클라이언트 주소 정보를 받아오기 위해 recvfrom 호출
str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

[3] sendto()

code
#include <sys/socket.h>

// sendto 함수의 정의
ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

1) 역할

: 소켓을 통해 메시지를 전송한다.

2) 파라미터 설명

  • sockfd: 소켓 파일 디스크립터

  • buffer: 전송할 메시지를 포함하는 버퍼

  • length: 버퍼에서 최대 length 바이트까지 데이터를 전송

  • flags: 메시지 전송의 종류

    • 일반적인 경우 0

  • dest_addr: 데이터를 전송할 주소

  • addrlen: 주소 구조체의 크기(바이트)

3) 반환 값

  • 성공: 전송된 바이트 수

  • 오류: -1

4) 예제

code
// 클라이언트 주소 정보를 사용하여 메시지를 전송
sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);

[4] 연결된 UDP

1) sendto() 함수 내부 절차

  1. UDP 소켓에 목적지 IP와 포트를 할당한다.

  2. 할당된 목적지 IP와 포트로 데이터를 전송한다.

  3. UDP 소켓에서 목적지 IP와 포트를 할당 해제한다.

2) 연결된 UDP (Connected UDP)

  • 목적지 주소가 미리 할당된 소켓을 사용할 수 있다.

    • 주소 할당과 해제 절차를 생략할 수 있다.

  • sendto()recvfrom() 대신 write()read() 함수를 사용할 수 있다.

  • 이것이 TCP처럼 연결 지향 소켓을 의미하는 것은 아니다.

3-1) connected UDP를 생성하는 방법

code
// 소켓 생성
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock == -1)
    error_handling("socket() error");

// 소켓에 주소 설정
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]); // 목적지 IP 주소 설정
serv_adr.sin_port = htons(atoi(argv[2]));      // 목적지 포트 번호 설정

// 소켓 연결
connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

3-2 코드 설명

  • 이 코드에서 connect() 함수는 UDP 소켓의 목적지 주소를 미리 설정하는 역할을 한다.

  • connect()를 사용하여 설정된 목적지 주소로만 데이터를 주고받기 때문에, 이후에는 sendto()recvfrom() 대신 write()read()를 사용할 수 있다.

  • 주의: 연결 지향형 통신을 의미하는 것이 아니라, 연결된 주소에 대해 더 간단히 데이터를 송수신할 수 있게 하는 설정이다.

4) 간단한 데이터라 함은, 텍스트 기반인가? 바이트 기반인가?

  • 간단한 데이터는 텍스트 기반과 바이트 기반 모두를 포함할 수 있다.

  • 소켓 통신에서는 데이터를 바이트 스트림으로 송수신하기 때문에, 실제로는 모든 데이터가 바이트 기반으로 전송된다.

  • 다만, 전송하고자 하는 데이터가 사람이 읽을 수 있는 텍스트 형태라면 텍스트 기반이라고 부르고, 이미지나 파일처럼 바이너리 데이터가 포함된 경우에는 바이트 기반 데이터라고 칭한다.

  • 따라서, 단순한 문자열이든 복잡한 파일이든 소켓에서는 결국 바이트 단위로 처리되며, 텍스트 기반 데이터도 내부적으로는 바이트 스트림으로 송수신되는 방식이다.

6. 소켓 옵션

[1] 소켓 옵션

  1. 일부 소켓 속성을 수정하기 위해 사용된다.

  2. 소켓 옵션을 가져오고 설정하는 세 가지 방법

    • getsockopt(), setsockopt()

    • fcntl(): 논블로킹 I/O

    • ioctl()

  3. 시스템 의존적이다.

[2] getsockopt() & setsockopt()

C++
#include <sys/types.h> // 타입 정의를 위한 헤더
#include <sys/socket.h> // 소켓 관련 함수를 위한 헤더

// 소켓 옵션을 가져오는 함수
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

// 소켓 옵션을 설정하는 함수
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t *optlen);

1) 역할

: 소켓의 옵션을 가져오고 설정한다.

2) 파라미터 설명

  1. sock: 소켓 파일 디스크립터

  2. level: 옵션이 위치한 프로토콜 레벨

  3. optname: 옵션의 이름

  4. optval: 변수를 가리키는 포인터

  5. optlen: 옵션 변수의 크기

3) 예시

C++
// 소켓 타입을 가져오기 위한 변수 선언
int sock_type; 
socklen_t optlen = sizeof(sock_type); // 옵션 변수의 크기 초기화

// 소켓 옵션을 가져오는 함수 호출
state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);

// 에러 처리
if (state)
    error_handling("getsockopt() error!");

// 가져온 소켓 타입 출력
printf("Socket type one: %d \n", sock_type);

[3] Socket Options

Protocol Level

Option Name

Get

Set

SOL_SOCKET

SO_SNDBUF

O

O

SO_RCVBUF

O

O

SO_REUSEADDR

O

O

SO_KEEPALIVE

O

O

SO_BROADCAST

O

O

SO_DONTROUTE

O

O

SO_OOBINLINE

O

O

SO_ERROR

O

X

SO_TYPE

O

X

IPPROTO_IP

IP_TOS

O

O

IP_TTL

O

O

IP_MULTICAST_TTL

O

O

IP_MULTICAST_LOOP

O

O

IP_MULTICAST_IF

O

O

IPPROTO_TCP

TCP_KEEPALIVE

O

O

TCP_NODELAY

O

O

TCP_MAXSEG

O

O

[4] SOL_SOCKET : SO_TYPE

  • SO_TYPE: 소켓의 타입을 가져옴 (예: SOCK_STREAM, SOCK_DGRAM)

1) 예시 : sock_type.c

C++
#include <stdio.h>    // 표준 입출력 헤더 파일 포함
#include <string.h>   // 문자열 처리 헤더 파일 포함
#include <sys/types.h> // 데이터 타입 헤더 파일 포함
#include <sys/socket.h> // 소켓 관련 헤더 파일 포함
#include <netinet/in.h> // 인터넷 주소 구조체 헤더 파일 포함
#include <arpa/inet.h> // IP 주소 변환 관련 헤더 파일 포함

int main() {
    int tcp_sock, udp_sock;      // TCP 소켓과 UDP 소켓을 위한 변수 선언
    int sock_type;               // 소켓 타입을 저장할 변수
    socklen_t optlen;            // 소켓 옵션의 길이를 저장할 변수
    int state;                   // 상태를 저장할 변수

    optlen = sizeof(sock_type);  // sock_type의 크기를 optlen에 저장
    tcp_sock = socket(PF_INET, SOCK_STREAM, 0); // TCP 소켓 생성
    udp_sock = socket(PF_INET, SOCK_DGRAM, 0);  // UDP 소켓 생성

    printf("SOCK_STREAM: %d \n", SOCK_STREAM); // SOCK_STREAM의 값 출력
    printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);   // SOCK_DGRAM의 값 출력

    // TCP 소켓의 타입을 가져옴
    state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    printf("Socket type one: %d \n", sock_type); // TCP 소켓 타입 출력

    // UDP 소켓의 타입을 가져옴
    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    printf("Socket type two: %d \n", sock_type); // UDP 소켓 타입 출력

    return 0; // 프로그램 종료
}

1-2) 결과

code
SOCK_STREAM: 1 
SOCK_DGRAM: 2 
Socket type one: 1 
Socket type two: 2

[5] SOL_SOCKET : SO_RCVBUF, SO_SNDBUF

  • SO_RCVBUF: 최대 소켓 수신 버퍼 크기를 바이트 단위로 가져오거나 설정함

  • SO_SNDBUF: 최대 소켓 송신 버퍼 크기를 바이트 단위로 가져오거나 설정함

1) 예시 : set_buf.c

C++
#include <stdio.h>     // 표준 입출력 헤더 파일 포함
#include <stdlib.h>    // 표준 라이브러리 헤더 파일 포함
#include <unistd.h>    // 유닉스 표준 헤더 파일 포함
#include <sys/socket.h> // 소켓 관련 헤더 파일 포함

void error_handling(char *message); // 에러 핸들링 함수 선언

int main(int argc, char *argv[])
{
    int sock;                     // 소켓을 위한 변수
    int snd_buf = 1024 * 3;      // 송신 버퍼 크기 (3KB)
    int rcv_buf = 1024 * 3;      // 수신 버퍼 크기 (3KB)
    int state;                   // 상태를 저장할 변수
    socklen_t len;               // 버퍼 크기 길이를 저장할 변수

    sock = socket(PF_INET, SOCK_STREAM, 0); // TCP 소켓 생성

    // 수신 버퍼 크기를 설정
    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
    if (state) // 설정 실패 시 에러 처리
        error_handling("setsockopt() error!");

    // 송신 버퍼 크기를 설정
    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
    if (state) // 설정 실패 시 에러 처리
        error_handling("setsockopt() error!");

    len = sizeof(snd_buf); // 송신 버퍼 크기 길이 설정
    // 송신 버퍼 크기를 가져옴
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if (state) // 가져오기 실패 시 에러 처리
        error_handling("getsockopt() error!");

    len = sizeof(rcv_buf); // 수신 버퍼 크기 길이 설정
    // 수신 버퍼 크기를 가져옴
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if (state) // 가져오기 실패 시 에러 처리
        error_handling("getsockopt() error!");

    printf("Input buffer size: %d \n", rcv_buf); // 수신 버퍼 크기 출력
    printf("Output buffer size: %d \n", snd_buf); // 송신 버퍼 크기 출력
    return 0; // 프로그램 종료
}

// 에러 핸들링 함수 정의
void error_handling(char *message)
{
    fputs(message, stderr); // 에러 메시지를 표준 에러에 출력
    fputc('\n', stderr);    // 줄바꿈 출력
    exit(1);                // 프로그램 종료
}

1-2) 결과

code
Input buffer size: 3072 
Output buffer size: 3072

1-3) 유의 사항

  • 커널에 설정된 실제 값은 사용자가 설정한 값과 정확히 일치하지 않을 수 있다.

  • 커널은 설정된 값의 두 배 크기로 설정한다.

[6] SOL_SOCKET : SO_REUSEADDR

  • SO_REUSEADDR: 이전에 설정된 연결이 해당 포트를 사용하더라도 서버가 포트를 시작하고 바인딩할 수 있도록 허용함

    • SO_REUSEADDR가 필요한 경우

      • TIME_WAIT 상태에 있는 서버

      • 다중 홈 서버

1) 예시 : set_buf.c

code
socklen_t optlen; // 옵션 길이를 저장할 변수
int option;       // 옵션을 저장할 변수

optlen = sizeof(option); // option의 크기를 optlen에 저장
option = TRUE;           // option을 TRUE로 설정
// SO_REUSEADDR 옵션을 설정하여 포트 재사용을 허용
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);

2)

[7] IPPROTO_TCP: TCP_KEEPALIVE, TCP_MAXSEG

  • TCP_KEEPALIVE

    • TCP가 keepalive 프로브를 전송하기 전에 연결의 유휴 시간을 초 단위로 지정함.

      • 기본값: 2시간.

    • 이 옵션은 SO_KEEPALIVE 소켓 옵션이 활성화되어 있을 때만 효과적이다.

  • TCP_MAXSEG

    • TCP 연결의 최대 세그먼트 크기(MSS)를 가져오거나 설정할 수 있게 함.

[8] IPPROTO_TCP: TCP_NODELAY

  • TCP_NODELAY

    • 설정 시, TCP의 Nagle 알고리즘을 비활성화함.

      • 기본값: 이 알고리즘이 활성화되어 있음.

    • Nagle 알고리즘

      • 애플리케이션에서 데이터가 들어오면, 첫 번째 바이트를 전송하고,

        • 1) 나머지 바이트를 모두 ACK가 수신될 때까지 버퍼에 저장함.

        • 2) 데이터가 윈도우의 절반을 채우거나 최대 세그먼트를 초과하면 새 패킷을 전송함.

        • 마우스 이동과 같은 경우에는 비활성화하는 것이 좋음.

1) 사용법

  1. TCP_NODELAY 설정 (Nagle 알고리즘 비활성화)

  2. int opt_val = 1; // 옵션 값을 1로 설정 (비활성화) setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val)); // 소켓 옵션 설정

  3. TCP_NODELAY 옵션 가져오기

  4. int opt_val; // 옵션 값을 저장할 변수 socklen_t opt_len; // 옵션 길이를 저장할 변수 opt_len = sizeof(opt_val); // opt_val의 크기를 opt_len에 저장 getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len); // 소켓 옵션 가져오기

7. 멀티캐스트 및 브로드캐스트

[1] 멀티캐스트

1) 특징

  • 서버는 특정 멀티캐스트 그룹을 위해 한 번만 데이터를 전송한다.

  • 멀티캐스트 그룹에 가입한 모든 클라이언트는 모든 데이터를 받을 수 있다.

  • 클라이언트는 데이터를 받기 위해 멀티캐스트 그룹에 가입해야 한다.

  • UDP를 기반으로 한다.

  • 멀티캐스트 IP 주소 (클래스 D)

    • 224.0.0.0 ~ 239.255.255.255

2) 전역 범위: 224.0.0.0 ~ 239.255.255.255

  • 예약 주소: 224.0.0.0 ~ 224.0.0.255

  • 인터넷 전체 주소: 224.0.1.0 ~ 238.255.255.255

  • 로컬 주소: 239.0.0.0 ~ 239.255.255.255

3) 몇 가지 특별 주소

  • 224.0.0.1: 서브넷의 모든 멀티캐스트 시스템

  • 224.0.0.2: 서브넷의 모든 멀티캐스트 라우터

[2] 멀티캐스트 프로그래밍

1) 송신자 (Sender)

  • UDP 소켓 생성: 멀티캐스트 통신을 위해 UDP 소켓을 생성한다.

  • TTL(Time to Live) 설정: 패킷이 네트워크를 통과할 수 있는 최대 홉 수를 지정하는 TTL 값을 설정한다.

    • 기본값은 1로 설정되어 있으며, 이는 패킷이 동일한 로컬 네트워크 내에서만 전송됨을 의미한다.

  • 멀티캐스트 주소로 데이터 전송: 멀티캐스트 주소로 데이터를 송신한다. 이를 통해 멀티캐스트 그룹에 가입한 모든 수신자가 데이터를 수신할 수 있다.

2) 수신자 (Receiver)

  • UDP 소켓 생성: 멀티캐스트 데이터를 수신하기 위해 UDP 소켓을 생성한다.

  • 멀티캐스트 그룹에 가입: setsockopt 함수를 사용하여 특정 멀티캐스트 그룹에 가입한다. 이렇게 함으로써 해당 그룹에 전송된 데이터를 수신할 수 있다.

3) 정리

  • 이러한 과정을 통해 송신자는 멀티캐스트 주소로 데이터를 전송하고, 수신자는 해당 주소에 가입하여 데이터를 수신하게 된다.

  • 멀티캐스트는 특히 대규모 데이터 전송에서 효율성을 제공하여 네트워크 자원을 절약하는 데 유용하다.

수정된 내용은 다음과 같습니다:

[3] 멀티캐스트 소켓 옵션

  1. IP_ADD_MEMBERSHIP

    • 멀티캐스트 그룹에 가입하는 옵션이다.

    • 사용자는 이 옵션을 통해 특정 멀티캐스트 주소로부터 전송되는 데이터를 수신할 수 있다.

  2. IP_DROP_MEMBERSHIP

    • 멀티캐스트 멤버십을 종료하는 옵션이다.

    • 이 옵션을 사용하여 더 이상 해당 멀티캐스트 그룹에서 데이터를 수신하지 않도록 설정할 수 있다.

  3. 관련 구조체

    • 이 구조체는 IP_ADD_MEMBERSHIPIP_DROP_MEMBERSHIP 소켓 옵션과 함께 사용된다.

    • imr_multiaddr 필드는 가입하려는 멀티캐스트 주소를 지정하고, imr_interface 필드는 해당 멀티캐스트 데이터를 수신할 네트워크 인터페이스를 지정한다.

    • 이를 통해 소켓은 정확한 멀티캐스트 그룹에 대한 수신을 관리할 수 있다.

  4. struct ip_mreq { struct in_addr imr_multiaddr; // 멀티캐스트 주소 struct in_addr imr_interface; // 가입할 인터페이스의 IP 주소 };

  5. IP_MULTICAST_IF

    • 아웃고잉 멀티캐스트의 기본 인터페이스를 지정하는 옵션이다.

    • IPv4의 경우 IN_ADDR 형식으로 지정된다.

  6. IP_MULTICAST_LOOP

    • 아웃고잉 멀티캐스트의 루프백을 활성화하거나 비활성화하는 옵션이다.

  7. IP_MULTICAST_TTL

    • 멀티캐스트의 TTL(Time To Live)을 지정하는 옵션이다.

    • 지정하지 않을 경우 기본 값은 1이다.

[4] 예시

아래는 news_sender.cnews_receiver.c 코드의 구조를 수정하여 실행 가능하도록 구성한 예입니다. 코드가 명확하게 작동하도록 주요 부분을 함수로 나누고, 오류 처리와 변수 정의를 잘 정리하였습니다.

1) news_sender.c

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

#define BUF_SIZE 1024
#define TTL 64

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int send_sock;
    struct sockaddr_in mul_adr;
    int time_live = TTL;
    FILE *fp;
    char buf[BUF_SIZE];

    if (argc != 3) {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }

    // UDP 소켓 생성
    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (send_sock == -1) 
        error_handling("socket() error");

    memset(&mul_adr, 0, sizeof(mul_adr)); // 구조체 초기화
    mul_adr.sin_family = AF_INET; // IPv4
    mul_adr.sin_addr.s_addr = inet_addr(argv[1]); // 멀티캐스트 IP
    mul_adr.sin_port = htons(atoi(argv[2])); // 멀티캐스트 포트

    // TTL 설정
    if (setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)) < 0) 
        error_handling("setsockopt() error");

    // 뉴스 파일 열기
    if ((fp = fopen("news.txt", "r")) == NULL) 
        error_handling("fopen() error");

    // 뉴스 전송
    while (fgets(buf, BUF_SIZE, fp) != NULL) {
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
        sleep(2);
    }

    // 리소스 해제
    fclose(fp);
    close(send_sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

2) news_receiver.c

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

#define BUF_SIZE 1024

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in adr;
    struct ip_mreq join_adr;

    if (argc != 3) {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }

    // UDP 소켓 생성
    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (recv_sock == -1) 
        error_handling("socket() error");

    memset(&adr, 0, sizeof(adr)); // 구조체 초기화
    adr.sin_family = AF_INET; // IPv4
    adr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 IP 수신
    adr.sin_port = htons(atoi(argv[2])); // 수신 포트

    // 소켓 바인드
    if (bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr)) == -1)
        error_handling("bind() error");

    // 멀티캐스트 그룹 가입
    join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]);  // 멀티캐스트 IP
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY); // 수신할 인터페이스

    if (setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr)) < 0) 
        error_handling("setsockopt() error");

    // 데이터 수신
    while (1) {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
        if (str_len < 0) 
            break;

        buf[str_len] = 0; // 문자열 종료
        fputs(buf, stdout); // 수신된 데이터 출력
    }

    // 리소스 해제
    close(recv_sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

3) 실행 방법

  1. 송신자 실행: 멀티캐스트 송신자를 실행하기 위해 아래와 같이 명령어를 입력합니다.예시:

  2. ./news_sender 224.0.0.1 12345

  3. ./news_sender <GroupIP> <PORT>

  4. 수신자 실행: 멀티캐스트 수신자를 실행하기 위해 아래와 같이 명령어를 입력합니다.예시:

  5. ./news_receiver 224.0.0.1 12345

  6. ./news_receiver <GroupIP> <PORT>

4) 주의사항

  • news.txt 파일에 뉴스 내용을 작성해야 송신자가 해당 내용을 전송할 수 있습니다.

  • 멀티캐스트 주소는 224.0.0.0 ~ 239.255.255.255 범위의 주소를 사용해야 합니다.

수정된 내용을 아래와 같이 제시합니다.

[5] 브로드캐스트

1) 특징

  • 목적지: 동일 네트워크에 존재하는 모든 호스트

  • 기반: UDP

  • 브로드캐스트 IP 주소:

    • 지정 브로드캐스트:

      • 호스트 ID 부분이 모두 1로 설정됨

      • 예: 특정 네트워크에 속한 모든 호스트에 데이터를 전송하기 위해 사용

    • 제한 브로드캐스트 (동일 서브넷):

      • 주소: 255.255.255.255

      • 서브넷 내 모든 호스트에 데이터를 전송하기 위해 사용

2) 예시

  • 지정 브로드캐스트 예시:

    • 네트워크 주소가 192.168.1.0인 경우, 지정 브로드캐스트 주소는 192.168.1.255가 됨.

  • 제한 브로드캐스트 예시:

    • 서브넷 내 모든 장치에 메시지를 보내고자 할 때 사용.

브로드캐스트는 동일한 네트워크 세그먼트 내의 모든 장치에 데이터를 전송하는 데 유용하며, 주로 서비스 검색이나 네트워크 관리 등에 사용된다.

[6] 브로드캐스트 프로그래밍

  • 브로드캐스트 IP 주소와 소켓 옵션을 제외하면 UDP 소켓 프로그래밍과 거의 동일함.

  • 소켓 옵션: SO_BROADCAST

    • SOL_SOCKET 레벨 옵션

    • 프로세스가 브로드캐스트 메시지를 전송할 수 있는 능력을 활성화하거나 비활성화함. (오직 데이터그램 소켓에서만 사용됨)

[7] 예시

1) news_sender_brd.c

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

#define BUF_SIZE 1024

// 오류 처리 함수
void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[]) {
    int send_sock; // 송신 소켓
    struct sockaddr_in broad_adr; // 브로드캐스트 주소 구조체
    FILE *fp; // 파일 포인터
    char buf[BUF_SIZE]; // 버퍼
    int so_brd = 1; // 브로드캐스트 옵션 설정

    // 인자 개수 확인
    if (argc != 3) {
        printf("Usage : %s <Broadcast IP> <PORT>\n", argv[0]);
        exit(1);
    }

    // UDP 소켓 생성
    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&broad_adr, 0, sizeof(broad_adr)); // 구조체 초기화
    broad_adr.sin_family = AF_INET; // 주소 가족 설정
    broad_adr.sin_addr.s_addr = inet_addr(argv[1]); // 브로드캐스트 IP 설정
    broad_adr.sin_port = htons(atoi(argv[2])); // 포트 설정

    // 브로드캐스트 옵션 설정
    setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd));

    // 파일 열기
    if ((fp = fopen("news.txt", "r")) == NULL)
        error_handling("fopen() error");

    // 파일에서 한 줄씩 읽어 브로드캐스트 전송
    while (!feof(fp)) {
        fgets(buf, BUF_SIZE, fp); // 파일에서 한 줄 읽기
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr)); // 데이터 전송
        sleep(2); // 2초 대기
    }

    close(send_sock); // 소켓 닫기
    fclose(fp); // 파일 닫기
    return 0;
}

2) news_reciever_brd.c

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

#define BUF_SIZE 1024

// 오류 처리 함수
void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[]) {
    int recv_sock; // 수신 소켓
    struct sockaddr_in adr; // 수신 주소 구조체
    int str_len; // 수신 데이터 길이
    char buf[BUF_SIZE]; // 버퍼

    // 인자 개수 확인
    if (argc != 2) {
        printf("Usage : %s <PORT>\n", argv[0]);
        exit(1);
    }

    // UDP 소켓 생성
    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);

    memset(&adr, 0, sizeof(adr)); // 구조체 초기화
    adr.sin_family = AF_INET; // 주소 가족 설정
    adr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 인터페이스에서 수신
    adr.sin_port = htons(atoi(argv[1])); // 포트 설정

    // 소켓 바인드
    if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
        error_handling("bind() error");

    // 무한 루프를 통해 수신 대기
    while (1) {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0); // 데이터 수신
        if (str_len < 0) // 오류 발생 시 루프 종료
            break;
        buf[str_len] = 0; // 문자열 종료 문자 추가
        fputs(buf, stdout); // 수신한 데이터 출력
    }

    close(recv_sock); // 소켓 닫기
    return 0; // 정상 종료
}

Summary

5. UDP 소켓 프로그래밍

  • UDP는 연결 없는 프로토콜로, 데이터를 보내고 즉시 다음 패킷을 보낼 수 있다.

  • 주요 함수:

    • socket(): 소켓 생성

    • bind(): 소켓에 주소 할당

    • recvfrom(): 데이터 수신

    • sendto(): 데이터 송신

    • close(): 소켓 닫기

  • Connected UDP: 목적지 주소를 미리 설정하여 write()와 read() 함수를 사용할 수 있다.

6. 소켓 옵션

  • 소켓의 동작을 조절하기 위한 설정 값

    • getsockopt(): 소켓 옵션 가져오기

    • setsockopt(): 소켓 옵션 설정

  • 주요 옵션:

    • SO_RCVBUF, SO_SNDBUF: 수신/송신 버퍼 크기 설정

    • SO_REUSEADDR: 포트 재사용 허용

    • SO_KEEPALIVE: 연결 유지 확인

    • SO_BROADCAST: 브로드캐스트 허용

    • TCP_NODELAY: Nagle 알고리즘 비활성화

  • 멀티캐스트 옵션: IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_TTL 등

7-1. 멀티캐스트

  • 특정 그룹에 속한 모든 호스트에 데이터 전송

  • UDP 기반

  • 멀티캐스트 주소: 클래스 D 주소 사용

  • 소켓 옵션: IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP 등을 사용하여 멀티캐스트 그룹에 가입하거나 탈퇴

7-2. 브로드캐스트

  • 동일 네트워크 상의 모든 호스트에 데이터 전송

  • UDP 기반

  • 브로드캐스트 주소: 지정 브로드캐스트 주소 또는 제한 브로드캐스트 주소 사용

  • 소켓 옵션: SO_BROADCAST를 사용하여 브로드캐스트 기능 활성화

추가 설명

  • Nagle 알고리즘: 작은 패킷 여러 개를 하나로 합쳐서 전송하여 네트워크 효율을 높이는 알고리즘. TCP_NODELAY 옵션으로 비활성화할 수 있다.

  • TIME_WAIT 상태: TCP 연결 종료 후, 데이터가 손실될 경우를 대비하여 일정 시간 동안 유지되는 상태. SO_REUSEADDR 옵션을 사용하면 이 상태의 소켓에 바인딩할 수 있다.

  • 멀티캐스트 vs 브로드캐스트: 멀티캐스트는 특정 그룹에 대한 전송이고, 브로드캐스트는 네트워크 전체에 대한 전송이다. 멀티캐스트는 더 효율적이며, 브로드캐스트는 네트워크 트래픽을 증가시킬 수 있다.

실제 활용 예시

  • 멀티캐스트:

    • 실시간 비디오 스트리밍

    • 온라인 게임

    • IP 텔레비전

  • 브로드캐스트:

    • 네트워크 구성 정보 전달

    • 서비스 검색

    • 시간 동기화


목차