C언어에서 소켓 프로그래밍은 네트워크 통신의 기반을 이해하는 데 필수적인 기술입니다. 특히, 데이터를 송수신하는 recv()
와 send()
함수는 소켓 통신의 핵심 요소로, 클라이언트-서버 모델에서 정보를 주고받는 데 사용됩니다. 본 기사에서는 이 두 함수의 작동 원리, 주요 옵션, 에러 핸들링 방법, 그리고 실용적인 응용 사례를 통해 소켓 데이터 송수신의 기초부터 고급 활용법까지 상세히 설명합니다.
소켓 프로그래밍의 기초 개념
소켓 프로그래밍은 네트워크 상에서 데이터를 송수신하기 위해 소켓이라는 추상화를 사용하는 기술입니다. 소켓은 운영체제와 네트워크 간의 인터페이스 역할을 하며, 이를 통해 두 장치 간의 통신이 가능해집니다.
소켓의 정의와 역할
소켓(Socket)은 네트워크 프로토콜 스택과 애플리케이션 간의 연결 지점을 제공합니다. 소켓을 통해 데이터를 송수신하거나 네트워크 연결을 관리할 수 있습니다.
소켓의 주요 구성 요소
- IP 주소: 통신할 장치의 네트워크 주소를 나타냅니다.
- 포트 번호: 동일한 장치 내에서 서로 다른 프로세스를 식별하기 위한 숫자입니다.
- 프로토콜: TCP(신뢰성 있는 연결 지향 프로토콜)와 UDP(비신뢰성 있는 비연결형 프로토콜) 중 하나를 선택합니다.
소켓 프로그래밍의 단계
- 소켓 생성:
socket()
함수를 사용하여 소켓을 생성합니다. - 주소 바인딩:
bind()
함수로 소켓에 IP 주소와 포트를 할당합니다. - 연결 대기 및 수락: 서버는
listen()
및accept()
를 통해 클라이언트 연결을 기다립니다. - 데이터 송수신: 클라이언트와 서버 간에
recv()
와send()
로 데이터를 교환합니다. - 연결 종료: 통신이 끝난 후
close()
로 소켓을 닫습니다.
클라이언트-서버 모델
소켓 프로그래밍은 일반적으로 클라이언트-서버 모델을 기반으로 이루어집니다.
- 서버는 소켓을 생성하고 연결 요청을 수락합니다.
- 클라이언트는 서버에 연결 요청을 보내고 데이터를 송수신합니다.
이러한 기초 개념은 recv()
와 send()
의 동작 원리를 이해하는 데 중요한 기반이 됩니다.
`recv()` 함수의 동작 원리
recv()
함수는 소켓을 통해 데이터를 수신하는 데 사용됩니다. 클라이언트-서버 모델에서 주로 서버가 클라이언트로부터 데이터를 받을 때 활용됩니다.
`recv()` 함수의 기본 구조
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
: 데이터를 수신할 소켓의 파일 디스크립터.buf
: 수신한 데이터를 저장할 버퍼의 주소.len
: 수신할 최대 데이터 크기.flags
: 수신 동작을 제어하는 플래그.
동작 방식
- 소켓
sockfd
를 통해 데이터를 기다립니다. - 데이터가 도착하면 지정된
buf
에 저장합니다. - 데이터 크기(바이트 단위)를 반환합니다.
- 데이터가 없고 연결이 종료되면 0을 반환합니다.
주요 플래그
MSG_PEEK
: 데이터를 버퍼에 유지하면서 읽습니다.MSG_WAITALL
: 요청한 데이터가 모두 수신될 때까지 대기합니다.MSG_DONTWAIT
: 블로킹 없이 비동기로 동작합니다.
예제 코드
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 1024
int main() {
int sockfd; // 이미 생성된 소켓 파일 디스크립터
char buffer[BUF_SIZE];
ssize_t received_bytes;
received_bytes = recv(sockfd, buffer, BUF_SIZE, 0);
if (received_bytes > 0) {
buffer[received_bytes] = '\0'; // 문자열 종료
printf("Received: %s\n", buffer);
} else if (received_bytes == 0) {
printf("Connection closed by peer.\n");
} else {
perror("recv() failed");
}
return 0;
}
사용 시 주의사항
- 버퍼 크기 초과 방지: 수신 데이터가 버퍼 크기를 초과하지 않도록 충분한 크기를 확보합니다.
- 에러 핸들링: 반환 값이
-1
이면 에러로 간주하고errno
를 참조해야 합니다. - 블로킹 처리: 소켓이 블로킹 모드라면 데이터가 도착할 때까지 대기합니다.
recv()
함수는 효율적인 데이터 수신을 위해 다양한 옵션과 플래그를 제공하며, 이를 통해 사용자 요구에 맞는 동작을 구현할 수 있습니다.
`send()` 함수의 동작 원리
send()
함수는 소켓을 통해 데이터를 송신하는 데 사용됩니다. 클라이언트-서버 모델에서 주로 클라이언트가 서버로 데이터를 보낼 때 활용되며, 서버가 클라이언트로 응답을 보낼 때도 사용됩니다.
`send()` 함수의 기본 구조
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
: 데이터를 송신할 소켓의 파일 디스크립터.buf
: 송신할 데이터가 저장된 버퍼의 주소.len
: 송신할 데이터의 크기.flags
: 송신 동작을 제어하는 플래그.
동작 방식
sockfd
를 통해 지정된 데이터를 전송합니다.- 전송된 데이터의 크기를 바이트 단위로 반환합니다.
- 오류 발생 시
-1
을 반환하며,errno
를 통해 자세한 오류 정보를 얻을 수 있습니다.
주요 플래그
MSG_CONFIRM
: UDP에서 사용되며, 상대방에게 도착 확인 메시지를 요청합니다.MSG_DONTWAIT
: 비블로킹(non-blocking) 송신을 수행합니다.MSG_NOSIGNAL
: 연결이 끊어졌을 때SIGPIPE
신호를 발생시키지 않습니다.
예제 코드
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
int sockfd; // 이미 생성된 소켓 파일 디스크립터
const char *message = "Hello, World!";
ssize_t sent_bytes;
sent_bytes = send(sockfd, message, strlen(message), 0);
if (sent_bytes > 0) {
printf("Sent %zd bytes: %s\n", sent_bytes, message);
} else {
perror("send() failed");
}
return 0;
}
사용 시 주의사항
- 전송 크기 확인: 반환된 값이
len
보다 작을 경우, 남은 데이터를 추가로 전송해야 할 수 있습니다. - 연결 상태 확인: 연결이 종료되었거나 상대방이 데이터를 받을 수 없는 상태일 경우, 오류가 발생합니다.
- 에러 핸들링: 반환 값이
-1
이면 전송 실패를 의미하며,errno
를 확인하여 원인을 파악해야 합니다.
동작 최적화
- 데이터를 전송하기 전에 가능한 한 데이터 크기를 줄이고, 네트워크 병목 현상을 줄이기 위해 패킷 크기를 조정합니다.
- 네트워크 상태를 고려하여 블로킹 또는 비블로킹 모드를 선택합니다.
send()
함수는 네트워크 통신에서 데이터를 송신하기 위한 기본 도구이며, 다양한 플래그와 설정을 통해 다양한 송신 요구를 충족할 수 있습니다.
`recv()`와 `send()`의 주요 옵션 및 플래그
recv()
와 send()
함수는 각각 데이터를 수신하고 송신하는 데 필수적이며, 다양한 옵션 및 플래그를 통해 동작 방식을 제어할 수 있습니다. 이러한 옵션은 특정 요구사항에 맞는 통신을 구현하는 데 유용합니다.
공통 플래그
두 함수 모두에서 사용할 수 있는 공통적인 플래그는 다음과 같습니다.
MSG_DONTWAIT
- 함수 호출을 비블로킹(non-blocking) 모드로 설정합니다. 데이터가 준비되지 않았을 때 즉시 반환하며, 이 경우
errno
가EAGAIN
또는EWOULDBLOCK
으로 설정됩니다. - 용도: 비동기 소켓 프로그래밍에서 대기 시간을 줄이기 위해 사용.
MSG_NOSIGNAL
- 연결이 끊어졌을 때
SIGPIPE
신호를 방지합니다. 대신 함수 호출이-1
을 반환하며,errno
가 설정됩니다. - 용도: 안정적인 오류 처리를 위해 사용.
`recv()` 전용 플래그
MSG_PEEK
- 데이터 버퍼의 내용을 읽으면서 데이터를 소켓에 남겨둡니다.
- 용도: 데이터를 삭제하지 않고 내용을 미리 확인할 때 사용.
MSG_WAITALL
- 지정한 크기(
len
)만큼의 데이터를 모두 수신할 때까지 대기합니다. - 용도: 데이터 손실을 방지하며, 패킷 기반 통신에서 유용.
`send()` 전용 플래그
MSG_CONFIRM
- UDP 소켓에서 사용되며, 상대방에게 데이터 도착 확인 메시지를 보냅니다.
- 용도: 상대방의 활성 상태를 확인하기 위해 사용.
MSG_EOR
- 레코드 기반 프로토콜에서 데이터가 끝났음을 나타냅니다.
- 용도: 데이터 스트림의 구분을 명확히 할 때 사용.
옵션 설정 함수
플래그 외에도 setsockopt()
함수를 사용하여 소켓 옵션을 설정할 수 있습니다.
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
SO_RCVBUF
: 수신 버퍼 크기를 설정.SO_SNDBUF
: 송신 버퍼 크기를 설정.SO_REUSEADDR
: 동일한 주소로 재바인딩 허용.
사용 사례
MSG_PEEK
와MSG_DONTWAIT
를 활용한 패킷 검사
데이터를 제거하지 않고 검사하여 필요한 데이터인지 확인 후 처리합니다.MSG_WAITALL
로 신뢰성 보장
데이터가 손실되지 않도록 완전히 수신한 후 처리합니다.MSG_NOSIGNAL
로 오류 방지
SIGPIPE 신호를 차단하여 프로그램 충돌을 예방합니다.
플래그와 옵션은 네트워크 소켓의 유연성과 효율성을 극대화하며, 다양한 통신 요구에 맞는 솔루션을 제공합니다. 적절한 조합을 통해 최적화된 네트워크 프로그램을 설계할 수 있습니다.
에러 핸들링과 트러블슈팅
recv()
와 send()
함수는 네트워크 통신의 핵심이지만, 다양한 원인으로 인해 오류가 발생할 수 있습니다. 적절한 에러 핸들링과 트러블슈팅 전략을 사용하면 이러한 문제를 효과적으로 해결할 수 있습니다.
주요 에러 코드
EAGAIN
또는EWOULDBLOCK
- 소켓이 비블로킹 모드일 때, 데이터가 준비되지 않은 상태에서 발생합니다.
- 해결 방법: 데이터를 수신하거나 송신하기 전에 준비 상태를 확인합니다.
EPIPE
- 연결이 종료된 소켓에 데이터를 송신하려고 시도할 때 발생합니다.
- 해결 방법:
MSG_NOSIGNAL
플래그를 사용하거나 연결 상태를 사전에 확인합니다. ECONNRESET
- 연결된 상대방이 연결을 강제로 종료했을 때 발생합니다.
- 해결 방법: 연결 종료 처리를 구현하고 재연결 로직을 추가합니다.
EINVAL
- 잘못된 인수나 상태에서 함수가 호출될 때 발생합니다.
- 해결 방법: 함수 호출 전에 모든 매개변수를 검증합니다.
에러 핸들링 기본 전략
- 반환값 확인
recv()
와send()
는 성공 시 전송 또는 수신한 바이트 수를 반환하며, 오류 발생 시-1
을 반환합니다.- 오류 발생 시
errno
를 참조하여 상세한 원인을 확인합니다.
- 로그 기록
- 오류 발생 시 로그를 기록하여 원인을 파악하고 디버깅에 활용합니다.
if (recv(sockfd, buffer, BUF_SIZE, 0) == -1) {
perror("recv() failed");
}
- 타임아웃 설정
- 수신 또는 송신 대기 시간이 과도하게 길어지는 것을 방지하기 위해 타임아웃을 설정합니다.
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
트러블슈팅 방법
- 네트워크 상태 확인
- 데이터 송수신 전에 네트워크 연결 상태를 점검합니다.
ping
명령어 또는 네트워크 모니터링 도구를 사용합니다.
- 데이터 패킷 검사
- Wireshark 같은 네트워크 분석 도구를 사용하여 송수신 데이터를 검사합니다.
- 디버깅 도구 활용
- GDB 같은 디버깅 도구를 사용하여 프로그램의 실행 상태를 추적합니다.
- 코드 검증
- 소켓 초기화, 바인딩, 연결 절차가 올바른지 점검합니다.
실제 적용 예
ssize_t received_bytes = recv(sockfd, buffer, BUF_SIZE, 0);
if (received_bytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("No data available. Non-blocking mode.\n");
} else {
perror("recv() failed");
}
} else if (received_bytes == 0) {
printf("Connection closed by peer.\n");
}
최적의 오류 처리 방법
- 오류 발생 가능성을 미리 분석하고 방지하는 코드를 작성합니다.
- 사용자 친화적인 오류 메시지를 제공하여 문제를 명확히 파악할 수 있도록 합니다.
에러 핸들링은 네트워크 통신에서 프로그램의 안정성과 신뢰성을 높이는 핵심 요소입니다. 잘 설계된 에러 처리와 트러블슈팅 로직은 실시간 네트워크 애플리케이션에서 큰 차이를 만들어냅니다.
비동기 데이터 송수신
비동기 데이터 송수신은 네트워크 애플리케이션의 성능과 응답성을 향상시키기 위해 중요한 기법입니다. recv()
와 send()
를 비동기적으로 사용하는 방법을 이해하면, 동시에 여러 작업을 처리하거나 대기 시간을 줄일 수 있습니다.
비동기 통신의 개념
- 블로킹 모드: 함수가 완료될 때까지 실행을 멈추고 대기합니다.
- 비동기 모드: 함수 호출이 즉시 반환되며, 작업 완료 여부는 별도의 방식으로 처리됩니다.
- 장점: 네트워크 대기 시간 동안 애플리케이션이 다른 작업을 수행할 수 있습니다.
소켓을 비동기로 설정하기
비동기 통신을 구현하려면 소켓을 논블로킹(non-blocking) 모드로 설정해야 합니다.
#include <fcntl.h>
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
비동기 `recv()`와 `send()`
소켓이 비동기 모드일 때, recv()
와 send()
함수는 데이터를 즉시 처리하거나, 준비되지 않았을 경우 EAGAIN
또는 EWOULDBLOCK
오류를 반환합니다.
ssize_t bytes_received = recv(sockfd, buffer, BUF_SIZE, 0);
if (bytes_received == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("No data available. Try again later.\n");
} else {
perror("recv() failed");
}
}
비동기 통신 구현 방법
select()
를 활용한 다중 입출력
여러 소켓에서 데이터를 비동기로 처리할 수 있습니다.
#include <sys/select.h>
fd_set read_fds;
struct timeval timeout;
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
timeout.tv_sec = 5; // 5초 대기
timeout.tv_usec = 0;
int result = select(sockfd + 1, &read_fds, NULL, NULL, &timeout);
if (result > 0 && FD_ISSET(sockfd, &read_fds)) {
recv(sockfd, buffer, BUF_SIZE, 0);
} else if (result == 0) {
printf("Timeout occurred.\n");
} else {
perror("select() failed");
}
epoll()
또는poll()
을 활용한 고성능 비동기 처리
대규모 소켓 연결에서 효율적인 이벤트 처리를 지원합니다.
epoll()
: 리눅스 환경에서 사용되는 고성능 이벤트 관리 함수.poll()
: 멀티 플랫폼에서 사용 가능한 다중 소켓 이벤트 처리 함수.
비동기 통신의 주요 과제
- 데이터 손실 방지
데이터가 준비되지 않았을 때 에러 처리를 적절히 구현해야 합니다. - 상태 관리
여러 소켓의 상태를 효율적으로 관리하기 위해 별도의 데이터 구조를 설계합니다. - CPU 사용률 최적화
이벤트 루프 설계를 통해 불필요한 반복 작업을 줄입니다.
응용 사례
- 채팅 서버: 다수의 클라이언트가 동시에 메시지를 주고받는 애플리케이션.
- 파일 전송 프로그램: 비동기 방식을 통해 파일 업로드와 다운로드를 병렬 처리.
비동기 데이터 송수신은 고성능 네트워크 애플리케이션의 핵심 기술로, 적절한 소켓 설정과 이벤트 기반 처리 방식을 활용하면 효율성을 극대화할 수 있습니다.
고속 데이터 전송 최적화
네트워크 통신에서 데이터 전송 속도를 최적화하는 것은 대규모 시스템의 성능을 높이는 데 매우 중요합니다. recv()
와 send()
를 활용하여 고속 데이터 전송을 구현하기 위해 다양한 기술적 접근 방식을 고려해야 합니다.
전송 속도를 저하시키는 요인
- 네트워크 대역폭 제한: 전송 가능한 데이터 양이 제한됩니다.
- RTT(Round-Trip Time): 패킷 왕복 시간으로 인해 전송 속도가 느려질 수 있습니다.
- 소켓 버퍼 크기: 기본 설정 크기가 충분하지 않으면 데이터 병목이 발생합니다.
- 패킷 손실: 데이터 재전송으로 인해 속도가 저하됩니다.
최적화 전략
1. 소켓 버퍼 크기 조정
- 송수신 버퍼 크기를 늘리면 대규모 데이터 전송 시 병목 현상을 줄일 수 있습니다.
int buf_size = 65536; // 64KB
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));
2. 데이터 청크 분할
- 데이터 크기가 클 경우, 전송을 작은 청크로 나누어 처리합니다.
- 송수신 중 데이터 누락을 방지하고 패킷 손실 시 재전송 비용을 줄입니다.
size_t total_sent = 0;
size_t chunk_size = 1024; // 1KB
while (total_sent < data_size) {
ssize_t sent = send(sockfd, data + total_sent, chunk_size, 0);
if (sent == -1) {
perror("send() failed");
break;
}
total_sent += sent;
}
3. 비동기 전송 활용
- 블로킹 방식을 피하고, 비동기 방식으로 전송하여 대기 시간을 줄입니다.
select()
,epoll()
,poll()
등의 비동기 이벤트 처리 기법을 사용합니다.
4. TCP 옵션 최적화
- Nagle 알고리즘 비활성화: 작은 패킷의 전송 속도를 높이기 위해 Nagle 알고리즘을 비활성화합니다.
int flag = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
5. 압축 및 데이터 직렬화
- 전송 전에 데이터를 압축하여 데이터 크기를 줄입니다.
- JSON이나 Protocol Buffers 같은 직렬화 기술을 사용하여 데이터를 구조화하고 효율적으로 전송합니다.
6. 멀티스레딩 사용
- 대규모 데이터를 병렬로 처리하여 전송 속도를 높입니다.
- 데이터의 각 부분을 별도 스레드에서 처리합니다.
모니터링 및 튜닝
- 네트워크 상태 모니터링: Wireshark 같은 도구를 사용하여 패킷 전송 상태를 분석합니다.
- 성능 튜닝: 송수신 속도를 주기적으로 테스트하고, 설정을 조정합니다.
응용 예제
#include <stdio.h>
#include <sys/socket.h>
#include <string.h>
#define BUF_SIZE 1024
void optimized_send(int sockfd, const char *data, size_t data_size) {
size_t total_sent = 0;
while (total_sent < data_size) {
ssize_t sent = send(sockfd, data + total_sent, BUF_SIZE, 0);
if (sent == -1) {
perror("send() failed");
return;
}
total_sent += sent;
}
printf("Total sent: %zu bytes\n", total_sent);
}
최적화의 효과
- 데이터 전송 속도와 신뢰성을 동시에 개선할 수 있습니다.
- 대규모 파일 전송, 스트리밍, 채팅 애플리케이션 등 다양한 네트워크 환경에서 성능이 향상됩니다.
고속 데이터 전송 최적화는 네트워크 애플리케이션의 효율성을 극대화하며, 사용자 경험을 향상시키는 중요한 기술적 도구입니다.
응용 예제: 채팅 프로그램 만들기
recv()
와 send()
함수를 활용하면 간단한 클라이언트-서버 기반의 채팅 프로그램을 구현할 수 있습니다. 이 섹션에서는 다중 클라이언트를 지원하는 서버와 클라이언트 구현 사례를 통해 소켓 데이터 송수신의 실질적인 응용 방법을 살펴봅니다.
프로그램 개요
- 서버
- 다수의 클라이언트 연결을 수락.
- 클라이언트로부터 메시지를 받아 다른 클라이언트에게 전달.
- 클라이언트
- 서버에 연결하여 메시지를 송수신.
서버 코드
서버는 select()
를 사용해 다중 클라이언트를 처리합니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define PORT 12345
#define MAX_CLIENTS 10
#define BUF_SIZE 1024
int main() {
int server_fd, new_socket, client_sockets[MAX_CLIENTS], max_sd, sd, activity;
struct sockaddr_in address;
char buffer[BUF_SIZE];
fd_set readfds;
// Initialize client sockets list
memset(client_sockets, 0, sizeof(client_sockets));
// Create server socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Bind socket to port
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");
exit(EXIT_FAILURE);
}
// Listen for connections
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("Listening on port %d\n", PORT);
while (1) {
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
max_sd = server_fd;
// Add client sockets to set
for (int i = 0; i < MAX_CLIENTS; i++) {
sd = client_sockets[i];
if (sd > 0) FD_SET(sd, &readfds);
if (sd > max_sd) max_sd = sd;
}
// Wait for activity
activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(server_fd, &readfds)) {
new_socket = accept(server_fd, NULL, NULL);
printf("New connection\n");
// Add new socket to client sockets list
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] == 0) {
client_sockets[i] = new_socket;
printf("Added new client\n");
break;
}
}
}
// Check for incoming messages
for (int i = 0; i < MAX_CLIENTS; i++) {
sd = client_sockets[i];
if (FD_ISSET(sd, &readfds)) {
int valread = recv(sd, buffer, BUF_SIZE, 0);
if (valread == 0) {
close(sd);
client_sockets[i] = 0;
} else {
buffer[valread] = '\0';
printf("Received: %s\n", buffer);
send(sd, buffer, strlen(buffer), 0); // Echo back to client
}
}
}
}
return 0;
}
클라이언트 코드
클라이언트는 서버에 연결하여 메시지를 주고받습니다.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUF_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in serv_addr;
char buffer[BUF_SIZE];
char message[BUF_SIZE];
// Create socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// Connect to server
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection failed");
return -1;
}
printf("Connected to server\n");
while (1) {
printf("Enter message: ");
fgets(message, BUF_SIZE, stdin);
send(sockfd, message, strlen(message), 0);
int valread = recv(sockfd, buffer, BUF_SIZE, 0);
if (valread > 0) {
buffer[valread] = '\0';
printf("Server: %s\n", buffer);
}
}
close(sockfd);
return 0;
}
프로그램의 특징
- 서버는 다중 클라이언트를 동시에 처리할 수 있습니다.
- 클라이언트는 메시지를 서버로 보내고 서버로부터 응답을 수신합니다.
recv()
와send()
함수의 주요 플래그와 옵션을 통해 안정적인 통신을 구현합니다.
이 채팅 프로그램은 네트워크 프로그래밍의 기본 원리를 이해하고, 실전에서 소켓 통신을 구현하는 데 유용한 예제입니다.
요약
recv()
와 send()
함수는 C언어에서 소켓 통신의 핵심으로, 데이터를 송수신하는 기본 도구입니다. 본 기사에서는 함수의 동작 원리, 주요 플래그 및 옵션, 에러 핸들링, 비동기 처리, 데이터 전송 최적화 방법, 그리고 실제 응용 사례인 채팅 프로그램 구현까지 상세히 다뤘습니다. 이를 통해 네트워크 프로그래밍의 기초부터 고급 활용 기술까지 익힐 수 있습니다. 적절한 함수 사용과 최적화 전략을 통해 안정적이고 효율적인 네트워크 애플리케이션을 개발할 수 있습니다.