^(코딩캣)^ = @"코딩"하는 고양이;
썸네일 이미지
[BSD Socket] WinSock 버전 TCP(UDP) 클라이언트측 코드
BSD Socket 사용법 정리 WinSock 버전 TCP 클라이언트측 코드 BSD Socket 프로그램은 WinSock 기반의 Windows 프로그램으로 포팅이 가능하다. 본 포스팅에서는 이전 내용(FreeBSD(+*nix) 버전 TCP 클라이언트측 코드)에서 작성한 *nix용 소스 코드를 Windows용 프로그램으로 옮긴 예를 통해 WinSock에서 TCP(UDP) 클라이언트 코드를 작성하는 방법에 대해 정리한다. *nix용 소스코드는 프롬프트 상에서 작동되기 때문에 printf 함수에 의한 표준 출력(stdout)으로 프로그램의 작동 과정을 출력하였지만, Windows에서는 프롬프트 상에서 구동되는 프로그램을 작성하는 경우보다는 Windows API 또는 MFC 기반의 창(Window) 형태의 프로..
BSD Socket
2020. 6. 7. 08:18

[BSD Socket] WinSock 버전 TCP(UDP) 클라이언트측 코드

BSD Socket
2020. 6. 7. 08:18

BSD Socket 사용법 정리


WinSock 버전 TCP 클라이언트측 코드


BSD Socket 프로그램은 WinSock 기반의 Windows 프로그램으로 포팅이 가능하다. 본 포스팅에서는 이전 내용(FreeBSD(+*nix) 버전 TCP 클라이언트측 코드)에서 작성한 *nix용 소스 코드를 Windows용 프로그램으로 옮긴 예를 통해 WinSock에서 TCP(UDP) 클라이언트 코드를 작성하는 방법에 대해 정리한다.

*nix용 소스코드는 프롬프트 상에서 작동되기 때문에 printf 함수에 의한 표준 출력(stdout)으로 프로그램의 작동 과정을 출력하였지만, Windows에서는 프롬프트 상에서 구동되는 프로그램을 작성하는 경우보다는 Windows API 또는 MFC 기반의 창(Window) 형태의 프로그램을 작성하는 경우가 더 많을 것이므로 프로그램의 작동 과정도 표준 출력이 아니라 디버그 출력을 통해 나타낼 것이다. 변수 또는 버퍼의 값을 간편하게 출력하기 위해 OutputFormattedDebugString이라는 사용자 정의 함수를 사용하였다. 이 함수에 대한 선언과 정의는 다른 포스트([Windows API] OutputDebugString을 printf처럼 서식(포맷) 적용하여 사용하기)에 자세히 적어 두었다.

 

포팅된 코드


#pragma comment(lib, "ws2_32.lib")

#include <WinSock2.h>
#include <Windows.h>

#define SCK_PORT 9999
#define SCK_ADDR "192.168.204.169"
#define SCK_BUFF 512
#define SCK_MESG "Hello, TCP Server!"

void OutputFormattedDebugString(LPCTSTR format, ...);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
	DWORD dwThreadId;

	WSADATA wsaData;

	SOCKET fdserver;
	SOCKADDR_IN addrserver;
	int cbaddrserver;

	int cbbuffserver, cbbuffclient;
	char buffserver[SCK_BUFF], buffclient[SCK_BUFF];
	int rwresult;

	int sockresult;

	dwThreadId = GetCurrentThreadId();

	ZeroMemory(&wsaData, sizeof(WSADATA));
	sockresult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (sockresult != 0) {
		OutputFormattedDebugString(TEXT("[FAILURE] WSAStartup(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] WSAStartup() @ dwThreadId = %p\n"), dwThreadId);

	fdserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fdserver == INVALID_SOCKET) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] socket(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] WSAStartup() @ dwThreadId = %p\n"), dwThreadId);

	cbaddrserver = sizeof(SOCKADDR_IN);
	ZeroMemory(&addrserver, cbaddrserver);
	addrserver.sin_family = AF_INET;
	addrserver.sin_addr.S_un.S_addr = inet_addr(SCK_ADDR);
	addrserver.sin_port = htons(SCK_PORT);

	sockresult = connect(fdserver, (SOCKADDR *)&addrserver, cbaddrserver);
	if (sockresult < 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] connect(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] connect() @ dwThreadId = %p\n"), dwThreadId);
	
	cbbuffserver = sizeof(buffserver);
	ZeroMemory(buffserver, cbbuffserver);

	rwresult = recv(fdserver, buffserver, cbbuffserver, 0);
	cbbuffserver = strlen(buffserver) * sizeof(char);
	if (rwresult < 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] recv(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] recv(): %d byte(s) @ dwThreadId = %p\n"), rwresult, dwThreadId);
	OutputFormattedDebugString(TEXT(">> %hs\n"), buffserver);

	cbbuffclient = sizeof(buffclient);
	ZeroMemory(buffclient, cbbuffclient);

	sprintf(buffclient, "%s @ dwThreadId = %p", SCK_MESG, dwThreadId);
	cbbuffclient = strlen(buffclient) * sizeof(char);
	rwresult = send(fdserver, buffclient, cbbuffclient, 0);
	if (rwresult < 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] send(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] send(): %d byte(s) @ dwThreadId = %p\n"), rwresult, dwThreadId);
	OutputFormattedDebugString(TEXT("<< %hs\n"), buffclient);

EXITPROC_WINMAIN:
	if (fdserver != INVALID_SOCKET) {
		sockresult = closesocket(fdserver);
		if (sockresult != 0) {
			sockresult = WSAGetLastError();
			OutputFormattedDebugString(TEXT("[FAILURE] closesocket(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		}
		OutputFormattedDebugString(TEXT("[SUCCESS] closesocket() @ dwThreadId = %p\n"), dwThreadId);
	}

	sockresult = WSACleanup();
	if (sockresult != 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] WSACleanup(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] WSACleanup() @ dwThreadId = %p\n"), dwThreadId);

	return 0;
}

WSAStartup, WSACleanup, socket, closesocket은 이전 포스트(WinSock 버전 TCP 서버측 코드)와 동일하므로 생략한다. 클라이언트 측에서 사용하는 함수는 connect이다.

 

connect


*nix버전 connect 함수의 선언은 다음과 같다.

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // sys/types.h, sys/socket.h 순으로 include

WinSock 버전 connect 함수의 선언은 다음과 같다.

int connect(SOCKET s, const struct sockaddr FAR *name, int namelen); // WinSock2.h

이 함수에서도 인터넷 주소를 나타내는 구조체인 struct sockaddr_in(SOCKADDR_IN)이 사용된다. 관련 내용은 이미 이전 포스트 (WinSock 버전 TCP 서버측 코드)에서 소개하였다.

접속에 성공하면 두 함수 모두 0을 반환한다. 그렇지 않은 경우 *nix 버전은 -1을 반환하고 errno에 오류 내용을 설정한다. WinSock 버전은 SOCKET_ERROR를 반환한다. 오류 내용은 WSAGetLastError로 확인할 수 있다.

 

recv, recvfrom, send, sendto


클라이언트와 서버 양측이 데이터를 송수신할 때 사용하는 함수이다. 이미 살펴 보았듯이 TCP에서는 recv, send를 사용하고 UDP에서는 recvfrom, sendto를 사용한다. 4가지 함수의 *nix 선언과 비교하여 WinSock에서 선언된 것과 비교한다.

먼저 *nix에서 4가지 함수 선언은 다음과 같았다.

ssize_t recv(int sockfd, void *buf, size_t len, int flags); // sys/types.h, sys/socket.h 순으로 include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // sys/types.h, sys/socket.h 순으로 include
ssize_t send(int sockfd, const void *buf, size_t len, int flags); // sys/types.h, sys/socket.h 순으로 include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // sys/types.h, sys/socket.h 순으로 include

또한 *nix에서 sockfd는 파일 디스크립터의 일종이므로 운영체제 입출력 함수인 read, write를 사용하면 flags = 0인 호출과 동일하다고 하였다.

ssize_t read(int fd, void *buf, size_t count); // unistd.h
ssize_t write(int fd, const void *buf, size_t count); // unistd.h

size_t는 unsigned형 정수이지만 ssize_t는 signed형 정수이다. 송수신에 성공하면 바이트 수를 반환하고 그렇지 않으면 -1을 반환하고 errno에 그 내용이 기록된다. 이에 비해 WinSock 버전에서는 read, write 같은 함수는 사용할 수 없고 기본 소켓 함수만 호출 가능하다.

int recv(SOCKET s, char FAR *buf, int len, int flags); // WinSock2.h
int recvfrom(SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);
int send(SOCKET s, const char FAR *buf, int len, int flags);
int sendto(SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

WinSock의 위 함수들은 송수신에 성공하면 바이트 수를 반환하고 그렇지 않으면 SOCKET_ERROR를 반환한다. 오류 내용은 WSAGetLastError로 확인할 수 있다.

 

실행 결과 보기


소스 코드를 실행한 결과는 다음과 같다.

서버와 문자열을 주고받은 TCP 클라이언트측 프로그램

여기서 0xFFF6242D는 클라이언트 측 Thread ID이다. 서버로부터는 0xFFF6A755라는 서버측 Thread ID를 수신하였고 이것으로 WinSock 서버 및 클라이언트 프로그램이 정상적으로 작동함을 확인할 수 있다.

 

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] WinSock 버전 TCP 서버측 코드
BSD Socket 사용법 정리 WinSock 버전 TCP 서버측 코드 BSD Socket 프로그램은 WinSock 기반의 Windows 프로그램으로 포팅이 가능하다. 본 포스팅에서는 이전 내용(FreeBSD(+*nix) 버전 TCP 서버측 코드)에서 작성한 *nix용 소스 코드를 Windows용 프로그램으로 옮긴 예를 통해 WinSock에서 TCP 서버 코드를 작성하는 방법에 대해 정리한다. *nix용 소스코드는 프롬프트 상에서 작동되기 때문에 printf 함수에 의한 표준 출력(stdout)으로 프로그램의 작동 과정을 출력하였지만, Windows에서는 프롬프트 상에서 구동되는 프로그램을 작성하는 경우보다는 Windows API 또는 MFC 기반의 창(Window) 형태의 프로그램을 작성하는 경우가 더..
BSD Socket
2020. 6. 5. 23:54

[BSD Socket] WinSock 버전 TCP 서버측 코드

BSD Socket
2020. 6. 5. 23:54

BSD Socket 사용법 정리


WinSock 버전 TCP 서버측 코드


BSD Socket 프로그램은 WinSock 기반의 Windows 프로그램으로 포팅이 가능하다. 본 포스팅에서는 이전 내용(FreeBSD(+*nix) 버전 TCP 서버측 코드)에서 작성한 *nix용 소스 코드를 Windows용 프로그램으로 옮긴 예를 통해 WinSock에서 TCP 서버 코드를 작성하는 방법에 대해 정리한다.

*nix용 소스코드는 프롬프트 상에서 작동되기 때문에 printf 함수에 의한 표준 출력(stdout)으로 프로그램의 작동 과정을 출력하였지만, Windows에서는 프롬프트 상에서 구동되는 프로그램을 작성하는 경우보다는 Windows API 또는 MFC 기반의 창(Window) 형태의 프로그램을 작성하는 경우가 더 많을 것이므로 프로그램의 작동 과정도 표준 출력이 아니라 디버그 출력을 통해 나타낼 것이다. 변수 또는 버퍼의 값을 간편하게 출력하기 위해 OutputFormattedDebugString이라는 사용자 정의 함수를 사용하였다. 이 함수에 대한 선언과 정의는 다른 포스트([Windows API] OutputDebugString을 printf처럼 서식(포맷) 적용하여 사용하기)에 자세히 적어 두었다.

 

포팅된 코드

#pragma comment(lib, "ws2_32.lib")

#include <WinSock2.h>
#include <Windows.h>

#define SCK_PORT 9999
#define SCK_BUFF 512
#define SCK_MESG "Hello, TCP Client!"

VOID OutputFormattedDebugString(LPCTSTR format, ...);
DWORD WINAPI ClientThreadProc(LPVOID lpParameter);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
	WSADATA wsaData;
	DWORD dwThreadId, dwClientThreadId;
	int cbaddrserver, cbaddrclient;
	SOCKADDR_IN addrserver, addrclient;
	SOCKET fdserver, fdclient;

	int sockresult;

	dwThreadId = GetCurrentThreadId();

	sockresult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (sockresult != 0) {
		OutputFormattedDebugString(TEXT("[FAILURE] WSAStartup(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] WSAStartup() @ dwThreadId = %p\n"), dwThreadId);

	fdserver = fdclient = INVALID_SOCKET;
	fdserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fdserver == INVALID_SOCKET) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] socket(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] socket() @ dwThreadId = %p\n"), dwThreadId);

	cbaddrserver = sizeof(SOCKADDR_IN);
	ZeroMemory(&addrserver, cbaddrserver);
	addrserver.sin_family = AF_INET;
	addrserver.sin_port = htons(SCK_PORT);
	addrserver.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	sockresult = bind(fdserver, (SOCKADDR *)&addrserver, cbaddrserver);
	if (sockresult != 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] bind(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] bind() @ dwThreadId = %p\n"), dwThreadId);

	sockresult = listen(fdserver, SOMAXCONN);
	if (sockresult != 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] listen(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_WINMAIN;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] listen() @ dwThreadId = %p\n"), dwThreadId);

	while (TRUE) {
		cbaddrclient = sizeof(SOCKADDR_IN);
		ZeroMemory(&addrclient, cbaddrclient);

		fdclient = accept(fdserver, (SOCKADDR *)&addrclient, &cbaddrclient);
		if (fdclient == INVALID_SOCKET) {
			sockresult = WSAGetLastError();
			OutputFormattedDebugString(TEXT("[FAILURE] accept(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
			goto EXITPROC_WINMAIN;
		}
		OutputFormattedDebugString(TEXT("[SUCCESS] accept() @ dwThreadId = %p\n"), dwThreadId);

		dwClientThreadId = 0;
		if (CreateThread(NULL, 0, ClientThreadProc, (LPVOID)((INT_PTR)fdclient), 0, &dwClientThreadId) == NULL) {
			DWORD dwLastError = GetLastError();
			OutputFormattedDebugString(TEXT("[FAILURE] CreateThread(): 0x%08x @ dwThreadId = %p\n"), dwLastError, dwThreadId);
		}
		OutputFormattedDebugString(TEXT("[SUCCESS] CreateThread(): dwClientThreadId = %p @ dwThreadId = %p\n"), dwClientThreadId, dwThreadId);
	}

EXITPROC_WINMAIN:
	if (fdserver != INVALID_SOCKET) {
		sockresult = closesocket(fdserver);
		if (sockresult != 0) {
			sockresult = WSAGetLastError();
			OutputFormattedDebugString(TEXT("[FAILURE] closesocket(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
			ExitThread(sockresult);
		}
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] closesocket() @ dwThreadId = %p\n"), dwThreadId);

	sockresult = WSACleanup();
	if (sockresult != 0) {
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] WSACleanup(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		ExitThread(sockresult);
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] WSACleanup() @ dwThreadId = %p\n"), dwThreadId);

	return 0;
}

DWORD WINAPI ClientThreadProc(LPVOID lpParameter) {
	DWORD dwThreadId;

	int fdclient;
	int sockresult;

	int cbbuffserver, cbbuffclient;
	char buffserver[SCK_BUFF], buffclient[SCK_BUFF];
	int rwresult;

	dwThreadId = GetCurrentThreadId();
	fdclient = (int)((INT_PTR)lpParameter);

	cbbuffserver = sizeof(buffserver);
	ZeroMemory(buffserver, cbbuffserver);
	sprintf(buffserver, "%s @ dwThreadId = %p", SCK_MESG, dwThreadId);

	cbbuffserver = (strlen(buffserver) + 1) * sizeof(char);
	rwresult = send(fdclient, buffserver, cbbuffserver, 0);
	if (rwresult < 0) {
		// 오류가 발생하면 오류 내용을 출력 후 Thread를 종료시킨다.
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] send(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_THREAD;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] send(): %d byte(s) @ dwThreadId = %p\n"), rwresult, dwThreadId);
	OutputFormattedDebugString(TEXT("<< %hs\n"), buffserver);

	cbbuffclient = sizeof(buffclient);
	ZeroMemory(buffclient, cbbuffclient);
	rwresult = recv(fdclient, buffclient, cbbuffclient, 0);
	if (rwresult < 0) {
		// 오류가 발생하면 오류 내용을 출력 후 Thread를 종료시킨다.
		sockresult = WSAGetLastError();
		OutputFormattedDebugString(TEXT("[FAILURE] recv(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
		goto EXITPROC_THREAD;
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] recv(): %d byte(s) @ dwThreadId = %p\n"), rwresult, dwThreadId);
	OutputFormattedDebugString(TEXT(">> %hs\n"), buffclient);

EXITPROC_THREAD: // Thread가 종료되기 전 실행되어야 할 내용
	if (fdclient != INVALID_SOCKET) {
		sockresult = closesocket(fdclient);
		if (sockresult < 0) {
			// 오류가 발생하면 오류 내용을 출력 후 Thread를 종료시킨다.
			sockresult = WSAGetLastError();
			OutputFormattedDebugString(TEXT("[FAILURE] closesocket(): %d @ dwThreadId = %p\n"), sockresult, dwThreadId);
			ExitThread(sockresult);
		}
	}
	OutputFormattedDebugString(TEXT("[SUCCESS] closesocket() @ dwThreadId = %p\n"), dwThreadId);
	
	return 0;
}

전체적인 구조는 *nix버전과 크게 차이가 없다. socket, bind, listen 순서로 서버 소켓을 구성하고, 무한 루프 내에서 accept가 클라이언트를 기다리다가, 클라이언트 접속이 확인되면 새로운 thread를 생성하고, 새로운 thread에서 클라이언트와의 통신을 수행한다. 그러나 몇 가지 다른 부분을 살펴보면,

 

WSAStartup, WSACleanup


WSAStartupWSACleanup은 WinSock을 사용하기 전에 리셋하고, 사용 후 정리하는 함수이다. 이는 Unix/Linux의 Socket에는 없는 Windows만의 과정이다.

int WSAStartup(
	WORD wVersionRequested,
	LPWSADATA lpWSAData
);
int WSACleanup(void);

 

함수의 실행 결과는 정수로 리턴한다. 성공하면 0을 반환하고 그렇지 않으면 다른 값을 반환한다. 오류의 상세한 종류는 WSAGetLastError의 반환값으로 확인 가능하다. 단, WSAStartup이 실패할 경우 WinSock이 아예 적용되지 않은 상태(WinSock의 오류 코드 변수를 사용할 수 없는 상태)이기 때문에 WSAStartup이 반환하는 값이 곧 상세한 오류 내용이다.

int WSAGetLastError(void);

 

다음은 Windows 프로그램에서 WinSock을 시작하고 종료하는 예이다. 윈도우 운영체제는 Windows 95/NT를 전후로 하여 매우 크게 변하였고, WinSock역시 이 때를 기준으로 WinSock 1과 WinSock 2로 나뉜다. Windows 10을 쓰는 지금까지도 WinSock 2가 이어져 내려온다.

먼저 WinSock을 사용하려면 별도로 라이브러리(ws2_32.lib)와 헤더(ws2_32.h)를 끌어와야 한다.

#pragma comment(lib, "ws2_32.lib")
#include <WinSock2.h>

 

그리고 다음과 같은 뼈대를 바탕으로 변용해서 사용하면 된다. 이후의 함수 호출은 Unix/Linux와 같다.

WORD wVersionRequested;
WSADATA stWSAData;

/* using WinSock 2.2 */
wVersionRequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionRequested, &stWSAData) == 0) {
	/* TODO: WinSock */
} else {
	/* TODO: 오류 처리 */
}

WSACleanup();

그리고 DWORD와 같은 자료형 및 각종 운영체제 함수들을 사용하기 위하여 윈도우 헤더를 가져온다.

#include <Windows.h>

 

SOCKET, SOCKADDR_IN


*nix에서 파일 디스크립터의 일종으로서 int형으로 다뤄지던 소켓 객체가 WinSock에서는 의도를 약간 변형시켜 SOCKET이라는 별도의 자료형으로 다뤄진다(어쨌거나 정수인 것은 마찬가지임). 또한 인터넷 주소를 지정하는 struct sockaddr_in 구조체는 SOCKADDR_IN으로 typedef되었다. 몰론 전자를 사용해도 된다.

 

그 외 선언의 차이


그 외 소켓 함수의 선언에서 세세한 차이가 있다.

*nix의 socket 선언

int socket(int domain, int type, int protocol);

WinSock의 socket 선언

SOCKET socket(int af, int type, int protocol);

 

*nix의 bind 선언

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

WinSock의 bind 선언

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);

이 부분에서 특히 struct sockaddr_in의 멤버 이름에 차이가 있음을 확인할 수 있다. AF_INET 모드로 인터넷 주소를 지정할 때 *nix에서는

struct sockaddr_in {
	sa_family_t    sin_family; /* address family: AF_INET */
	in_port_t      sin_port;   /* port in network byte order */
	struct in_addr sin_addr;   /* internet address */
};

와 같이 선언되었다면 WinSock에서는,

struct sockaddr_in {
	short          sin_family;
	unsigned short sin_port;
	struct in_addr sin_addr;
	char           sin_zero[8];
};

와 같이 sin_zero 멤버가 추가됨을 확인할 수 있다. 그러나 이 멤버는 어차피 사용되지 않고 0으로 리셋할 멤버이므로 무시한다 하더라도, sin_addr으로 명명된 struct in_addr 멤버는 *nix와 WinSock에서 중대한 차이가 있다. *nix와 WinSock 사이에 포팅을 할 때는 이 부분에서 "정의되지 않은 식별자" 오류가 높은 확률로 발생할 것이다.

*nix의 struct in_addr 선언은 다음과 같다.

struct in_addr {
	uint32_t       s_addr;     /* address in network byte order */
};

그러나 WinSock의 struct in_addr 선언은 다음과 같다.

struct in_addr {
	union {
		struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
		struct { u_short s_w1,s_w2; } S_un_w;
		u_long S_addr;
	} S_un;
}

struct in_addr 구조체 안에 S_un 공용체가 있다.

이 공용체 안에 xxx.xxx.xxx.xxx 형태의 4바이트 아이피 주소를 직접 적을 수 있는 S_un_b이 있고, 딱히 비중있는 용도는 없어 보이는 S_un_w가 있고, 또한 *nix의 s_addr과 같은 방식으로 접근하는 S_addr이 있다.

따라서 *nix에서 주소 지정을 위해 코딩했던 (struct sockaddr_in).sin_addr.s_addr의 접근은 WinSock에서 (struct sockaddr_in).sin_addr.S_un.S_addr이 된다.

 

*nix의 listenaccept 선언

int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

WinSock의 listenaccept 선언

int listen(SOCKET s, int backlog);
SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

 

closesocket


*nix에서 socket이 반환하는 정수는 파일 디스크립터의 일종이므로, 운영체제 파일 입출력 함수인 close로 소켓 통신을 통료할 수 있다. 그러나 Windows 운영체제는 파일 디스크립터에 해당하는 개념이 없고, 입출력 대상에 따라 사용하는 함수가 파편화되어 있다. Windows에서는 socket으로 생성한 소켓은 closesocket으로 종료시킨다.

int close(int fd); // unistd.h
int closesocket(SOCKET s); // WinSock2.h

소켓 종료에 성공하면 두 함수 모두 0을 반환한다. 그렇지 않은 경우 *nix 버전은 -1을 반환하고 errno에 오류 내용을 설정한다. WinSock 버전은 SOCKET_ERROR를 반환한다. 오류 내용은 WSAGetLastError로 확인할 수 있다.

 

Thread 작업에서 나타나는 차이


*nix와 Windows 둘 다 다중 스레드를 지원하기는 하지만, 세밀한 부분에서 차이가 있다.

먼저 *nix에서는 thread를 종료할 때 자동으로 실행되는 내용을 기술할 수 있는 pthread_cleanup_push 함수가 있었다. 그러나 Windows API에는 Thread가 종료될 때 특정 작동을 기술할 수 있는 기능이 없으므로, 부득이하게 goto 문을 사용하였다. 이는 온전한 Windows 응용 프로그램에서 event에 의한 실행으로 대체될 수 있다.

 

실행 결과 보기


다음은 소스 코드를 실행한 화면이다.

클라이언트와 문자열 송수신을 끝내고 다른 클라이언트의 연결을 기다리고 있는 TCP 서버 측 프로그램

현재 WinMain을 구동하고 있는 thread의 번호는 0xFFF688ED이다. 무한루프를 돌리면서 클라이언트를 기다리다가, 클라이언트 접속이 확인되면 새 스레드를 생성하여 클라이언트와 통신한다. 이 새로운 스레드의 번호는 0xFFF6A755이며 클라이언트가 접속할 때마다 전혀 다른 번호로 thread가 생성될 것이다. 또한 클라이언트로부터 문자열을 수신하여 클라이언트 측에서 구동하고 있는 thread의 번호가 0xFFF6242D임을 확인할 수 있었다. 이 번호는 실행 할 때마다 달라질 수 있다.

카테고리 “BSD Socket”
more...
[BSD Socket] FreeBSD(+*nix) 버전 소켓함수 정리
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 클라이언트측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 Socket 함수들을 정리한다. 1. socket #include #include int socket(int domain, int type, int protocol); socket 객체(enpoint)를 생성하는 함수이다. 생성에 성공하면 file descriptor를 반환한다. 그러나 오류가 발생하면 -1를 반환하고, 자세한 사유는 errno를 통해 확인할 수 있다. parameter: domain sys/socket.h에 정의된 여러개의 상수들 중 하나를 선택할 수 있다. 이번 예제에서 쓰인 상수는 IPC를 구현할 때 사용된 AF_UNIX 및 TCP/UDP 통..
BSD Socket
2020. 6. 2. 21:51

[BSD Socket] FreeBSD(+*nix) 버전 소켓함수 정리

BSD Socket
2020. 6. 2. 21:51

BSD Socket 사용법 정리


FreeBSD(+*nix) 버전 클라이언트측 코드


본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 Socket 함수들을 정리한다.

 

1. socket


#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

socket 객체(enpoint)를 생성하는 함수이다. 생성에 성공하면 file descriptor를 반환한다. 그러나 오류가 발생하면 -1를 반환하고, 자세한 사유는 errno를 통해 확인할 수 있다.

 

parameter: domain


sys/socket.h에 정의된 여러개의 상수들 중 하나를 선택할 수 있다. 이번 예제에서 쓰인 상수는 IPC를 구현할 때 사용된 AF_UNIX 및 TCP/UDP 통신에 사용된 AF_INET이다. 참고로 AF_LOCAL도 있는데 이는 AF_UNIX와 같은 값이다. IPv6 기반의 인터넷 통신에 대해서는 AF_INET6라는 별도의 상수가 선언되어 있는데 이를 활용한 IPv6 Socket 통신은 추후에 확인한다.

 

parameter: type


이번 예제에서 쓰인 상수는 TCP 및 IPC 통신에 쓰이는 상수인 SOCK_STREAM, UDP 통신에 쓰이는 상수인 SOCK_DGRAM이다. 패킷의 내용을 직접 다룰 수 있는 상수인 SOCK_RAW도 있는데 이는 보안을 위해 운영체제에 따라서 지원을 안 할 수도 있다.

 

parameter: protocol


socket 함수가 반환하는 file descripter도 일종의 파일처럼 취급되는데, 이 파일에 적용될 옵션을 설정할 수 있다. 이번 예제에서는 0을 설정하였다. 즉 특수한 옵션은 부여하지 않았다.

 

정리하면 다음과 같이 정형화시킬 수 있다.

TCP (IPv4): int fd = socket(AF_INET, SOCK_STREAM, 0);

TCP (IPv6): int fd = socket(AF_INET6, SOCK_STREAM, 0);

UDP (IPv4): int fd = socket(AF_INET, SOCK_DGRAM, 0);

UDP (IPv6): int fd = socket(AF_INET6, SOCK_DGRAM, 0);

IPC: int fd = socket(AF_UNIX, SOCK_STREAM, 0);

 

2. bind와 connect


#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

TCP, IPC와 같이 송신과 수신을 확실히 확인하는 프로토콜에서는 서버 측에서 호출하는 bind와 클라이언트 측에서 호출하는 connect를 사용한다. 두 함수는 모두 접속할 주소가 적혀있는 inaddr_XX 구조체를 필요로 한다.

 

parameter: sockfd


앞의 socket 함수로 얻은 file descriptor를 전달한다.

 

parameter: addr


sockaddr_in는 인터넷으로 접속하려는 주소를 구성하고 전달하는 구조체이다. sockaddr_un은 IPC로 접속할 때 그 위치를 구성하고 전달하는 구조체이다. 그 외에도 다른 구조체들이 사용될 수 있으나, 이들 구조체의 기본 형태는 모두 공통적으로 아래의 sockaddr 구조체의 변형이다.

struct sockaddr {
	sa_family_t sa_family;
	char sa_data[14];
}

sa_family는 주소의 종류를 결정한다. AF_INET, AF_INET6, AF_UNIX, AF_LOCAL 등을 지정할 수 있다. 상수의 종류에 따라 sa_addr[14]에 들어갈 주소의 구조는 달라진다.

TCP, UDP 등의 인터넷(IPv4) 기반 통신은 다음과 같이 sin_portsin_addr로 세분화된다.

struct sockaddr_in {
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
};

struct in_addr {
	uint32_t s_addr;
};

sin_port는 포트 번호이고, sin_addr는 IPv4 주소이며, IPv4는 in_addr이라는 별도의 구조체 내의 32비트 정수로 전달된다. 두 변수의 endian은 모두 Big Endian이다. sin_addr.s_addr의 몇몇 특수한 IP 주소는 다음과 같은 상수로 정의되어 있다.

INADDR_LOOPBACK
127.0.0.1이다.
INADDR_ANY
0.0.0.0이며 아무 단말기로부터 연결을 수용할 때 사용된다.
INADDR_BROADCAST
255.255.255.255이며 모든 단말기로 브로드캐스트할 때 사용된다.

IPC 기반의 통신은 다음과 같이 sun_path로 분화된다.

struct sockaddr_un {
	sa_family_t sun_family;
	char sun_path[108];
};

여기서 sun_path의 길이는 POSIX 표준에서는 정의되지 않았다. 꼭 108문자가 아니어도 되고, 구현에 따라 다른 크기로 선언될 수도 있다. 문자열의 실제 길이가 얼마가 되었든, 항상 NULL 문자로 끝나야 하며 런타임 시 동적으로 문자열 길이를 측정한다.

 

parameter: addrlen


addr로 지정한 구조체의 길이를 전달한다.

 

예를 들어, TCP, UDP에 기반한 통신을 하고자 할 때 IP 주소와 포트 번호를 구조체에 다음과 같이 정형화할 수 있다. IPv4 주소를 Big Endian 정수로 변환할 때 inet_pton 함수를 사용하고, 포트 번호를 나타내는 시스템 정수를 Big Endian 정수로 변환할 때는 htons를 사용한다. 구조체의 내용을 전부 0x00으로 리셋하기 위하여 bzero 함수를 사용한다.

struct sockaddr_in addr_server;

bzero(&addr_server, sizeof(struct sockaddr_in)); // #include <strings.h>
addr_server.sin_family = AF_INET;
addr_server.sin_port = htons(포트번호); // #include <arpa/inet.h>
switch (inet_pton(AF_INET, "000.000.000.000", &addr_server.sin_addr)) { // #include <arpa/inet.h>
	case 1: 
		// 정상인 경우
		break;
	case 0:
		// IP 주소가 잘못된 경우
		break;
	case -1:
		// AF_ 상수가 잘못 지정된 경우
		break;
}

IPC에 기반한 통신을 하고자 할 때 연결 주소를 구조체에 다음과 같이 정형화할 수 있다.

struct sockaddr_un addr_server;

bzero(&addr_server, sizeof(struct sockaddr_un)); // #include <strings.h>
addr_server.sun_family = AF_UNIX;
strcpy(addr_server.sun_path, "주소");

 

3. listen과 accept


#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

listenaccept는 TCP와 IPC 서버 프로그램과 같이 상대방의 연결 여부를 확인하는 프로토콜로 소켓을 사용할 때 서버가 클라이언트를 기다렸다가 연결을 허용하는 함수이다. listen은 연결 대기열에 클라이언트가 추가되기를 기다리는 함수이고, accept는 연결 대기열에서 클라이언트를 하나 꺼내와서 통신을 시작하는 함수이다. 사용 예는 다음과 같이 정형화할 수 있다.

struct sockaddr addr_client;
socklen_t addr_client_len;

if (listen(fd, SOMAXCONN) < 0) {
	// 오류 처리
}

while (1) {
	if (accept(fd, &addr_client, &addr_client_len) < 0) {
		// 오류 처리
	}
	// 클라이언트와 통신할 내용
}

 

parameter: sockfd


socket 함수로 얻은 파일 디스크립터이다.

 

parameter: backlog


클라이언트 대기열의 최대 크기이다. 시스템에서 기본으로 제공되는 상수로는 SOMAXCONN이 있다.

 

parameter: addr, addrlen

클라이언트 측 주소를 담게 될 구조체 및 그 구조체의 길이이다.

 

3. read, write와 recv, send 및 recvfrom, sendto


#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

모두 클라이언트와 무언가를 주고 받는데 사용되는 함수이다. 여기서 read/write/recv/send는 TCP 및 UDP와 같이 송수신 여부를 확인하는 방식의 프로토콜로 통신할 때 사용하는 함수이고, recvfrom/sendto는 UDP와 같이 송수신여부를 확인하지 않고 일방적으로 전달하는 방식의 프로토콜로 통신할 때 사용하는 함수이다.

특히 read/write는 소켓함수라기 보다는 운영체제의 입출력 함수인데, socket에서 반환한 객체가 파일 디스크립터이기 때문에 이들 함수의 사용이 가능하다. 이 때는 내부적으로 recv, send가 호출되며 flags = 0으로 처리된다.

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 IPC 클라이언트측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 클라이언트측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 IPC Socket 클라이언트측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // strerror() #include // errno /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_clean..
BSD Socket
2020. 4. 8. 16:05

[BSD Socket] FreeBSD(+*nix) 버전 IPC 클라이언트측 코드

BSD Socket
2020. 4. 8. 16:05

BSD Socket 사용법 정리


FreeBSD(+*nix) 버전 클라이언트측 코드


본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 IPC 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 <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 Server!"

void cleanup_routine(void * pfdclient); // 소켓 함수 실행 중 오류가 발생할 때 수행할 공통의 해제 작업이 기술된 함수이다.

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

	int fdserver; // 서버에 연결하는 소켓에 대한 파일 디스크럽터.
	socklen_t cbaddrserver; // 서버측 인터넷 주소의 길이를 보관한다.
	struct sockaddr_in addrserver; // 서버측 인터넷 주소를 보관한다.
	int sockresult; // 소켓 함수의 실행 결과를 보관한다.

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

	thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, &fdserver);
	
	fdserver = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fdserver < 0) {
		printf("[FAILURE] socket(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] socket() @ pthread_t = %p\n", thself);

	cbaddrserver = sizeof(struct sockaddr_un);
    bzero(&addrserver, cbaddrserver);
    addrserver.sun_family = AF_UNIX;
	strncpy(addrserver.sun_path, SCK_FILE, (sizeof(addrserver.sun_path) / sizeof(addrserver.sun_path[1])));

	sockresult = connect(fdserver, (struct sockaddr *)&addrserver, cbaddrserver);
	if (sockresult < 0) {
		printf("[FAILURE] connect(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] connect() @ pthread_t = %p\n", thself);

	cbbuffserver = sizeof(buffserver);
	bzero(buffserver, cbbuffserver);

	rwresult = read(fdserver, buffserver, cbbuffserver);
	cbbuffserver = strlen(buffserver) * sizeof(char);
	if (rwresult < 0) {
		printf("[FAILURE] read(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] read(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf(">> %s\n", buffserver);

	cbbuffclient = sizeof(buffclient);
	bzero(buffclient, cbbuffclient);
	sprintf(buffclient, "%s @ pthread_t = %p", SCK_MESG, thself);

	cbbuffclient = strlen(buffclient) * sizeof(char);
	rwresult = write(fdserver, buffclient, cbbuffclient);
	if (rwresult < 0) {
		printf("[FAILURE] write(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] write(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf("<< %s\n", buffclient);
	
	pthread_exit((void *)((intptr_t)0));
	pthread_cleanup_pop(0);
	
	return 0;
}

void cleanup_routine(void * pfdsocket) {
	pthread_t thself;

	int fdsocket;
	int sockresult;

	fdsocket = *((int *)pfdsocket);
	thself = pthread_self();

	if (fdsocket) {
		sockresult = close(fdsocket);
		if (sockresult < 0) {
			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 함수를 호출하는 부분에서 '정의되지 않은 식별자' 오류를 낼 것이다.

서버 접속에 성공하면 다음과 같이 서버로부터 메시지를 수신한다. 여기서 0x800681500은 이 클라이언트와 통신하고 있는 서버 측 thread ID이다.

서버와 문자열 교환에 성공하였을 때 서버측에서 보낸 메시지가 출력된다.

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 IPC 서버측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 IPC 서버측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 IPC Socket 서버측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // errno #include // strerror() /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_cleanup..
BSD Socket
2020. 4. 8. 16:05

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

BSD Socket
2020. 4. 8. 16:05

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 파일이름

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

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 UDP 클라이언트측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 클라이언트측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 UDP Socket 클라이언트측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // strerror() #include // errno /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_clean..
BSD Socket
2020. 3. 15. 08:06

[BSD Socket] FreeBSD(+*nix) 버전 UDP 클라이언트측 코드

BSD Socket
2020. 3. 15. 08:06

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_ADDR "192.168.204.143"
#define SCK_PORT 9998
#define SCK_BUFF 512
#define SCK_MESG "Hello, UDP Server!"

void cleanup_routine(void * pfdclient); // 소켓 함수 실행 중 오류가 발생할 때 수행할 공통의 해제 작업이 기술된 함수이다.

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

	int fdserver; // 서버에 연결하는 소켓에 대한 파일 디스크럽터.
	socklen_t cbaddrserver; // 서버측 인터넷 주소의 길이를 보관한다.
	struct sockaddr_in addrserver; // 서버측 인터넷 주소를 보관한다.
	int sockresult; // 소켓 함수의 실행 결과를 보관한다.

	size_t cbbuffserver, cbbuffclient; // 서버에서 클라이언트로, 클라이언트에서 서버로 송수신하는 문자열의 길이를 보관한다.
	char buffserver[SCK_BUFF], buffclient[SCK_BUFF]; // 서버에서 클라이언트로, 클라이언트에서 서버로 송수신하는 문자열을 보관한다.
	ssize_t rwresult; // 서버와 클라이언트간 송수신된 문자열의 길이를 보관한다.
	
	thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, &fdserver); // 소켓 함수의 호출이 실패하면 cleanup_routine(fdserver)를 실행한다.

	/* 서버에 접속하기 위한 소켓을 생성한다. */
	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 = inet_addr(SCK_ADDR); // 구조체 내용 구성 - 2. 서버 자신은 모든 주소로부터 연결에 응할 수 있다.
    addrserver.sin_port = htons(SCK_PORT); // 구조체 내용 구성 - 3. 서버는 SCK_PORT 매크로상수에 지정된 포트 번호로 통신한다.
	
	cbclient = SCK_BUFF * sizeof(char);
	bzero(buffclient, cbclient);
	sprintf(buffclient, "%s @ pthread_t = %p", SCK_MESG, thself);

	/* 서버에 문자열을 송신한다. */
	cbclient = strlen(buffclient) * sizeof(char);
	rwresult = sendto(fdserver, buffclient, cbclient, 0, (const struct sockaddr *)&addrserver, cbaddrserver);
	if (rwresult < 0) {
    	// 송신에 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] sendto(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] sendto(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf("<< %s\n", buffclient);

	cbserver = SCK_BUFF * sizeof(char);
	bzero(buffserver, cbserver);

	/* 서버로부터 문자열을 수신한다. */
	rwresult = recvfrom(fdserver, buffserver, cbserver, 0, (struct sockaddr *)&addrserver, &cbaddrserver);
	cbserver = strlen(buffserver) * sizeof(char);
	if (rwresult < 0) {
    	// 수신에 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] recvfrom(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] recvfrom(): %zd byte(s) @ pthread_t = %p\n", rwresult, thself);
	printf("<< %s\n", buffserver);

	pthread_exit((void *)((intptr_t)errno)); // 현재 errno를 리턴하며 본 스레드 종료

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

/*
 * 소켓 함수에 오류가 발생했을 경우 수행할 공통의 정리 작업이다.
 * pfdsocket : 오류가 발생하여 닫아야 할 소켓이다.
 */
void cleanup_routine(void * pfdsocket) {
	pthread_t thself;

	int fdsocket;
	int sockresult;

	assert(pfdserver != NULL);
	fdsocket = *((int *)pfdsocket); // 지정된 주소에서 서버 또는 클라이언트 소켓 파일 디스크럽터를 복사해 온다.
	thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.

	if (fdsocket >= 0) {
    	/* 소켓을 닫는다. */
		sockresult = close(fdsocket);
		if (sockresult < 0) {
			printf("[FAILURE] close(): %d(%s)\n", errno, strerror(errno)); // 소켓 닫기에 오류가 발생하면, errno를 출력한다.
		} else {
			printf("[SUCCESS] close()\n");
        }
	}

	return;
}

 

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

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

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

서버 접속에 성공하면 다음과 같이 서버로부터 메시지를 수신한다. 여기서 0x800681500은 이 클라이언트와 통신하고 있는 서버 측 thread ID이다.

서버와 문자열 교환에 성공하였을 때 서버측에서 보낸 메시지가 출력된다.

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 UDP 서버측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 서버측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 UDP Socket 서버측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // strerror() #include // errno /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_cleanup_pop..
BSD Socket
2020. 3. 14. 21:21

[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”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 TCP 클라이언트측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 클라이언트측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 TCP Socket 클라이언트측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // strerror() #include // errno /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_clean..
BSD Socket
2020. 1. 2. 16:41

[BSD Socket] FreeBSD(+*nix) 버전 TCP 클라이언트측 코드

BSD Socket
2020. 1. 2. 16:41

BSD Socket 사용법 정리


FreeBSD(+*nix) 버전 클라이언트측 코드


본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 TCP 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_ADDR "127.0.0.1"
#define SCK_PORT 9999
#define SCK_BUFF 512
#define SCK_MESG "Hello, TCP Server!"

void cleanup_routine(void * pfdclient); // 소켓 함수 실행 중 오류가 발생할 때 수행할 공통의 해제 작업이 기술된 함수이다.

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

	int fdserver; // 서버에 연결하는 소켓에 대한 파일 디스크럽터.
	socklen_t cbaddrserver; // 서버측 인터넷 주소의 길이를 보관한다.
	struct sockaddr_in addrserver; // 서버측 인터넷 주소를 보관한다.
	int sockresult; // 소켓 함수의 실행 결과를 보관한다.

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

	thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, &fdserver); // 소켓 함수의 호출이 실패하면 cleanup_routine(fdserver)를 실행한다.

	/* 서버에 접속하기 위한 소켓을 생성한다. */
	fdserver = socket(AF_INET, 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_in); // 서버 측 인터넷 주소의 길이를 구한다.
	bzero(&addrserver, cbaddrserver); // sockaddr_in 구조체의 내용을 0으로 리셋한다.
	addrserver.sin_family = AF_INET; // 구조체 내용 구성 - 1. 주소가 인터넷 주소이다.
	addrserver.sin_addr.s_addr = inet_addr(SCK_ADDR); // 구조체 내용 구성 - 2. 서버 자신은 모든 주소로부터 연결에 응할 수 있다.
	addrserver.sin_port = htons(SCK_PORT); // 구조체 내용 구성 - 3. 서버는 SCK_PORT 매크로상수에 지정된 포트 번호로 통신한다.

	/* 서버에 접속한다. */
	sockresult = connect(fdserver, (struct sockaddr *)&addrserver, cbaddrserver);
	if (sockresult < 0) {
    	// 서버 접속에서 오류가 발생하면, errno를 출력한다.
		printf("[FAILURE] connect(): %d(%s) @ pthread_t = %p\n", errno, strerror(errno), thself);
        // cleanup_routine(errno)를 실행한 뒤 본 스레드를 종료한다.
		pthread_exit((void *)((intptr_t)errno));
	}
	printf("[SUCCESS] connect() @ pthread_t = %p\n", thself);

	cbbuffserver = sizeof(buffserver);
	bzero(buffserver, cbbuffserver);

	/* 서버로부터 문자열을 수신한다. */
	rwresult = read(fdserver, buffserver, cbbuffserver); 
	cbbuffserver = strlen(buffserver) * 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", buffserver);

	cbbuffclient = sizeof(buffclient);
	bzero(buffclient, cbbuffclient);
	sprintf(buffclient, "%s @ pthread_t = %p", SCK_MESG, thself);

	/* 서버에 문자열을 송신한다. */
	cbbuffclient = strlen(buffclient) * sizeof(char);
	rwresult = write(fdserver, buffclient, cbbuffclient);
	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", buffclient);
	
	pthread_exit((void *)((intptr_t)errno)); // 현재 errno를 리턴하며 본 스레드 종료

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

/*
 * 소켓 함수에 오류가 발생했을 경우 수행할 공통의 정리 작업이다.
 * pfdsocket : 오류가 발생하여 닫아야 할 소켓이다.
 */
void cleanup_routine(void * pfdsocket) {
	pthread_t thself;

	int fdsocket;
	int sockresult;

	assert(pfdserver != NULL);
	fdsocket = *((int *)pfdsocket); // 지정된 주소에서 서버 또는 클라이언트 소켓 파일 디스크럽터를 복사해 온다.
	thself = pthread_self(); // 현재 실행중인 스레드 번호를 가져온다.

	if (fdsocket >= 0) {
		/* 소켓을 닫는다. */
		sockresult = close(fdsocket);
		if (sockresult < 0) {
			printf("[FAILURE] close(): %d(%s)\n", errno, strerror(errno)); // 소켓 닫기에 오류가 발생하면, errno를 출력한다.
		} else {
			printf("[SUCCESS] close()\n");
		}
	}

	return;
}

 

실행 결과


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

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

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

서버 접속에 성공하면 다음과 같이 서버로부터 메시지를 수신한다. 여기서 0x800681500은 이 클라이언트와 통신하고 있는 서버 측 thread ID이다.

서버와 문자열 교환에 성공하였을 때 서버측에서 보낸 메시지가 출력된다.

카테고리 “BSD Socket”
more...
썸네일 이미지
[BSD Socket] FreeBSD(+*nix) 버전 TCP 서버측 코드
BSD Socket 사용법 정리 FreeBSD(+*nix) 버전 TCP 서버측 코드 본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 TCP Socket 서버측 코드의 사용 예를 정리한다. 전체 코드 먼저 전체적인 코드를 훑어본다. /* 표준 헤더 include */ #include // intptr_t #include // exit() #include // printf(), sprintf() #include // assert() #include // strerror() #include // errno /* 유닉스 헤더 include */ #include // pthread_create(), pthread_exit(), pthread_cleanup_push(), pthread_cleanup..
BSD Socket
2020. 1. 1. 11:16

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

BSD Socket
2020. 1. 1. 11:16

BSD Socket 사용법 정리


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


본 포스팅에서는 FreeBSD를 포함하여 *nix를 기준으로 한 TCP 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 9999
#define SCK_BUFF 512
#define SCK_MESG "Hello, TCP 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_in addrserver, addrclient; // 서버 및 클라이언트에 대한 인터넷 주소를 보관한다.
	int sockresult; // 소켓 함수의 실행 결과를 보관한다.

	thself = pthread_self(); // 현재 실행중인 자기 자신의 스레드 번호를 가져온다.
	pthread_cleanup_push(cleanup_routine, &fdclient); // 소켓 함수 실행 중 오류가 발생하였을 때 cleanup_routine을 실행한다.
	
    /* 서버 측 소켓을 생성한다. */
	fdserver = socket(AF_INET, 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_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);

	/* 연결 요청을 수용하기 위한 대기열을 구성한다. */
	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_in); // 클라이언트 측 인터넷 주소의 길이를 구한다.
		bzero(&addrclient, cbaddrclient); // sockaddr_in 구조체의 내용을 0으로 리셋한다.

		/* 클라이언트 측의 연결 요청이 있을 때까지 무한정 기다린다. */
		fdclient = accept(fdserver, (struct sockaddr *)&addrclient, &camp;baddrclient);
		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); // 새 스레드에서 startroutine(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 cbserver, cbclient; // 서버에서 클라이언트로 보내는 문자열의 길이와 클라이언트에서 서버로 수신된 문자열의 길이를 보관한다.
	char buffserver[SCK_BUFF], buffclient[SCK_BUFF]; // 서버에서 클라이언트로 보내는 문자열과 클라이언트에서 서버로 수신된 문자열을 보관한다.
	ssize_t rwresult; // 송수신한 결과(바이트 수)를 보관한다.

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

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

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

	/* 서버에서 클라이언트로 문자열을 보낸다. */
	cbserver = strlen(buffserver) * sizeof(char);
	rwresult = write(fdclient, buffserver, cbserver);
	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);

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

	/* 클라이언트로부터 문자열을 받는다. */
	rwresult = read(fdclient, buffclient, cbclient);
	cbclient = 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)errno)); // 현재 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);
		} else {
			printf("[SUCCESS] close() @ pthread_t = %p\n", thself);
        }
	}

	return;
}

 

실행 결과 보기


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

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

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

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

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

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

카테고리 “BSD Socket”
more...

“BSD Socket” (9건)