BSD Socket 사용법 정리
FreeBSD(+*nix) 버전 서버측 코드
본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 UDP Socket 서버측 코드의 사용 예를 정리한다.
전체 코드
먼저 전체적인 코드를 훑어본다.
/* 표준 헤더 include */
#include <stdint.h> // intptr_t
#include <stdlib.h> // exit()
#include <stdio.h> // printf(), sprintf()
#include <assert.h> // assert()
#include <string.h> // strerror()
#include <errno.h> // errno
/* 유닉스 헤더 include */
#include <pthread.h> // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_cleanup_pop()
#include <strings.h> // bzero()
#include <unistd.h> // ssize_t, read(), write()
/* 소켓 헤더 include */
#include <arpa/inet.h> // htons()
#include <netinet/in.h> // struct sockaddr_in
#include <sys/types.h> // socket
#include <sys/socket.h> // socket
#define SCK_PORT 9998
#define SCK_BUFF 512
#define SCK_MESG "Hello, UDP Client!"
void cleanup_routine(void * pfdsocket); // 소켓 함수 실행 중 오류가 발생할 때 수행할 공통의 해제 작업이 기술된 함수이다.
int main(void) {
pthread_t thself; // 현재 실행중인 스레드 번호를 보관한다.
int fdserver; // 서버 소켓 및 클라이언트 소켓에 대한 파일 디스크럽터이다.
socklen_t cbaddrserver, cbaddrclient; // 서버 및 클라이언트에 대한 인터넷 주소의 길이를 보관한다.
struct sockaddr_in addrserver, addrclient; // 서버 및 클라이언트에 대한 인터넷 주소를 보관한다.
int sockresult; // 소켓 함수의 실행 결과를 보관한다.
thself = pthread_self(); // 현재 실행중인 자기 자신의 스레드 번호를 가져온다.
pthread_cleanup_push(cleanup_routine, &fdserver); // 소켓 함수 실행 중 오류가 발생하였을 때 cleanup_routine을 실행한다.
/* 서버 측 소켓을 생성한다. */
fdserver = socket(AF_INET, SOCK_DGRAM, 0);
if (fdserver < 0) {
// 서버 측 소켓 생성에서 오류가 발생하면, errno를 출력한다.
printf("[FAILURE] socket(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
// cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
pthread_exit((void *)((intptr_t)errno));
}
printf("[SUCCESS] socket() @ pthread_t = %p\n", thself);
/* 서버측에서 연결하고자 하는 대상 컴퓨터의 인터넷 주소를 구성한다. */
cbaddrserver = sizeof(struct sockaddr_in); // 인터넷 주소의 길이를 구한다.
bzero(&addrserver, cbaddrserver); // sockaddr_in 구조체의 내용을 0으로 리셋한다.
addrserver.sin_family = AF_INET; // 구조체 내용 구성 - 1. 주소가 인터넷 주소이다.
addrserver.sin_addr.s_addr = htonl(INADDR_ANY); // 구조체 내용 구성 - 2. 서버 자신은 모든 주소로부터 연결에 응할 수 있다.
addrserver.sin_port = htons(SCK_PORT); // 구조체 내용 구성 - 3. 서버는 SCK_PORT 매크로상수에 지정된 포트 번호로 통신한다.
/* 앞서 구성한 구조체로 서버용 소켓에 바인딩한다. */
sockresult = bind(fdserver, (struct sockaddr *)&addrserver, cbaddrserver);
if (sockresult < 0) {
// 인터넷 주소 바인딩에서 오류가 발생하면, errno를 출력한다.
printf("[FAILURE] bind(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
// cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
pthread_exit((void *)((intptr_t)errno));
}
printf("[SUCCESS] bind() @ pthread_t = %p\n", thself);
while (1) {
size_t cbserver, cbclient;
char buffserver[SCK_BUFF], buffclient[SCK_BUFF];
ssize_t rwresult;
printf("SERVER WAITING...\n");
cbclient = SCK_BUFF * sizeof(char);
bzero(buffclient, cbclient);
cbaddrclient = sizeof(struct sockaddr_in);
bzero(&addrclient, cbaddrclient);
/* 클라이언트 측으로부터 문자열이 수신될 때까지 무한정 기다린다. */
rwresult = recvfrom(fdserver, buffclient, cbclient, 0, (struct sockaddr *)&addrclient, &cbaddrclient);
cbclient = strlen(buffclient) * sizeof(char);
if (rwresult < 0) {
// 문자열 수신에서 오류가 발생하면, errno를 출력한다.
printf("[FAILURE] recvfrom(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
// 연결 대기 다시 한다(무한루프).
continue;
}
printf("[SUCCESS] recvfrom(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
printf(">> %s\n", buffclient);
/* 서버에서 생성한 임의의 정수를 섞어서 클라이언트로 보낼 문자열을 구성한다. */
srand((unsigned int)time(NULL));
cbserver = SCK_BUFF * sizeof(char);
bzero(buffserver, cbserver);
sprintf(buffserver, "%s (%d) @ pthread_t = %p", SCK_MESG, rand(), thself);
/* 클라이언트로 문자열을 송신한다. */
cbserver = strlen(buffserver) * sizeof(char);
rwresult = sendto(fdserver, buffserver, cbserver, 0, (const struct sockaddr *)&addrclient, cbaddrclient);
if (rwresult < 0) {
// 문자열 송신에서 오류가 발생하면, errno를 출력한다.
printf("[FAILURE] sendto(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
// 연결 대기 다시 한다(무한루프).
continue;
}
printf("[SUCCESS] sendto(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
printf("<< %s\n", buffserver);
}
sockresult = close(fdserver);
if (sockresult < 0) {
printf("[FAILURE] close(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
pthread_exit((void *)((intptr_t)errno));
}
printf("[SUCCESS] close() @ pthread_t = %p\n", thself);
pthread_exit((void *)((intptr_t)errno)); // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
pthread_cleanup_pop(0); // pthread_cleanup_push를 실행했으면 이것을 실행하며 짝을 맞춘다.
return 0;
}
void cleanup_routine(void * pfdsocket) {
pthread_t thself;
int fdsocket;
int sockresult;
assert(pfdsocket != NULL);
fdsocket = *((int *)pfdsocket); // 지정된 주소에서 서버 또는 클라이언트 소켓 파일 디스크럽터를 복사해 온다.
thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.
if (fdsocket >= 0) {
/* 소켓을 닫는다. */
sockresult = close(fdsocket);
if (sockresult < 0) {
// 소켓 닫기에서 오류가 발생하면, errno를 출력한다.
printf("[FAILURE] close(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
} else {
printf("[SUCCESS] close() @ pthread_t = %p\n", thself);
}
}
return;
}
실행 결과 보기
위 소스 코드를 FreeBSD에서는 다음과 같이 컴파일한 후 실행할 수 있다.
$ cc 소스파일.c -o출력파일 -lpthread
pthread
함수를 사용하기 위한 옵션으로 특별히 -lpthread
가 추가되었다. 이 옵션이 없다면, pthread
함수를 호출하는 부분에서 '정의되지 않은 식별자' 오류를 낼 것이다.
클라이언트 접속이 확인될때마다 임의의 정수를 생성하고, 메시지에 이를 섞어서 클라이언트에게 보낸다.