소개
간단한 TCP 서버 및 클라이언트와 비교하여, 이 글의 서버 및 클라이언트 프로그램은 다음과 같은 점이 개선되었습니다:
inet_ntop()함수를 사용하여 서버 프로그램이 IPv4 및 IPv6 주소를 올바르게 처리할 수 있습니다.getaddrinfo()함수를 사용하여 서버 프로그램이 IPv4 및 IPv6 주소를 올바르게 처리할 수 있습니다.fork()함수를 사용하여 서버 프로그램이 여러 클라이언트의 연결 요청을 동시에 처리할 수 있습니다.waitpid()함수를 사용하여 서버 프로그램이 자식 프로세스의 종료를 올바르게 처리할 수 있습니다.
서버 코드
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT "43866" /* 포트 번호 */
#define BACKLOG 10 /* 최대 연결 요청 대기 수 */
#define MAXDATASIZE 100 /* 최대 데이터 전송량 */
/* 시그널 핸들러, 자식 프로세스 종료 처리 */
void sigchld_handler(int s)
{
/* waitpid()가 errno를 변경할 수 있으므로 저장 후 복원 */
int saved_errno = errno;
/* 모든 자식 프로세스 종료 대기 */
while (waitpid(-1, NULL, WNOHANG) > 0)
;
/* errno 복원 */
errno = saved_errno;
}
/* sockaddr 구조체의 IP 주소 가져오기 */
void *get_in_addr(struct sockaddr *sa)
{
/* 주소 체계 확인 */
if (sa->sa_family == AF_INET)
{
/* IPv4 주소 반환 */
return &(((struct sockaddr_in *)sa)->sin_addr);
}
/* IPv6 주소 반환 */
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
/* sockaddr 구조체의 포트 번호 가져오기 */
int get_in_port(struct sockaddr *sa)
{
/* 주소 체계 확인 */
if (sa->sa_family == AF_INET)
{
/* IPv4 포트 번호 반환 */
return (((struct sockaddr_in *)sa)->sin_port);
}
/* IPv6 포트 번호 반환 */
return (((struct sockaddr_in6 *)sa)->sin6_port);
}
/* 메인 함수 */
int main(int argc, char *argv[])
{
int sockfd, new_fd; /* sockfd: 리스닝 소켓, new_fd: 데이터 전송 소켓 */
struct addrinfo hints, *servinfo, *p; /* hints: addrinfo 구조체 설정용, servinfo: 주소 정보 저장, p: servinfo 순회용 addrinfo 포인터 */
struct sockaddr_storage their_addr; /* 클라이언트 IP 주소 및 포트 번호 정보 저장 */
socklen_t sin_size; /* accept()용 */
struct sigaction sa; /* sigaction()용 */
int yes = 1; /* setsockopt() 함수 설정용 */
int rv; /* getaddrinfo() 함수의 반환값 저장 */
char s[INET6_ADDRSTRLEN]; /* inet_ntop() 함수가 반환한 주소 정보 문자열 저장 */
int numbytes_send; /* send() 함수의 반환값 저장 */
int numbytes_recv; /* recv() 함수의 반환값 저장 */
char buf[MAXDATASIZE]; /* 수신 데이터 저장 */
/* hints 구조체 초기화 */
memset(&hints, 0, sizeof hints);
/* hints 구조체 설정 */
hints.ai_family = AF_UNSPEC; /* IPv4 또는 IPv6 미지정 */
hints.ai_socktype = SOCK_STREAM; /* TCP 스트림 소켓 */
hints.ai_flags = AI_PASSIVE; /* 로컬 IP 사용 */
/* 주소 정보 가져오기 */
if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0)
{
/* 오류 메시지 출력 */
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
/* servinfo 구조체 링크드 리스트 순회 */
for (p = servinfo; p != NULL; p = p->ai_next)
{
/* 소켓 생성 */
if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
/* 오류 메시지 출력 */
perror("server: socket");
continue;
}
/* 소켓 옵션 설정 */
// SO_REUSEADDR 옵션은 로컬 주소와 포트 재사용을 허용
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
/* 오류 메시지 출력 */
perror("setsockopt");
exit(1);
}
/* 소켓 바인딩 */
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
/* 서버 연결 실패 */
close(sockfd);
perror("server: bind");
continue;
}
break;
}
/* 결과 링크드 리스트 해제 */
freeaddrinfo(servinfo);
/* 바인딩 성공 여부 확인 */
if (p == NULL)
{
fprintf(stderr, "server: failed to bind\n");
exit(1);
}
/* 소켓 리스닝 */
if (listen(sockfd, BACKLOG) == -1)
{
perror("listen");
exit(1);
}
/* 시그널 핸들러 설정 */
sa.sa_handler = sigchld_handler; /* 모든 좀비 프로세스 처리 */
sigemptyset(&sa.sa_mask); /* 모든 시그널 마스킹 */
sa.sa_flags = SA_RESTART; /* 중단된 시스템 호출 재시작 */
if (sigaction(SIGCHLD, &sa, NULL) == -1)
{
/* 오류 메시지 출력 */
perror("sigaction");
exit(1);
}
printf("server: waiting for connections...\n");
/* accept() 메인 루프 */
while (1)
{
sin_size = sizeof their_addr;
/* 연결 요청 수락 */
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
if (new_fd == -1)
{ /* 연결 요청 수락 실패 */
perror("accept");
continue;
}
/* IP 주소를 문자열로 변환 */
inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
printf("server: got connection from %s, port %d, socketfd %d\n", s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);
/* 새 연결 요청 처리를 위해 자식 프로세스 생성 */
if (!fork())
{
/* 자식 프로세스는 리스닝 소켓이 필요 없음 */
close(sockfd);
/* 자식 프로세스 데이터 전송 처리 */
/* 전송 메시지 구성 */
char *msg = "Hello world from server!";
int msg_len = strlen(msg);
/* 클라이언트로 메시지 전송 */
if ((numbytes_send = send(new_fd, msg, msg_len, 0)) == -1)
perror("send");
/* 전송 정보 출력 */
printf("server: sent message: %s (%d bytes) to %s, port %d, sockfd %d\n", msg, numbytes_send, s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);
/* 클라이언트 메시지 수신 */
if ((numbytes_recv = recv(new_fd, buf, MAXDATASIZE - 1, 0)) == -1)
{
perror("recv");
exit(1);
}
buf[numbytes_recv] = '\0';
printf("server: received message: %s (%d bytes) from %s, port %d, sockfd %d\n", buf, numbytes_recv, s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);
/* 데이터 전송 소켓 닫기 */
close(new_fd);
printf("\n");
/* 자식 프로세스 종료 */
exit(0);
}
/* 부모 프로세스는 데이터 전송 소켓이 필요 없음 */
close(new_fd);
}
return 0;
}
/* 참고: 31-33쪽, Beej's Guide to Network Programming Using Internet Sockets */
클라이언트 코드
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT "43866" /* 포트 번호 */
#define MAXDATASIZE 100 /* 최대 데이터 전송량 */
/* sockaddr 가져오기, IPv4 또는 IPv6: */
void *get_in_addr(struct sockaddr *sa)
{
/* 주소 체계 확인 */
if (sa->sa_family == AF_INET)
{
/* IPv4 주소 반환 */
return &(((struct sockaddr_in *)sa)->sin_addr);
}
/* IPv6 주소 반환 */
return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}
int main(int argc, char *argv[])
{
int sockfd; /* sockfd: socket() 함수의 반환값 저장 */
char buf[MAXDATASIZE]; /* 버퍼, 수신 데이터 저장 */
struct addrinfo hints, *servinfo, *p; /* hints: addrinfo 구조체 설정용, servinfo: 주소 정보 저장, p: servinfo 순회용 addrinfo 포인터 */
int rv; /* getaddrinfo() 함수의 반환값 저장 */
char s[INET6_ADDRSTRLEN]; /* inet_ntop() 함수가 반환한 주소 정보 문자열 저장 */
int numbytes_recv; /* recv() 함수의 반환값 저장 */
int numbytes_send; /* send() 함수의 반환값 저장 */
/* 인자 개수 확인 */
if (argc != 2)
{
fprintf(stderr, "usage: ./client hostname\n");
exit(1);
}
/* hints 구조체 초기화 */
memset(&hints, 0, sizeof hints);
/* hints 구조체 설정 */
hints.ai_family = AF_UNSPEC; /* IPv4 또는 IPv6 미지정 */
hints.ai_socktype = SOCK_STREAM; /* TCP 스트림 소켓 */
/* 주소 정보 가져오기 */
if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
{
/* 오류 메시지 출력 */
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
/* servinfo 구조체 링크드 리스트 순회 */
for (p = servinfo; p != NULL; p = p->ai_next)
{
/* 소켓 생성 */
if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
{
/* 오류 메시지 출력 */
perror("client: socket");
continue;
}
/* 서버 연결 */
if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
{
/* 서버 연결 실패 */
close(sockfd);
perror("client: connect");
continue;
}
break;
}
/* servinfo 구조체를 모두 순회했으나 연결 성공하지 못함 */
if (p == NULL)
{
fprintf(stderr, "client: failed to connect to server\n"); /* 서버로 데이터 전송 */
return 2;
}
/* 주소 정보를 문자열로 변환 */
inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
/* 연결된 서버의 주소 정보 출력 */
printf("client: connecting to %s port %s\n", s, PORT);
/* servinfo 구조체 해제 */
freeaddrinfo(servinfo);
/* 데이터 수신 */
if ((numbytes_recv = recv(sockfd, buf, MAXDATASIZE - 1, 0)) == -1)
{
/* 데이터 수신 실패 */
perror("recv");
exit(1);
}
/* 문자열 종료자 추가 */
buf[numbytes_recv] = '\0';
/* 수신 데이터 출력 */
printf("client: received '%s' from server.\n", buf);
/* 전송할 데이터 구성 */
char *msg = "Hello world from client!";
int msg_len = strlen(msg);
/* 서버로 데이터 전송 */
if ((numbytes_send = send(sockfd, msg, msg_len, 0)) == -1)
{
/* 데이터 전송 실패 */
perror("send");
}
printf("client: sent '%s' (%d bytes) to server.\n", msg, numbytes_send);
/* 소켓 닫기 */
close(sockfd);
printf("client: closed socketfd %d.\n", sockfd);
return 0;
}
/* 참고: 31-33쪽, Beej's Guide to Network Programming Using Internet Sockets */
컴파일
gcc server.c -o server
gcc client.c -o client
실행 및 출력
$ ./server
server: waiting for connections...
server: got connection from 127.0.0.1, port 56836, socketfd 4
server: sent message: Hello world from server! (24 bytes) to 127.0.0.1, port 56836, sockfd 4
server: received message: Hello world from client! (24 bytes) from 127.0.0.1, port 56836, sockfd 4
$ ./client localhost
client: connecting to
client: received 'Hello world from server!' from server.
client: sent 'Hello world from client!' (24 bytes) to server.
client: closed socketfd 3
서버 실행 전에 클라이언트를 실행하면 다음과 같은 오류 메시지가 출력됩니다:
$ ./client localhost
client: connect: Connection refused
client: failed to connect to server