C 언어의 네트워크 프로그래밍에서 recv
와 send
함수는 데이터 송수신의 핵심입니다. 클라이언트-서버 모델에서 데이터를 효율적으로 주고받기 위해 이 함수들은 TCP/IP 기반 소켓 프로그래밍에서 자주 사용됩니다. 본 기사에서는 recv
와 send
함수의 동작 원리, 활용 방법, 그리고 실전 코드 예제를 통해 이해를 돕고자 합니다. 이를 통해 네트워크 데이터 송수신의 기본을 익히고, 실무에서 응용할 수 있는 지식을 제공할 것입니다.
recv와 send 함수란 무엇인가
recv
와 send
함수는 C 언어에서 네트워크 소켓을 통해 데이터를 송수신하는 데 사용되는 표준 함수입니다.
recv 함수
recv
함수는 네트워크 소켓에서 데이터를 읽어오는 데 사용됩니다. 일반적으로 서버와 클라이언트 간의 데이터 수신에 사용되며, 다음과 같은 형식으로 호출됩니다:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- sockfd: 데이터가 수신되는 소켓의 파일 디스크립터
- buf: 수신된 데이터를 저장할 버퍼
- len: 버퍼의 크기
- flags: 수신 옵션
send 함수
send
함수는 네트워크 소켓을 통해 데이터를 전송하는 데 사용됩니다. 일반적으로 클라이언트와 서버 간의 데이터 송신에 사용되며, 호출 형식은 다음과 같습니다:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd: 데이터를 전송할 소켓의 파일 디스크립터
- buf: 전송할 데이터가 담긴 버퍼
- len: 전송할 데이터의 크기
- flags: 송신 옵션
이 두 함수는 TCP 소켓 프로그래밍에서 데이터를 송수신하기 위한 기본적인 도구로, 네트워크 통신의 핵심을 이루는 역할을 합니다.
함수의 기본 동작 원리
recv
와 send
함수는 TCP/IP 프로토콜을 기반으로 데이터를 송수신하며, 동작 원리는 간단하지만 그 내부 처리 과정은 중요한 의미를 가집니다.
recv 함수의 동작 원리
recv
함수는 소켓의 수신 버퍼에서 데이터를 읽어와 호출자에게 전달합니다.
- 수신 버퍼 확인:
소켓의 수신 버퍼에 데이터가 있는지 확인합니다.
- 데이터가 있을 경우, 해당 데이터를 읽어와 호출자가 제공한 버퍼에 저장합니다.
- 데이터가 없으면 함수는 기본적으로 블로킹(대기) 상태에 들어갑니다(블로킹 모드인 경우).
- 반환값 처리:
- 양수: 수신된 데이터의 바이트 수
- 0: 연결이 종료됨
- -1: 오류 발생 (
errno
를 통해 상세 원인 확인 가능)
send 함수의 동작 원리
send
함수는 데이터를 송신 버퍼에 작성하고 네트워크를 통해 전송을 시도합니다.
- 송신 버퍼 확인:
송신 버퍼에 여유 공간이 있는지 확인합니다.
- 공간이 충분하면 데이터를 송신 버퍼에 기록합니다.
- 공간이 부족하면 블로킹 모드에서는 대기하고, 논블로킹 모드에서는 즉시 반환합니다.
- 데이터 전송:
송신 버퍼의 데이터가 네트워크 인터페이스를 통해 전송됩니다. 데이터가 완전히 전송되지 않을 수도 있으므로, 반환된 바이트 수를 확인하여 반복적으로 호출해야 할 수 있습니다. - 반환값 처리:
- 양수: 전송된 데이터의 바이트 수
- -1: 오류 발생 (
errno
를 통해 상세 원인 확인 가능)
작동 방식 비교
함수 | 주요 동작 | 반환값 | 주요 동작 모드 |
---|---|---|---|
recv | 데이터를 수신 버퍼에서 읽음 | 0(연결 종료), 양수(바이트 수), -1(오류) | 블로킹, 논블로킹 |
send | 데이터를 송신 버퍼에 기록 | 양수(바이트 수), -1(오류) | 블로킹, 논블로킹 |
이 기본 동작 원리를 이해하면, 네트워크 통신에서 발생할 수 있는 다양한 문제를 효과적으로 처리할 수 있습니다.
네트워크 소켓과 함수 사용
recv
와 send
함수는 네트워크 소켓을 기반으로 동작합니다. 소켓은 클라이언트와 서버 간의 데이터 송수신을 위한 핵심 도구로, 네트워크 프로그래밍에서 필수적으로 사용됩니다.
소켓의 역할
소켓은 운영 체제가 제공하는 네트워크 통신 인터페이스로, 다음과 같은 주요 작업을 수행합니다:
- 연결 설정: 클라이언트와 서버 간의 연결을 생성 및 유지
- 데이터 송수신: 데이터를 전송하거나 수신할 수 있도록 버퍼와 네트워크 인터페이스를 관리
- 프로토콜 준수: TCP, UDP 등 선택한 프로토콜에 따라 데이터 흐름을 제어
recv와 send 함수의 소켓 사용
- 소켓 생성:
소켓은socket()
함수를 사용해 생성되며, 반환되는 파일 디스크립터는recv
와send
함수에서 사용됩니다.
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP 소켓 생성
- 소켓 연결:
클라이언트는connect()
함수를 사용해 서버에 연결을 요청하고, 서버는bind()
와listen()
,accept()
를 통해 연결을 수락합니다.
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
- 데이터 송수신:
연결이 설정되면, 클라이언트와 서버는recv
와send
를 통해 데이터를 주고받습니다.
// 데이터 송신
char *message = "Hello, Server!";
send(sockfd, message, strlen(message), 0);
// 데이터 수신
char buffer[1024];
int bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
buffer[bytes_received] = '\0'; // 문자열로 변환
printf("Received: %s\n", buffer);
TCP 소켓과 함수의 상호작용
- 클라이언트-서버 모델:
클라이언트와 서버 간의 데이터 흐름은 송신 측의send
함수 호출과 수신 측의recv
함수 호출이 조화를 이루어 이루어집니다. - 송신 버퍼와 수신 버퍼:
소켓 내부에 있는 송신 버퍼와 수신 버퍼는 데이터를 일시적으로 저장하며, 네트워크 지연이나 데이터 패킷 분할로 인한 문제를 완화합니다.
실제 코드 예시
서버:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_fd, 5);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addrlen);
recv(client_fd, buffer, sizeof(buffer), 0);
send(client_fd, response, strlen(response), 0);
클라이언트:
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
send(client_fd, message, strlen(message), 0);
recv(client_fd, buffer, sizeof(buffer), 0);
recv
와 send
는 이러한 네트워크 소켓 환경에서 필수적으로 작동하며, 데이터 송수신을 간단하고 효율적으로 구현할 수 있도록 돕습니다.
블로킹과 논블로킹 모드의 차이
recv
와 send
함수는 기본적으로 블로킹 모드에서 작동하지만, 논블로킹 모드로도 설정할 수 있습니다. 이 두 모드는 함수가 데이터를 처리하는 방식과 동작 시간에 큰 차이를 만듭니다.
블로킹 모드
블로킹 모드는 recv
와 send
함수가 호출되었을 때 작업이 완료될 때까지 실행을 멈추는 동작 방식입니다.
- 특징:
- 호출된 함수가 작업을 완료할 때까지 대기
- 송수신이 완료되거나 오류가 발생하기 전까지 다음 코드는 실행되지 않음
- 장점:
- 코드가 단순하고 직관적
- 작업 완료를 보장
- 단점:
- 대기 중 다른 작업을 수행할 수 없음
- 데이터 전송 지연 시 프로그램 응답 속도 저하
- 예시:
int bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
// 데이터가 수신될 때까지 대기
논블로킹 모드
논블로킹 모드는 함수 호출 시 작업이 완료되지 않았더라도 즉시 반환하는 방식입니다.
- 특징:
- 함수 호출 후 즉시 반환
- 수신 데이터가 없거나 송신 버퍼가 가득 차 있는 경우에도 대기하지 않음
- 장점:
- 여러 작업을 병렬로 처리 가능
- 응답 속도가 빠름
- 단점:
- 데이터 송수신 완료 여부를 확인하는 추가 코드 필요
- 구현이 복잡해질 수 있음
- 논블로킹 설정 방법:
fcntl()
함수를 사용해 소켓을 논블로킹 모드로 설정합니다.
#include <fcntl.h>
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
- 예시:
int bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received == -1 && errno == EAGAIN) {
printf("No data available, try again later.\n");
}
블로킹과 논블로킹 비교
특징 | 블로킹 모드 | 논블로킹 모드 |
---|---|---|
함수 호출 후 동작 | 작업 완료까지 대기 | 작업 완료 여부와 관계없이 반환 |
응답 속도 | 느림 | 빠름 |
구현 복잡도 | 낮음 | 높음 |
사용 사례 | 단순한 데이터 송수신 | 비동기 처리나 멀티태스킹 필요 |
적용 시나리오
- 블로킹 모드: 간단한 서버-클라이언트 통신, 데이터 전송이 자주 발생하지 않는 경우
- 논블로킹 모드: 실시간 처리, 다중 클라이언트 지원, 비동기 작업이 필요한 경우
블로킹과 논블로킹 모드를 적절히 이해하고 사용하는 것은 네트워크 프로그램의 성능과 안정성에 중요한 영향을 미칩니다.
에러 핸들링 방법
recv
와 send
함수는 네트워크 송수신 과정에서 다양한 이유로 실패할 수 있습니다. 발생할 수 있는 에러를 식별하고 처리하는 것은 안정적인 네트워크 프로그램을 작성하는 데 필수적입니다.
recv 함수의 에러 처리
recv
함수가 실패하면 반환값으로 -1
을 반환하며, 자세한 에러 정보는 errno
를 통해 확인할 수 있습니다.
- 주요 에러 코드:
- EAGAIN/EWOULDBLOCK: 논블로킹 모드에서 읽을 데이터가 없는 경우
- EBADF: 잘못된 소켓 파일 디스크립터
- ECONNRESET: 연결이 원격으로 종료됨
- EFAULT: 잘못된 버퍼 주소
- ENOTCONN: 소켓이 연결되지 않음
- 에러 처리 예제:
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("No data available, try again later.\n");
} else {
perror("recv failed");
// 추가적인 에러 처리 로직
}
}
send 함수의 에러 처리
send
함수도 실패 시 -1
을 반환하며, errno
를 통해 실패 원인을 확인할 수 있습니다.
- 주요 에러 코드:
- EAGAIN/EWOULDBLOCK: 논블로킹 모드에서 송신 버퍼가 가득 찬 경우
- EBADF: 잘못된 소켓 파일 디스크립터
- EPIPE: 연결이 끊어짐
- EMSGSIZE: 메시지가 너무 커서 전송할 수 없음
- ECONNRESET: 연결이 원격에서 종료됨
- 에러 처리 예제:
ssize_t bytes_sent = send(sockfd, buffer, sizeof(buffer), 0);
if (bytes_sent == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("Send buffer is full, try again later.\n");
} else if (errno == EPIPE) {
printf("Connection closed by the peer.\n");
} else {
perror("send failed");
// 추가적인 에러 처리 로직
}
}
공통 에러 처리 전략
- 재시도 로직 구현:
네트워크 환경은 불안정할 수 있으므로, 에러 발생 시 일정 횟수 재시도하는 로직을 추가합니다.
int attempts = 3;
while (attempts > 0) {
ssize_t result = send(sockfd, buffer, sizeof(buffer), 0);
if (result != -1) break; // 성공 시 종료
if (errno == EAGAIN || errno == EWOULDBLOCK) {
sleep(1); // 잠시 대기 후 재시도
}
attempts--;
}
if (attempts == 0) {
printf("Failed to send data after multiple attempts.\n");
}
- 로그 기록:
에러 발생 시 로그를 기록해 문제를 추적하고 디버깅에 활용합니다. - 연결 상태 확인 및 재연결:
네트워크 연결이 끊어지거나 문제가 발생한 경우 소켓을 닫고 재연결을 시도합니다.
close(sockfd);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
에러 핸들링의 중요성
네트워크 환경은 데이터 손실, 지연, 연결 끊김 등 다양한 문제가 발생할 수 있습니다. 이러한 상황을 적절히 처리하지 않으면 프로그램이 비정상적으로 종료되거나 작동이 중단될 수 있습니다. 철저한 에러 핸들링은 네트워크 프로그램의 안정성과 신뢰성을 높이는 데 필수적입니다.
데이터 버퍼링과 효율성
recv
와 send
함수에서 데이터 버퍼링은 네트워크 송수신의 성능과 안정성을 높이는 핵심 요소입니다. 데이터 전송 과정에서 효율적인 버퍼 사용은 처리 속도를 개선하고 네트워크 병목 현상을 줄이는 데 기여합니다.
데이터 버퍼링의 개념
- 버퍼링의 역할:
- 데이터를 전송하기 전 송신 버퍼에 저장하거나, 수신 후 수신 버퍼에 저장
- 데이터 전송이 완료되기 전에 프로그램이 작업을 계속 진행할 수 있도록 지원
- 운영 체제의 버퍼링:
소켓은 운영 체제에 의해 관리되는 송신 버퍼와 수신 버퍼를 제공합니다.
- 송신 버퍼: 전송할 데이터가 일시적으로 저장
- 수신 버퍼: 네트워크로부터 수신된 데이터가 임시로 저장
효율적인 데이터 전송을 위한 전략
- 적절한 버퍼 크기 설정:
- 데이터 전송 시 사용하는 버퍼의 크기는 전송 효율에 큰 영향을 미칩니다.
- 일반적으로 표준 네트워크 MTU(Maximum Transmission Unit, 1500바이트)와 맞추거나 더 큰 값을 사용하는 것이 효율적입니다.
char buffer[4096]; // 예시: 4KB 버퍼 사용
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
- 부분 전송 처리:
send
함수는 송신 버퍼에 여유 공간이 부족하면 데이터의 일부만 전송할 수 있습니다. 따라서, 반환값을 확인하고 남은 데이터를 반복적으로 전송해야 합니다.
ssize_t total_sent = 0;
ssize_t bytes_to_send = strlen(message);
while (total_sent < bytes_to_send) {
ssize_t bytes_sent = send(sockfd, message + total_sent, bytes_to_send - total_sent, 0);
if (bytes_sent == -1) {
perror("send failed");
break;
}
total_sent += bytes_sent;
}
- 데이터 누적 처리:
recv
함수는 한 번의 호출로 모든 데이터를 읽어오지 않을 수 있으므로, 원하는 데이터가 모두 수신될 때까지 반복적으로 호출해야 합니다.
ssize_t total_received = 0;
ssize_t bytes_expected = 1024;
while (total_received < bytes_expected) {
ssize_t bytes_received = recv(sockfd, buffer + total_received, bytes_expected - total_received, 0);
if (bytes_received == -1) {
perror("recv failed");
break;
} else if (bytes_received == 0) {
printf("Connection closed by peer.\n");
break;
}
total_received += bytes_received;
}
- 버퍼 초기화와 관리:
- 데이터를 수신하거나 전송한 후에는 버퍼를 초기화해 이전 데이터가 남아 있는 것을 방지해야 합니다.
memset()
함수나 명시적인 데이터 복사로 버퍼를 관리합니다.
memset(buffer, 0, sizeof(buffer)); // 버퍼 초기화
버퍼링의 장점
- 네트워크 효율성 향상: 데이터 전송을 최적화해 패킷 손실과 지연을 줄임
- CPU 사용량 감소: 대량의 데이터를 한 번에 처리하여 호출 횟수를 줄임
- 프로그램 안정성 증가: 네트워크 환경의 변동에도 안정적인 데이터 처리 보장
실제 적용 예
송신 예제:
char *message = "Large data payload...";
ssize_t total_sent = 0;
while (total_sent < strlen(message)) {
ssize_t bytes_sent = send(sockfd, message + total_sent, strlen(message) - total_sent, 0);
if (bytes_sent == -1) {
perror("Error sending data");
break;
}
total_sent += bytes_sent;
}
수신 예제:
char buffer[1024];
ssize_t total_received = 0;
while (1) {
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) break; // 연결 종료 또는 오류
total_received += bytes_received;
process_data(buffer, bytes_received);
}
데이터 버퍼링과 효율성을 적절히 활용하면 네트워크 프로그램의 성능과 안정성을 크게 향상시킬 수 있습니다.
일반적인 문제와 해결책
recv
와 send
함수를 사용하는 동안 다양한 문제가 발생할 수 있습니다. 이러한 문제를 이해하고 적절히 해결하는 것은 네트워크 프로그램의 신뢰성과 성능을 보장하는 데 중요합니다.
문제 1: 연결 종료
- 문제 상황:
- 상대방이 소켓 연결을 닫았을 때,
recv
는 0을 반환합니다. send
호출 시 EPIPE 또는 ECONNRESET 오류가 발생할 수 있습니다.
- 해결책:
- 수신 함수 호출 시 반환값을 확인해 연결 종료 여부를 처리합니다.
- 송신 전에 연결 상태를 점검하거나, 오류 발생 시 재연결을 시도합니다.
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received == 0) {
printf("Connection closed by the peer.\n");
close(sockfd); // 소켓 닫기
}
문제 2: 부분 전송 또는 수신
- 문제 상황:
- 네트워크의 패킷 크기 제한으로 인해 데이터가 한 번에 전송되지 않거나 수신되지 않을 수 있습니다.
recv
와send
가 일부 데이터만 처리하고 반환됩니다.
- 해결책:
- 루프를 사용해 전체 데이터를 처리합니다.
- 반환된 바이트 수를 추적하고 남은 데이터를 반복적으로 처리합니다.
ssize_t total_sent = 0;
while (total_sent < data_size) {
ssize_t bytes_sent = send(sockfd, data + total_sent, data_size - total_sent, 0);
if (bytes_sent == -1) break;
total_sent += bytes_sent;
}
문제 3: 송신 버퍼 또는 수신 버퍼 부족
- 문제 상황:
- 송신 버퍼가 가득 차거나 수신 버퍼에 데이터가 없을 경우, 블로킹 모드에서는 함수 호출이 대기 상태에 들어갑니다.
- 논블로킹 모드에서는 EAGAIN 또는 EWOULDBLOCK 오류가 반환됩니다.
- 해결책:
- 블로킹 모드에서는 타임아웃을 설정해 무한 대기를 방지합니다.
- 논블로킹 모드에서는 오류 발생 시 재시도 로직을 구현합니다.
struct timeval timeout;
timeout.tv_sec = 5; // 5초 타임아웃
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
문제 4: 데이터 손실
- 문제 상황:
- 데이터 전송 중 네트워크 혼잡이나 손실로 인해 패킷이 누락될 수 있습니다.
- 송신된 데이터가 수신 측에서 손실되거나 불완전할 수 있습니다.
- 해결책:
- 데이터를 전송하기 전에 체크섬 또는 CRC를 포함해 무결성을 확인합니다.
- 데이터 손실 시 재전송을 요청하는 로직을 추가합니다.
ssize_t bytes_sent = send(sockfd, data, data_size, 0);
if (bytes_sent == -1) {
printf("Error sending data, retrying...\n");
// 재시도 로직
}
문제 5: 네트워크 연결 지연
- 문제 상황:
- 네트워크 지연으로 인해 데이터 송수신 속도가 느려질 수 있습니다.
- 함수 호출이 예상보다 오래 걸릴 수 있습니다.
- 해결책:
- 비동기 I/O나 다중 스레드를 사용해 네트워크 지연이 프로그램의 다른 부분에 영향을 미치지 않도록 합니다.
// 예: select()를 사용한 비동기 I/O
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
struct timeval timeout = {5, 0}; // 5초 대기
if (select(sockfd + 1, &read_fds, NULL, NULL, &timeout) > 0) {
recv(sockfd, buffer, sizeof(buffer), 0);
} else {
printf("Timeout or no data available.\n");
}
문제 해결을 위한 일반 전략
- 로그 기록: 모든 오류를 기록해 디버깅과 문제 해결을 돕습니다.
- 에러 코드 확인:
errno
를 사용해 함수 호출 실패의 원인을 파악합니다. - 테스트 환경 시뮬레이션: 다양한 네트워크 조건을 시뮬레이션해 문제를 사전에 발견하고 대비합니다.
이러한 문제와 해결책을 적절히 적용하면 네트워크 프로그램의 안정성과 성능을 높일 수 있습니다.
실습 예제와 코드
recv
와 send
함수를 사용해 데이터를 송수신하는 간단한 클라이언트-서버 프로그램을 작성해 보겠습니다. 이 코드는 기본적인 네트워크 통신 흐름을 이해하는 데 도움을 줍니다.
서버 코드
서버는 클라이언트와 연결을 설정하고, 데이터를 수신한 후 응답 메시지를 보냅니다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE];
socklen_t addr_len = sizeof(client_addr);
// 소켓 생성
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
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(PORT);
// 소켓에 주소 바인딩
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 소켓 대기 상태로 설정
if (listen(server_fd, 5) == -1) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 클라이언트 연결 수락
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd == -1) {
perror("Accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 데이터 수신
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("Client: %s\n", buffer);
}
// 데이터 송신
char *response = "Hello from Server!";
send(client_fd, response, strlen(response), 0);
// 소켓 닫기
close(client_fd);
close(server_fd);
return 0;
}
클라이언트 코드
클라이언트는 서버에 연결한 후 데이터를 송신하고 응답을 수신합니다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int client_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 소켓 생성
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 서버 주소 설정
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
perror("Invalid address or address not supported");
close(client_fd);
exit(EXIT_FAILURE);
}
// 서버 연결
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Connection failed");
close(client_fd);
exit(EXIT_FAILURE);
}
// 데이터 송신
char *message = "Hello from Client!";
send(client_fd, message, strlen(message), 0);
// 데이터 수신
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("Server: %s\n", buffer);
}
// 소켓 닫기
close(client_fd);
return 0;
}
실행 순서
- 서버 프로그램을 실행합니다.
- 클라이언트 프로그램을 실행합니다.
- 클라이언트가 서버에 메시지를 보내면, 서버가 응답 메시지를 클라이언트로 전송합니다.
실행 결과 예시
서버 출력:
Server is listening on port 8080...
Client: Hello from Client!
클라이언트 출력:
Server: Hello from Server!
이 예제는 recv
와 send
함수의 기본 동작을 이해하고 네트워크 프로그램을 작성하는 데 중요한 기초를 제공합니다.
요약
recv
와 send
함수는 C 언어의 네트워크 프로그래밍에서 데이터를 송수신하는 핵심 도구입니다. 이 기사에서는 함수의 기본 동작, 소켓과의 상호작용, 에러 핸들링, 데이터 버퍼링, 일반적인 문제 해결 방법, 그리고 실습 예제를 통해 이 함수들의 효율적인 사용법을 다뤘습니다. 이를 통해 네트워크 통신에서 안정적이고 효율적인 데이터를 송수신하는 방법을 익힐 수 있습니다.