코딩캣: 코딩하는 고양이.
[BSD Socket] FreeBSD(+*nix) 버전 UDP 서버측 코드
BSD Socket
2020. 3. 14. 21:21

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 함수를 호출하는 부분에서 '정의되지 않은 식별자' 오류를 낼 것이다.

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

 

클라이언트 접속이 확인될때마다 임의의 정수를 생성하고, 메시지에 이를 섞어서 클라이언트에게 보낸다.

클라이언트로부터 접속이 확인될 때마다 임의의 정수를 섞은 문자열을 전송한다.

'BSD Socket' 카테고리의 다른 글
더 보기...
태그 : 
댓글