[BSD Socket] FreeBSD(+*nix) 버전 IPC 서버측 코드

BSD Socket 사용법 정리


FreeBSD(+*nix) 버전 IPC 서버측 코드


본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 IPC Socket 서버측 코드의 사용 예를 정리한다.

 

전체 코드


먼저 전체적인 코드를 훑어본다.

/* 표준 헤더 include */
#include <stdint.h> // intptr_t
#include <stdlib.h> // exit()
#include <stdio.h> // printf(), sprintf()
#include <assert.h> // assert()
#include <errno.h> // errno
#include <string.h> // strerror()

/* 유닉스 헤더 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()

/* Socket */
#include <arpa/inet.h> // htons()
#include <sys/types.h> // socket
#include <sys/socket.h> // socket
#include <sys/un.h> // struct sockaddr_un

#define SCK_FILE "/tmp/codingcat.ipc"
#define SCK_BUFF 512
#define SCK_MESG "Hello, IPC Client!"

void * start_routine(void * pfdclient); // 클라이언트가 접속할 때마다 새 스레드를 생성하여 실행할 함수이다.
void cleanup_routine(void * pfdsocket); // 소켓 함수 실행 중 오류가 발생할 때 수행할 공통의 해제 작업이 기술된 함수이다.

int main(void) {
	pthread_t thself; // 현재 실행중인 스레드 번호를 보관한다.

	int fdserver, fdclient; // 서버 소켓 및 클라이언트 소켓에 대한 파일 디스크럽터이다.
	socklen_t cbaddrserver, cbaddrclient; // 서버 및 클라이언트에 대한 인터넷 주소의 길이를 보관한다.
	struct sockaddr_un addrserver, addrclient; // 서버 및 클라이언트에 대한 인터넷 주소를 보관한다.
	int sockresult; // 소켓 함수의 실행 결과를 보관한다.

	thself = pthread_self(); // 현재 실행중인 자기 자신의 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, &fdclient); // 소켓 함수 실행 중 오류가 발생하였을 때 cleanup_routine을 실행한다.
	
    /* 서버 측 소켓을 생성한다. */
	fdserver = socket(AF_UNIX, SOCK_STREAM, 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_un); // 프로세스 주소의 길이를 구한다.
    bzero(&addrserver, cbaddrserver); // sockaddr_un 구조체의 내용을 0으로 리셋한다.
    addrserver.sun_family = AF_UNIX; // 구조체 내용 구성 - 1. 주소가 IPC 주소이다.
	strncpy(addrserver.sun_path, SCK_FILE, (sizeof(addrserver.sun_path) / sizeof(addrserver.sun_path[1]))); // 구조체 내용 구성 - 2. IPC로 접속할 수 있는 파일의 주소를 복사한다.

	/* 앞서 구성한 구조체로 서버용 소켓에 바인딩한다. */
	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);

	/* 연결 요청을 수용하기 위한 대기열을 구성한다. */
	sockresult = listen(fdserver, SOMAXCONN);
	if (sockresult < 0) {
		// 대기열 구성에서 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] listen(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
    }
	printf("[SUCCESS] listen() @ pthread_t = %p\n", thself);

	while (1) {
		pthread_t thclient;
		int thresult;

		printf("SERVER WAITING...\n");

		cbaddrclient = sizeof(struct sockaddr_un); // 클라이언트 측 인터넷 주소의 길이를 구한다.
		bzero(&addrclient, cbaddrclient); // sockaddr_in 구조체의 내용을 0으로 리셋한다.

		/* 클라이언트 측의 연결 요청이 있을 때까지 무한정 기다린다. */
		fdclient = accept(fdserver, (struct sockaddr *)&addrclient, &cbaddrclient);
		if (fdclient < 0) {
        	// 연결 대기에서 오류가 발생하면, errno를 출력한다.
			printf("[FAILURE] accept(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
            // 연결 대기 다시 한다(무한루프).
			continue;
        }
		printf("[SUCCESS] accept() @ pthread_t = %p\n", thself);

		// 클라이언트 연결이 확인되면, 후속 작업은 새 스레드에 맡긴다. (본 스레드는 연결 대기만 함)
		thresult = pthread_create(&thclient, NULL, start_routine, &fdclient);
		if (thresult < 0) {
        	// 새 스레드 생성에서 오류가 발생하면, errno를 출력한다.
			printf("[FAILURE] pthread_create(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
            // 연결 대기 다시 한다(무한루프).
			continue;
		}
		printf("[SUCCESS] pthread_create() -> %p @ pthread_t = %p\n", thclient, thself);
	}

	/* 위 루프가 무한루프이므로 이 이하는 직접 실행될 일은 없으나 형식적으로 적기만 함. */
	pthread_exit((void *)((intptr_t)errno)); // 현재 errno를 리턴하며 본 스레드 종료

	pthread_cleanup_pop(0); // pthread_cleanup_push를 실행했으면 이것을 실행하며 짝을 맞춘다.

	return 0;
}

/* 
 * 새 스레드가 생성되면서 호출될 함수이다. 
 * pdfclient : 클라이언트 측 소켓에 대한 파일 디스크럽터이다.
 */
void * start_routine(void * pfdclient) {
	pthread_t thself;  // 새 스레드의 번호를 보관한다.

	int fdclient; // 클라이언트 측 소켓에 대한 파일 디스크럽터를 보관한다.
	int sockresult; // 소켓 함수 실행 결과를 보관한다.

	size_t cbbuffserver, cbbuffclient; // 서버에서 클라이언트로 보내는 문자열의 길이와 클라이언트에서 서버로 수신된 문자열의 길이를 보관한다.
	char buffserver[SCK_BUFF], buffclient[SCK_BUFF]; // 서버에서 클라이언트로 보내는 문자열과 클라이언트에서 서버로 수신된 문자열을 보관한다.
	ssize_t rwresult; // 송수신한 결과(바이트 수)를 보관한다.

	fdclient = *((int *)pfdclient); // 지정된 주소에서 클라이언트 파일 디스크럽터를 복사해 온다.

	thself = pthread_self(); // 현재 실행 중인 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, pfdclient); // 소켓 함수에 오류가 발생했을 때 cleanup_routine을 호출한다.

	/* 서버에서 클라이언트로 보낼 문자열을 구성한다. 서버의 현 스레드 번호를 문자열로 알려준다. */
	cbbuffserver = SCK_BUFF * sizeof(char);
	bzero(buffserver, cbbuffserver);
	sprintf(buffserver, "%s @ pthread_t = %p", SCK_MESG, thself);

	/* 서버에서 클라이언트로 문자열을 보낸다. */
	cbbuffserver = strlen(buffserver) * sizeof(char);
	rwresult = write(fdclient, buffserver, cbbuffserver);
	if (rwresult < 0) {
		// 송신에서 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] write(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] write(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf(">> %s\n", buffserver);

	/* 클라이언트로부터 문자열을 수신할 버퍼를 구성한다. */
	cbbuffclient = SCK_BUFF * sizeof(char);
	bzero(buffclient, cbbuffclient);

	/* 클라이언트로부터 문자열을 받는다. */
	rwresult = read(fdclient, buffclient, cbbuffclient);
	cbbuffclient = strlen(buffclient) * sizeof(char);
	if (rwresult < 0) {
		// 수신에서 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] read(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] read(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf(">> %s\n", buffclient);

	pthread_exit((void *)((intptr_t)0)); // 현재 errno를 리턴하며 본 스레드 종료
	pthread_cleanup_pop(0); // pthread_cleanup_push를 실행했으면 이것을 실행하며 짝을 맞춘다.

	return (void *)((intptr_t)errno);
}

/*
 * 소켓 함수에 오류가 발생했을 경우 수행할 공통의 정리 작업이다.
 * pfdsocket : 오류가 발생하여 닫아야 할 소켓이다.
 */
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);
		}
		printf("[SUCCESS] close() @ pthread_t = %p\n", thself);
	}

	return;
}

실행 결과 보기


위 소스 코드를 FreeBSD에서는 다음과 같이 컴파일한 후 실행할 수 있다.

$ cc 소스파일.c -o출력파일 -lpthread

pthread 함수를 사용하기 위한 옵션으로 특별히 -lpthread가 추가되었다. 이 옵션이 없다면, pthread 함수를 호출하는 부분에서 '정의되지 않은 식별자' 오류를 낼 것이다.

클라이언트로부터 연결을 기다리고 있는 IPC 서버 프로그램.

ID가 0x800681000인 thread가 무한루프를 돌리며 접속을 기다리다가 클라이언트로부터 접속이 발생하면 새 thread를 생성되면서 해당 클라이언트와 통신하게 된다. 여기서는 0x800681000로부터 0x800681500가 파생되고, 0x800681500를 통해 클라이언트가 통신한다. 이 과정은 다음과 같이 화면으로 출력된다.

클라이언트로부터 접속이 발생할 때마다 새 thread를 생성하여 통신한다.

IPC 서버 프로그램을 실행한 후에는 다음과 같이 소켓으로 지정한 경로에 파일이 생성된 것을 확인할 수가 있다.

$ ls -al | grep 파일이름

해당 경로에 실제로 임시 파일이 생성되었음을 확인할 수 있다.