C 언어에서 소켓 프로그래밍은 네트워크 애플리케이션 개발의 핵심 기술 중 하나입니다. 본 기사에서는 소켓 프로그래밍의 기본 개념을 다루며, 특히 IPv4와 IPv6를 활용한 구현의 차이점에 초점을 맞춥니다. 이를 통해 네트워크 프로토콜의 기본 원리를 이해하고, 두 주소 체계의 특성을 활용한 소켓 애플리케이션 개발 능력을 향상시킬 수 있습니다.
소켓 프로그래밍 기초
소켓 프로그래밍은 네트워크 상에서 통신하는 두 프로그램 간 데이터를 주고받는 기술입니다. 소켓(Socket)은 네트워크 연결을 위한 인터페이스로, 프로세스 간 통신을 가능하게 합니다.
소켓의 개념
소켓은 네트워크에서 데이터를 송수신하기 위한 엔드포인트입니다. 이는 운영 체제의 API를 통해 제공되며, 클라이언트와 서버 간 연결을 설정하는 데 사용됩니다.
소켓의 주요 요소
- IP 주소: 통신할 대상 장치를 식별합니다.
- 포트 번호: 응용 프로그램의 특정 프로세스를 식별합니다.
- 프로토콜: TCP 또는 UDP와 같은 전송 프로토콜을 지정합니다.
소켓 프로그래밍의 절차
- 소켓 생성:
socket()
함수를 사용해 소켓을 생성합니다. - 주소 바인딩:
bind()
를 통해 소켓에 IP 주소와 포트를 바인딩합니다. - 연결 대기/요청: 서버는
listen()
으로 대기하고, 클라이언트는connect()
로 연결을 요청합니다. - 데이터 송수신:
send()
와recv()
또는read()
와write()
를 사용해 데이터를 주고받습니다. - 연결 종료: 통신이 끝나면
close()
로 소켓을 닫습니다.
이와 같은 기초는 IPv4와 IPv6 기반의 소켓 프로그래밍에서도 동일하게 적용되며, 이후 두 프로토콜의 차이점을 중심으로 심화 내용을 다룹니다.
IPv4와 IPv6의 기본 구조
IPv4의 주소 체계와 데이터 구조
IPv4(Internet Protocol Version 4)는 32비트 주소 체계를 사용하며, 총 232개(약 42억 개)의 고유 주소를 제공합니다. 각 주소는 점(.)으로 구분된 4개의 8비트 숫자로 표현됩니다(예: 192.168.1.1).
- 헤더 구조
IPv4 헤더는 20바이트 고정 길이로 구성되며, 다음과 같은 주요 필드를 포함합니다. - 버전(Version): 4비트를 사용해 프로토콜 버전을 나타냅니다.
- 헤더 길이(IHL): IP 헤더의 길이를 지정합니다.
- 소스 주소(Source Address): 데이터가 출발한 IP 주소입니다.
- 목적지 주소(Destination Address): 데이터가 향하는 IP 주소입니다.
IPv6의 주소 체계와 데이터 구조
IPv6(Internet Protocol Version 6)는 128비트 주소 체계를 사용하며, 총 2128개(약 3.4×1038)의 고유 주소를 제공합니다. 각 주소는 콜론(:)으로 구분된 8개의 16비트 블록으로 표현됩니다(예: 2001:0db8:85a3:0000:0000:8a2e:0370:7334).
- 헤더 구조
IPv6 헤더는 40바이트 고정 길이로, IPv4에 비해 단순화되어 있습니다. 주요 필드는 다음과 같습니다. - 버전(Version): 4비트로 IPv6를 나타냅니다.
- 소스 주소(Source Address): 데이터가 출발한 IPv6 주소입니다.
- 목적지 주소(Destination Address): 데이터가 향하는 IPv6 주소입니다.
IPv4와 IPv6의 주요 차이점
- 주소 표현: IPv4는 32비트(점-구분), IPv6는 128비트(콜론-구분).
- 주소 공간: IPv6는 더 많은 장치와 네트워크를 지원.
- 헤더 구조: IPv6는 간소화된 헤더로 처리 속도를 향상.
- 기능 개선: IPv6는 IPsec, 멀티캐스트, 자동 구성과 같은 기능이 기본 내장.
이러한 차이점은 네트워크의 확장성과 효율성을 높이기 위한 IPv6 설계의 기반이 됩니다. C 언어를 활용한 구현 시 이러한 구조적 특성을 이해하는 것이 중요합니다.
C 언어에서의 IPv4 소켓 구현
IPv4 소켓 프로그래밍 개요
IPv4 기반 소켓 프로그래밍은 32비트 IP 주소를 사용하여 클라이언트와 서버 간 데이터를 송수신합니다. C 언어에서는 소켓 API를 통해 이를 구현할 수 있습니다.
IPv4 소켓 구현 단계
1. 소켓 생성
socket()
함수를 사용하여 IPv4 소켓을 생성합니다.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
AF_INET
: IPv4 주소 체계 사용.SOCK_STREAM
: TCP 프로토콜 사용(UDP의 경우SOCK_DGRAM
).0
: 기본 프로토콜 사용.
2. 주소 바인딩
bind()
함수로 소켓에 IP 주소와 포트를 바인딩합니다.
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 모든 인터페이스에서 수신
server_addr.sin_port = htons(8080); // 포트 번호 8080
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
3. 연결 대기 및 요청
서버는 listen()
으로 연결 요청을 대기하고, 클라이언트는 connect()
로 요청을 보냅니다.
// 서버
if (listen(sockfd, 5) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
// 클라이언트
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
4. 데이터 송수신
send()
와 recv()
를 사용하여 데이터를 송수신합니다.
// 데이터 송신
char *message = "Hello, Client!";
send(client_sock, message, strlen(message), 0);
// 데이터 수신
char buffer[1024];
int bytes_received = recv(client_sock, buffer, sizeof(buffer), 0);
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
5. 연결 종료
close()
로 소켓 연결을 종료합니다.
close(sockfd);
IPv4 소켓 구현 예제
서버 예제 코드:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// 소켓 생성
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
return 1;
}
// 주소 바인딩
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
return 1;
}
// 연결 대기
listen(server_fd, 5);
printf("Waiting for a connection...\n");
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("Connection failed");
return 1;
}
// 데이터 송수신
char buffer[1024] = {0};
recv(client_fd, buffer, sizeof(buffer), 0);
printf("Received: %s\n", buffer);
char *response = "Hello from server!";
send(client_fd, response, strlen(response), 0);
// 연결 종료
close(client_fd);
close(server_fd);
return 0;
}
IPv4 소켓 프로그래밍은 네트워크 기본을 학습하는 데 중요한 단계입니다. 이후 IPv6 소켓과 비교해보면 두 프로토콜의 차이를 더 명확히 이해할 수 있습니다.
C 언어에서의 IPv6 소켓 구현
IPv6 소켓 프로그래밍 개요
IPv6 기반 소켓 프로그래밍은 128비트 IP 주소를 사용하여 클라이언트와 서버 간 데이터를 송수신합니다. 이는 IPv4의 주소 공간 제한을 해결하며, C 언어에서는 IPv4와 유사한 소켓 API를 사용해 구현됩니다.
IPv6 소켓 구현 단계
1. 소켓 생성
IPv6 소켓은 AF_INET6
주소 체계를 사용해 생성됩니다.
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
AF_INET6
: IPv6 주소 체계 사용.SOCK_STREAM
: TCP 프로토콜 사용.
2. 주소 구조 설정
IPv6 주소는 struct sockaddr_in6
구조체를 사용해 설정합니다.
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6;
server_addr.sin6_addr = in6addr_any; // 모든 인터페이스에서 수신
server_addr.sin6_port = htons(8080); // 포트 번호 8080
3. 주소 바인딩
bind()
함수로 소켓에 IPv6 주소를 바인딩합니다.
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
4. 연결 대기 및 요청
서버는 listen()
으로 연결 요청을 대기하고, 클라이언트는 connect()
로 요청을 보냅니다.
// 서버
if (listen(sockfd, 5) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
// 클라이언트
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, "fe80::1", &server_addr.sin6_addr); // 서버의 IPv6 주소
server_addr.sin6_port = htons(8080);
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
5. 데이터 송수신
send()
와 recv()
를 사용해 데이터를 주고받습니다.
// 데이터 송신
char *message = "Hello, IPv6 Client!";
send(client_sock, message, strlen(message), 0);
// 데이터 수신
char buffer[1024];
int bytes_received = recv(client_sock, buffer, sizeof(buffer), 0);
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
6. 연결 종료
close()
로 소켓 연결을 종료합니다.
close(sockfd);
IPv6 소켓 구현 예제
서버 예제 코드:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in6 server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// 소켓 생성
server_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
return 1;
}
// 주소 바인딩
server_addr.sin6_family = AF_INET6;
server_addr.sin6_addr = in6addr_any;
server_addr.sin6_port = htons(8080);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
return 1;
}
// 연결 대기
listen(server_fd, 5);
printf("Waiting for a connection...\n");
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("Connection failed");
return 1;
}
// 데이터 송수신
char buffer[1024] = {0};
recv(client_fd, buffer, sizeof(buffer), 0);
printf("Received: %s\n", buffer);
char *response = "Hello from IPv6 server!";
send(client_fd, response, strlen(response), 0);
// 연결 종료
close(client_fd);
close(server_fd);
return 0;
}
IPv6 소켓 프로그래밍은 현대 네트워크 환경에서 필수적인 기술입니다. IPv4 소켓과의 구조적 차이점을 이해하고 응용함으로써, 더 확장된 네트워크 환경에서 애플리케이션을 개발할 수 있습니다.
IPv4와 IPv6의 주요 차이점
주소 체계의 차이
- IPv4: 32비트 주소 체계를 사용하며 약 42억 개의 고유 주소를 제공합니다. 점으로 구분된 4개의 10진수로 표현됩니다(예:
192.168.0.1
). - IPv6: 128비트 주소 체계를 사용하며, 2128개의 고유 주소를 제공합니다. 콜론으로 구분된 8개의 16진수 블록으로 표현됩니다(예:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
).
헤더 구조의 차이
- IPv4 헤더: 20~60바이트의 가변 길이를 가지며, 필드가 많아 처리 속도가 느릴 수 있습니다.
- IPv6 헤더: 40바이트 고정 길이를 가지며, IPv4에 비해 단순화되어 라우터와 네트워크 장비의 처리 효율을 높입니다.
기능의 차이
- IPv4: 기본적으로 보안 및 멀티캐스트 지원이 내장되어 있지 않습니다. 추가 설정이 필요합니다.
- IPv6:
- IPsec: 데이터 암호화 및 인증을 기본적으로 지원하여 보안성을 강화합니다.
- 자동 구성: DHCP 없이 네트워크 구성 가능.
- 멀티캐스트: 대규모 네트워크에서 효율적인 데이터 전송이 가능.
주소 공간과 네트워크 확장성
- IPv4: 주소 공간이 제한적이며, NAT(Network Address Translation) 기술을 사용하여 주소 부족 문제를 해결합니다.
- IPv6: 거의 무제한의 주소 공간을 제공하며, NAT 없이도 대규모 네트워크 확장이 가능합니다.
소켓 프로그래밍의 차이점
- 주소 구조체:
- IPv4는
struct sockaddr_in
을 사용합니다. - IPv6는
struct sockaddr_in6
을 사용하며, 128비트 주소와 추가 필드를 포함합니다.
- 호스트 주소 지정:
- IPv4는
inet_pton(AF_INET, ...)
과 같은 함수로 주소를 변환합니다. - IPv6는
inet_pton(AF_INET6, ...)
로 IPv6 주소를 처리합니다.
- 바인딩과 호환성:
IPv6 소켓은 IPv4 주소를 포함한 Dual Stack을 지원할 수 있으나, 추가 설정(setsockopt
)이 필요합니다.
응용 측면에서의 차이점
- IPv4: 소규모 네트워크 및 레거시 시스템에 적합.
- IPv6: IoT, 대규모 클라우드 환경, 그리고 미래의 인터넷 확장에 적합.
IPv4와 IPv6의 이러한 차이점을 이해하면, 소켓 프로그래밍을 통해 두 프로토콜을 효과적으로 다룰 수 있습니다. C 언어로 구현할 때는 이러한 차이를 고려하여 코드 작성 및 디버깅을 수행해야 합니다.
호환성을 고려한 프로그래밍
Dual Stack 소켓 프로그래밍
IPv4와 IPv6를 모두 지원하는 네트워크 애플리케이션을 개발하려면 Dual Stack 방식이 필요합니다. Dual Stack은 하나의 네트워크 장비나 애플리케이션에서 두 프로토콜을 동시에 처리할 수 있도록 구성합니다.
IPv4 및 IPv6 주소를 처리하는 소켓 설정
IPv6 소켓은 기본적으로 IPv4 주소와 호환될 수 있지만, 이를 명시적으로 활성화해야 합니다. C 언어에서는 setsockopt()
를 사용하여 IPv4-mapped IPv6 주소를 활성화할 수 있습니다.
int opt = 0; // Dual Stack 활성화를 비활성화(기본값)
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
- IPV6_V6ONLY: 0으로 설정하면 IPv4-mapped IPv6 주소를 허용합니다.
Dual Stack 서버 구현
Dual Stack 서버는 하나의 소켓에서 IPv4와 IPv6 클라이언트를 모두 처리하거나, 별도의 소켓을 생성하여 각각의 요청을 처리할 수 있습니다.
1. 단일 소켓으로 처리
IPv6 소켓을 생성하고, IPV6_V6ONLY
옵션을 비활성화하여 IPv4-mapped IPv6 주소를 허용합니다.
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6;
server_addr.sin6_addr = in6addr_any; // IPv6 및 IPv4 주소 모두 수신
server_addr.sin6_port = htons(8080);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
2. 별도의 소켓으로 처리
하나는 IPv4 소켓, 다른 하나는 IPv6 소켓을 생성하여 각각 요청을 처리합니다.
int ipv4_sockfd = socket(AF_INET, SOCK_STREAM, 0);
int ipv6_sockfd = socket(AF_INET6, SOCK_STREAM, 0);
// 각각 다른 주소로 바인딩
bind(ipv4_sockfd, (struct sockaddr*)&ipv4_addr, sizeof(ipv4_addr));
bind(ipv6_sockfd, (struct sockaddr*)&ipv6_addr, sizeof(ipv6_addr));
// 각 소켓에서 연결 대기
listen(ipv4_sockfd, 5);
listen(ipv6_sockfd, 5);
호환성 테스트 및 디버깅
Dual Stack 애플리케이션의 안정성을 보장하려면 다양한 네트워크 환경에서 테스트해야 합니다.
1. IPv4 및 IPv6 연결 테스트
- IPv4 테스트: IPv4 주소를 사용하는 클라이언트에서 연결 요청을 보냅니다(예:
127.0.0.1
). - IPv6 테스트: IPv6 주소를 사용하는 클라이언트에서 연결 요청을 보냅니다(예:
::1
).
2. Wireshark를 활용한 네트워크 패킷 분석
Wireshark와 같은 네트워크 분석 도구를 사용하여 IPv4 및 IPv6 데이터 패킷을 캡처하고 확인합니다.
호환성 유지의 이점
- 레거시 시스템 지원: 기존 IPv4 기반 클라이언트와의 호환성을 유지합니다.
- 미래 지향적 설계: IPv6 환경으로의 원활한 전환을 준비할 수 있습니다.
- 유연성 향상: 다양한 네트워크 환경에서 동작 가능한 애플리케이션을 개발합니다.
Dual Stack 프로그래밍은 IPv4와 IPv6 네트워크의 경계를 허물고, 다양한 환경에서 애플리케이션을 실행할 수 있는 강력한 방법입니다. 이를 통해 프로토콜 전환기의 네트워크 요구를 충족시킬 수 있습니다.
디버깅과 문제 해결
소켓 프로그래밍에서 발생하는 일반적인 문제
소켓 프로그래밍은 네트워크 환경, 코드 오류, 설정 문제로 인해 다양한 문제를 발생시킬 수 있습니다. 이를 해결하기 위해 주요 이슈와 디버깅 방법을 이해하는 것이 중요합니다.
1. 소켓 생성 오류
- 문제:
socket()
함수 호출 시 반환값이 -1인 경우. - 원인: 잘못된 주소 체계(AF_INET 또는 AF_INET6) 또는 프로토콜 설정.
- 해결:
if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
2. 주소 바인딩 실패
- 문제:
bind()
함수가 실패하여 “Address already in use” 오류 발생. - 원인: 동일한 포트 번호를 이미 다른 소켓이 사용 중이거나, 소켓 옵션이 설정되지 않음.
- 해결: 소켓 옵션 활성화.
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
3. 연결 실패
- 문제: 클라이언트가
connect()
호출 시 “Connection refused” 오류 발생. - 원인: 서버가 실행 중이지 않거나, 잘못된 IP 주소와 포트 번호를 사용.
- 해결: 서버와 클라이언트의 IP 주소 및 포트가 일치하는지 확인.
4. 데이터 송수신 문제
- 문제:
send()
나recv()
호출 시 반환값이 음수 또는 0. - 원인: 네트워크 연결 중단, 데이터 크기 초과, 버퍼 설정 오류.
- 해결: 버퍼 크기를 확인하고, 데이터 전송 상태를 확인.
char buffer[1024];
int bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
}
디버깅 도구와 방법
1. 로그와 디버깅 출력
소켓 상태를 확인하는 로그를 코드에 추가합니다.
printf("Socket created successfully\n");
printf("Binding to port 8080\n");
2. 네트워크 트래픽 분석
Wireshark와 같은 네트워크 트래픽 분석 도구를 사용하여 패킷 송수신 상태를 확인합니다.
3. Telnet을 사용한 연결 테스트
서버 포트가 열려 있는지 확인하기 위해 Telnet을 사용합니다.
telnet <server_ip> <port>
트러블슈팅 사례
사례 1: IPv6 연결 문제
- 문제: IPv6 주소를 사용하는 클라이언트가 서버와 연결되지 않음.
- 원인: 서버가
IPV6_V6ONLY
로 설정되어 IPv4-mapped IPv6 주소만 허용. - 해결:
setsockopt()
로 Dual Stack 활성화.
사례 2: 데이터 전송 중 일부 손실
- 문제: 클라이언트가 보낸 데이터의 일부만 수신됨.
- 원인: TCP 스트림에서 데이터가 분할되어 전송.
- 해결: 수신 루프를 작성하여 모든 데이터를 받을 때까지 반복.
int total_bytes = 0;
while ((bytes_received = recv(sockfd, buffer, sizeof(buffer), 0)) > 0) {
total_bytes += bytes_received;
}
문제 예방을 위한 팁
- 항상 소켓 호출의 반환값을 확인하고, 적절히 에러를 처리합니다.
- 소켓 옵션을 설정하여 포트 재사용 및 네트워크 설정을 최적화합니다.
- 네트워크 환경(방화벽, 라우터 설정)을 점검하여 외부 장애를 최소화합니다.
소켓 프로그래밍 디버깅은 정확한 원인 분석과 적절한 도구 활용으로 복잡한 문제를 해결할 수 있습니다. 이를 통해 안정적인 네트워크 애플리케이션을 구현할 수 있습니다.
연습 문제와 코드 예제
학습을 심화하기 위한 연습 문제
문제 1: IPv4 클라이언트-서버 애플리케이션 작성
- 목표: TCP 기반의 IPv4 클라이언트와 서버를 구현하여 문자열을 주고받는 프로그램 작성.
- 요구 사항:
- 서버는 포트 8080에서 클라이언트 요청을 수신.
- 클라이언트는 사용자 입력 문자열을 서버로 전송.
- 서버는 받은 문자열을 그대로 클라이언트에게 반환.
문제 2: IPv6 기반의 멀티클라이언트 서버
- 목표: IPv6 주소를 사용하는 서버가 여러 클라이언트와 동시 통신하는 프로그램 작성.
- 요구 사항:
- 서버는 쓰레드를 사용하여 클라이언트 요청을 병렬 처리.
- 각 클라이언트는 서버와 3번의 메시지를 주고받아야 함.
문제 3: Dual Stack 서버
- 목표: IPv4와 IPv6 클라이언트 모두를 지원하는 Dual Stack 서버 작성.
- 요구 사항:
- 단일 소켓으로 IPv4와 IPv6 요청을 처리.
- 클라이언트 연결 시 서버가 클라이언트의 IP 버전을 콘솔에 출력.
코드 예제
예제 1: IPv4 에코 서버
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[1024] = {0};
// 소켓 생성
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 주소 바인딩
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// 연결 대기
listen(server_fd, 5);
printf("Waiting for a connection...\n");
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
// 데이터 송수신
recv(client_fd, buffer, sizeof(buffer), 0);
printf("Received: %s\n", buffer);
send(client_fd, buffer, strlen(buffer), 0);
// 연결 종료
close(client_fd);
close(server_fd);
return 0;
}
예제 2: IPv6 클라이언트
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int sockfd;
struct sockaddr_in6 server_addr;
char *message = "Hello, Server!";
char buffer[1024] = {0};
// 소켓 생성
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 서버 주소 설정
server_addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, "::1", &server_addr.sin6_addr); // 로컬호스트 IPv6 주소
server_addr.sin6_port = htons(8080);
// 서버 연결
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
// 데이터 송신 및 수신
send(sockfd, message, strlen(message), 0);
printf("Message sent to server\n");
recv(sockfd, buffer, sizeof(buffer), 0);
printf("Received from server: %s\n", buffer);
// 소켓 닫기
close(sockfd);
return 0;
}
연습 문제 풀이를 통한 이해 심화
위의 문제와 예제를 통해 IPv4와 IPv6 소켓 프로그래밍의 차이점과 구현 방법을 실습할 수 있습니다. 이를 통해 다양한 네트워크 환경에서의 실용적인 프로그램을 작성할 능력을 갖출 수 있습니다.
요약
IPv4와 IPv6 소켓 프로그래밍은 네트워크 애플리케이션 개발의 핵심 요소입니다. 본 기사에서는 C 언어를 활용하여 두 프로토콜의 차이점과 구현 방법, 호환성을 고려한 Dual Stack 프로그래밍, 디버깅과 문제 해결 방법을 다뤘습니다.
IPv4는 레거시 시스템에 적합하며, IPv6는 확장성과 보안이 강화된 현대 네트워크 환경에 최적화되어 있습니다. 두 프로토콜을 활용한 소켓 프로그래밍 기술은 다양한 네트워크 요구를 충족시키는 강력한 도구가 될 것입니다.