반응형

- 윈도우에서는 Linux에서 보인 형태의 시그널 핸들링이 존재하지 않는다.

- MSG_OOB 옵션에 대한 이벤트 핸들링이 윈도우 기반에서는 불가능

 

→ select 함수를 통해 가능

select 함수의 관찰항목: [수신한 데이터, 데이터 전송이 가능한 socket, 예외상황의 발생]

- out-of-band 데이터의 수신도 예외상황에 해당

 

// msg_oob 를 이용해 readv 구현
#define BUF_SZ	40
int main(int argc, char* argv[])
{
	WSADATA wsaData;
	int serv_sock, clnt_sock;
	SOCKADDR_IN serv_addr, clnt_addr;

	int adrsz, len;
	char buf[BUF_SZ];
	int result;

	fd_set reads, except, retemp, extemp;
	struct timeval timeout;

	if (argc != 2)
	{
		printf("usage: %s <addr>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		err_handling("startup() error");

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (serv_sock == INVALID_SOCKET)
		err_handling("socket() error");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));

	if (bind(serv_sock, &serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
		err_handling("bind() error");

	if (listen(serv_sock, 5) == SOCKET_ERROR)
		err_handling("listen() error");

	adrsz = sizeof(clnt_addr);
	clnt_sock = accept(serv_sock, &clnt_addr, &adrsz);
	FD_ZERO(&reads);
	FD_ZERO(&except);
	FD_SET(clnt_sock, &reads);
	FD_SET(clnt_sock, &except);

	while (1)
	{
		retemp = reads;
		extemp = except;

		timeout.tv_sec = 5;
		timeout.tv_usec = 0;

		result = select(0, &retemp, 0, &extemp, &timeout);

		if (result > 0)
		{
			if (FD_ISSET(clnt_sock, &extemp))
			{
				len = recv(clnt_sock, buf, BUF_SZ - 1, MSG_OOB);
				buf[len] = 0;
				printf("urgent msg: %s\n", buf);
			}
			if (FD_ISSET(clnt_sock, &retemp))
			{
				len = recv(clnt_sock, buf, BUF_SZ - 1, 0);
				if (len == 0)
				{
					break;
					closesocket(clnt_sock);
				}
				else
				{
					buf[len] = 0;
					printf("msg: %s\n", buf);
				}
			}
		}
	}
	closesocket(serv_sock);
	WSACleanup();

}
// msg_oob를 이용해 writev를 구현
#define BUF_SZ		40
int main(int argc, char* argv[])
{
	SOCKET sock;

	SOCKADDR_IN serv_adr;
	WSADATA wsaData;
	char msg[BUF_SZ] = { 0 };

	if (argc != 3)
	{
		printf("usage: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		err_handling("startup() error");

	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
		err_handling("socket() eror");

	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));

	if (connect(sock, &serv_adr, sizeof(serv_adr)) == SOCKET_ERROR)
		err_handling("connect() eror");

	send(sock, "123", 3, 0);
	send(sock, "4", 1, MSG_OOB);
	send(sock, "567", 3, 0);
	send(sock, "890", 3, MSG_OOB);

	closesocket(sock);
	WSACleanup();
	return 0;
}

// 실행창

 

 

* 다만 Linux의 wirtev & readv함수는 윈도우의 중첩 입출력에 의해 구현 가능

반응형
반응형
  • readv & wirtev 함수의 사용

- 데이터를 모아서 전송하고 모아서 수신하는 기능의 함수

- 입출력 함수호출의 수를 줄일 수 있음

- writev 함수를 이용해 여러 버퍼에 나뉘어 저장된 데이터를 한번에 전송하고 readv 함수를 이용해 데이터를 여러 버퍼에 나눠 수신

 

  • wirtev
#include <sys/uio.h>
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

- fd: 데이터 전송의 목적지를 나타내는 socket의 file descriptor 전달 (socket에만 제한된 함수가 아니므로 파일이나 콘솔 대상의 file descriptor도 전달 가능)

- iov: 구조체 iovec배열의 주소값, 구조체 iovec의 변수는 전송할 데이터의 위치 및 크기 정보 번달

- iovcnt: 두번째 인자로 전달된 주소값이 가리키는 배열의 길이정보

 

struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};

- iovec 구조체는 전송할 데이터가 저장된 버퍼의 주소값과 전성할 데이터의 크기정보를 담음

 

writev & iovec

- file descriptor 1이므로 콘솔에 출력이 이뤄짐 

- 세번째 인자가 2이기 때문에 ptr이 가리키는 주소를 시작으로 총 2 개의 iovec 구조체의 변수를 참조하여 변수가 가리키는 버퍼에 저장된 데이터 전송

 

// 실행창

 

  • readv
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

- fd: 데이터를 수신할 파일(혹은 socket)의 filde descriptor

- iov: 데이터를 저장할 위치와 크기 정보를 담고 있는 iovec 구조체 배열의 주소값

- iovcnt: 두 번째 인자로 전달된 주소값이 가리키는 배열의 길이정보 

 

// 실행창

(readv 함수: fgets 함수 2개를 연이어 호출하는 것과 비슷한 형식으로 동작)

- readv 함수의 두 번째 매개변수에서 데이터의 크기를 5로 지정했기 때문에 buf1의 크기에 상관없이 최대 5바이트만이 저장

- vec[0]의 버퍼에 5바이트가 저장되고 나머지 데이터는 vec[1]에 등록된 버퍼에 저장되므로 iov_len에는 버퍼에 저장할 최대 바이트 크기 정보를 저장해야 함

- readv 함수의 첫 번째 인자가 0이므로 콘솔로부터 데이터 수신

 

  • readv & writev 함수의 적절한 사용

- 전송해야 할 데이터가 여러 개의 버퍼(배열)에 나뉜 경우, 한 번의 writev함수 호출로 가능 -> 효율적

- 입력 버퍼에 수신된 데이터를 여러 저장소에 나눠 읽어 들이는 경우 한번의 readv 함수 호출로 가능 -> 효율적

- 함수의 호출 횟수가 적으면 성능 향상

- 전송되는 packet의 수를 줄일 수 있음

 

- 서버에서 성능 향상을 위해 Nagle 알고리즘을 명시적으로 중지시킨 경우

: 전송할 데이터가 세 곳의 영역에 나뉘어 저장된 상황에서 write 함수를 사용할 경우 세 번의 함수 호출로 세 개의 패켓이 생성되어 전송될 확률이 높다.

: 하지만 writev 함수를 호출할 경우 한 번에 모든 데이터를 출력 버퍼로 넣기 때문에 하나의 패킷만 생성되어 전송될 확률이 높음

 

※ 여러 영역에 나뉘어 있는 데이터를 전송 순서에 맞춰 하나의 큰 배열에 옮기고 한번의 write 함수 호출로 전송을 하면 writev 함수를 호출한 것과 같은 결과를 얻을 수 있음 (

반응형
반응형
  • Linux에서의 send & recv
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

- sockfd: 데이터 전송 대승과의 연결을 의미하는 socket의 file descriptor

- buf: 전송할 데이터를 저장하고 있는 버퍼의 주소값

- len: 전송할 바이트 수 

- flags: 데이터 전송 시 옵션 정보

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

- sockfd: 데이터 수신 대상과의 연결을 의미하는 socket file descriptor

- buf: 수신된 데이터를 저장할 버퍼의 주소값

- len: 수신할 수 있는 최대 바이트 수

- flags: 데이터 수신 시 적용할 옵션 정보

 

  • 옵션 정보 (| 를 이용하여 둘 이상을 함께 전달 가능)
옵션 의미 send recv
MSG_OOB 긴급 데이터(out-of-band data)의 전송을 위한 옵션 * *
MSG_PEEK 입력버퍼에 수신된 데이터의 존재유무 확인을 위한 옵션   *
MSG_DONTROUTE 데이터 전송과정에서 라우팅 테이블을 참조하지 않을 것을 요구하는 옵션, 따라서 로컬 네트워크 상에서 목적지를 찾을 때 사용되는 옵션 *  
MSG_DONTWAIT 입출력 함수 호출과정에서 블로킹 되지 않을 것을 요구하기 위한 옵션, non-blocking IO의 요구에 사용되는 옵션 * *
MSG_WAITALL 요청한 바이트 수에 해당하는 데이터가 전부 수신될 때까지 호출된 함수가 반환되는 것을 막기 위한 옵션   *

 


  • MSG_OOB: 긴급 메시지의 전송

- out-of-band data라는 긴급 메세지의 전송을 위해 메시지의 전송방법 및 경로를 달리 함

- MSG_OOB의 긴급 메시지를 수신하면 운영체제는 SIGURG 시그널을 발생시켜 프로세스가 등록한 시그널 핸들로가 호출

- fcntl(recv_sock, F_SETOWN, getpid());  // file descriptor의 컨트롤에 사용

: file descriptor recv_sock이 가리키는 socket에 의해 발생하는 SIGURG 시그널을 처리하는 프로세스를 getpid 함수가 반환하는 ID의 프로세스로 변경시키겠다

- getpid: 이 함수를 호출한 프로세스의 ID를 반환하는 함수

 

// oob_send.c

// oob_recv.c

// 실행창

 

- TCP는 전혀 다른 통신 경로로 전송되는 데이터를 뜻하는 out-of-band data를 위한 별도의 통신경로를 제공하지 않고 TCP의 Urgent mode를 이용해 데이터를 전송

- MSG_OOB 옵션을 추가해도 더 빨리 데이터가 전송되지 않음

- 시그널 핸들러를 이용해 읽히는 데이터는 1바이트이며 나머지는 일반적인 입력함수의 호출을 통해 읽힘

 

  • Urgent mode

- 데이터의 전송에서 전송순서가 유지되면서 데이터를 수신하는 대상에게는 데이터의 처리를 재촉

- 빠른 전송 속도를 보장하진 않지만 긴급 메시지가 전송되었음이 시그널 핸들러를 통해 인지됨

 

  • MSG_OOB 옵션의 데이터 전송 과정

send(sock, "890", strlen("890"), MSG_OOB);

긴급 메시지 전송단계의 출력버퍼

- offset 3의 위치가 urgent pointer로 지정되어 긴급 메시지의 마지막 문자열의 offset + 1의 위치를 가리키면서 상대 호스트에게 urgent pointer가 가리키는 offwet의 바로 앞에 존재하는 것이 긴급 메시지임을 알림

 

- 데이터 전송을 위해 구성되는 TCP 패킷의 구조

URG 설정 패킷

- TCP header에 URG=1 : 긴급 메시지 존재

- TCP header에 urgent pointer=5 : urgent pointer의 위치가 offset 5의 위치

- 데이터를 수신하는 상대 호스트에서는 urgent pointer의 앞 1바이트를 제외하고는 나머지는 일반적인 입려함수의 호출로 읽음

 

offset: 기본이 되는 위치를 바탕으로 상대적 위치를 표현하는 것
ex) 실제 3번지를 기준으로 offset 부여 -> 실제 4번지: offset 1
때문에 일반적인 주소와 달리 항상 0에서부터 시작

 

  • MSG_PEEK: 입력버퍼 검사하기

- MSG_PEEK

: MSG_DONTWAIT 옵션과 함께 설정되어 입력버퍼에 수신된 데이터가 존재하는지 확인하는 용도

: MSG_PEEK 옵션을 설정하고 recv 함수를 호출하면 입력버퍼에 존재하는 데이터가 읽혀지더라도 데이터가 지워지지 않음, MSG_DONTWAIT 옵션과 함께 사용해 입력버퍼에 데이터가 존재하지 않더라도 블로킹(반환안되는 상태) 되지 않도록 함

 

// peek_recv.c

- 입력버퍼의 데이터를 지우기 위해 옵션을 설정하지 않은 recv 함수를 한번 더 호출

// peek_send.c

// 실행창

- 한번 전송한 데이터가 두 번 읽혀짐

반응형
반응형
#include <winsock2.h>
int WSAAPI select(
  [in]      int           nfds,
  [in, out] fd_set        *readfds,
  [in, out] fd_set        *writefds,
  [in, out] fd_set        *exceptfds,
  [in]      const timeval *timeout
);
→ 성공 시 0 이상, 실패 시 -1 반환
typedef struct timeval {
  long tv_sec;
  long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;

- window의 fd_set은 Linux와 같이 비트의 배열로 구성되어 있지 않음

typedef struct fd_set {
  u_int  fd_count;
  SOCKET fd_array[FD_SETSIZE];
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;

- fd_count: 저장된 socket의 handle의 수를 기록

- fd_array: socket의 handle 저장

 

- Linux의 file descriptor는 0에서부터 시작하여 하나씩 값이 증가하는 구조를 지나므로 현재 생성된 file descriptor의 수와 마지막으로 생성된 file descriptor의 수 사이에 관계성을 찾을 수 있음

- window 기반의 socket handle은 0에서부터 시작하지 않고 생성되는 handle의 정수 값 사이의 규칙을 찾을 수 없음 (따라서 socket handle을 저장하는 배열, 저장된 배열의 수를 기록하기 위한 변수 필요)

 

  • 윈도우 기반 멀티플렉싱 서버의 구현
// multiplexing server
#define		BUF_SZ		30
int main(int argc, char* argv[])
{
	WSADATA wsaData;
	int serv_sock, clnt_sock;
	SOCKADDR_IN serv_addr, clnt_addr;
	fd_set reads, temps;
	TIMEVAL timeout;
	int num, result, sz, len;
	char buf[BUF_SZ];

	if (argc != 2)
	{
		printf("usage: %s <addr>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		err_handling("startup() error");

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (serv_sock == INVALID_SOCKET)
		err_handling("socket() error");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));

	if(bind(serv_sock, &serv_addr, sizeof(serv_addr))== SOCKET_ERROR)
		err_handling("bind() error");

	if (listen(serv_sock, 5) == SOCKET_ERROR)
		err_handling("listen() error");

	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	num = serv_sock + 1;
	while (1)
	{
		temps = reads;
		timeout.tv_sec = 5;
		timeout.tv_usec = 5000;

		result = select(num, &temps, 0, 0, &timeout);

		if (result == SOCKET_ERROR)
			break;

		else if (result == 0)
			continue;

		for (int i = 0; i < reads.fd_count; i++)
		{
			if (FD_ISSET(reads.fd_array[i], &temps))
			{
				if (reads.fd_array[i] == serv_sock)
				{
					sz = sizeof(clnt_addr);
					clnt_sock = accept(serv_sock, &clnt_addr, &sz);
					FD_SET(clnt_sock, &reads);
					printf("connect: %d\n", clnt_sock);
				}
				else
				{
					len = recv(reads.fd_array[i], buf, BUF_SZ, 0);
					if (len == 0)
					{
						FD_CLR(reads.fd_array[i], &reads);
						closesocket(temps.fd_array[i]);
						printf("disconnect: %d\n", temps.fd_array[i]);
					}
					else
						send(reads.fd_array[i], buf, len, 0);
				}
			}
		}
	}
	closesocket(serv_sock);
	WSACleanup();
	return 0;
}​
반응형
반응형
  • select 함수의 기능과 호출순서

- 한 곳에 여러 개의 file descriptor를 모아놓고 동시에 이벤트를 관찰 가능

  1. 수신한 데이터의 존재 여부
  2. blocking없는 데이터 전송의 가능 여부
  3. 예외상황의 발생 여부

- 호출 순서

  1. file descriptor의 설정, 검사의 범위 지정, 타임아웃의 설정
  2. select 함수의 호출
  3. 호출 결과 확인

  • file descriptor의 설정

- 관찰할 여러 개의 file descriptor를 관찰항목(수신, 전송, 예외)에 따라서 구분하여 모음

- fd_set형 변수를 사용하여 file descriptor를 세 묶음으로 모음

자료형 fd_set

- 비트 단위로 이뤄진 배열(file descriptor 0, 1, ...)에서 1일 경우 file descriptor가 관찰의 대상임

- fd_set형 변수에 값을 등록하거나 변경하는 등의 작업은 매크로 함수들의 도움으로 이뤄짐

void FD_CLR(int fd, fd_set *fdset);      // fdset 주소의 변수에서 fd로 전달된 file descriptor 정보를 삭제
void FD_SET(int fd, fd_set *fdset);      // fdset 주소의 변수에 fd로 전달된 file descriptor 정보를 등록
void FD_ZERO(fd_set *fdset);            // 인자로 전달된 주소의 fd_set형 변수의 모든 비트를 0으로 초기화
void FD_ISSET(int fd, fd_set *fdset);    // fdset 주소의 변수에 fd로 전달된 file descriptor 정보가 있으면 양수 반환 (함수의 호출 결과를 확인)

 

  • 검사(관찰)의 범위지정과 타임아웃의 설정
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
 
int select(int mafdl, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
→ 성공 시 0 이상, 실패 시 -1 반환

- mafdl: 검사 대상이 되는 file descriptor 수

- readfds: 수신된 데이터의 존재여부에 관심있는 file descriptor 정보를 모두 등록해 변수의 주소값 전달

- writefds: blocking 없는 데이터 전송이 가능한지에 관심있는 file descriptor를 등록해 변수의 주소값 전달

- exceptfds: 예외 상황의 발생여부에 관심있는 file descriptor 정보를 등록해 변수의 주소값 전달

- timeout: select함수 호출 이후 무한정 blocking 상태에 빠지지 않도록 time-out 설정

- return: 오류 발생: -1; 타임 아웃에 의한 반환: 0; 관심대상으로 등록된 file descriptor에 관심에 관련된 변화 발생: 변화가 발생한 file descriptor의 수

 

- 검사의 범위지정: file descriptor은 생성될때마다 1씩 증가하므로 가장 큰 file descriptor 값에 1을 더해 관찰의 대상이 되는 file descriptor의 수 저장

- 타임아웃의 설정: select 함수의 경우 변화가 발생해야 반환값을 반환하므로 무한정 blocking 상태에 머물게 하지 않도록 타임아웃 지정 (NULL: 타임아웃 지정안함)

#include <sys/time.h>
 
struct timeval {
long tv_sec;  // 초
long tv_usec; // 마이크로 초
};

 

  • select 함수호출 이후의 결과 확인

- 관심 대상으로 지정된 file descriptor 중 변화가 발생한 file descriptor만이 그대로 1을 유지

 

  • select 함수를 호출하는 예제

- select 함수 호출 후에는 구조체 timeval의 멤버 tv_sec, tv_usec의 값이 타임아웃이 발생하기까지 남아있던 시간으로 변하므로 select 함수 호출 전 timeval 구조체 변수의 초기화를 반복

- select 함수가 0보다 큰 값을 반환하여 변화를 보인 file descriptor가 stdin이 맞는지 확인하고 맞으면 표준입력으로부터 데이터를 읽어 출력

// 실행창

 

  • 멀티플렉싱 서버의 구현

// 실행창

- 연결 요청에 필요한 서버 socket, 데이터 송수신을 위한 socket의 file descriptor를 reads 비트 배열에서 관심 대상으로 설정

- 클라이언트의 연결요청도 데이터의 전송을 통해 이뤄짐

- select 함수에서 1이상 반환했을때만 FD_ISSET함수 호출

- temps 비트 배열(file descriptor)을 통해 서버 socket와의 연결요청한 경우, 송수신을 위한 socket을 생성하고 select함수로 수신 데이터 여부를 확인하기 위해 reads 비트 배열에 file descriptor등록

- temps 비트 배열(file descriptor)을 통해 또는 수신한 데이터 확인하여 file descriptor로 수신한 데이터를 읽고 송신 (수신 데이터가 없을 경우 file descriptor 등록 해제)

 

- 상태변화 확인 -> 서버 socket에서 변화 여부 확인 -> 연결 요청 수락 -> fd_set 형 변수에 클라이언트와 연결된 socket의 file descriptor 정보 등록 

반응형
반응형
  • 멀티프로세스 서버의 단점

- 프로세스를 생성하는 경우 많은 양의 연산이 요구되며 필요한 메모리 공간도 큼

- 프로세스마다 별도의 메모리 공간을 유지하므로 상호간의 데이터를 송수신하기 우해서는 IPC같은 복잡한 통신방법을 이용해야 함

  • 멀티플렉싱

: 하나의 통신채널을 통해서 둘 이상의 데이터를 전송하는데 사용되는 기술

: 물리적 장치의 효율성을 높이기 위해 최소한의 물리적 요소만 사용해 최대한의 데이터를 전달하기 위해 사용되는 기술

- 시분할 멀티플렉싱 기술

- 주파수 분할 멀티플렉싱 기술

 

  • 멀티플렉싱의 개념을 서버에 적용하기

- 서버에 멀티플렉싱 기술을 도입해 필요한 프로세스의 수를 줄일 수 있음

- 접속해있는 클라이언트의 수에 상관없이 서비스를 제공하는 프로세스의 수는 하나임

멀티프로세스 서버 모델


멀티플렉싱 서버 모델

 

반응형

+ Recent posts