Overlapped IO 란? -------------------------------------------------------------------------------------------------------

앞서 비동기화된 알람방식에 대해서 알아봤다면 이번에는 IO작업 자체의 비동기 방식에 대해서 알아볼 차례이다.

비동기 방식의 소켓전송이란 여러개의 소켓이 동시에 IO작업을 처리하는 것을 의미한다.

여러개의 쓰레드나 비동기 시스템을 통해서 여러개의 파일을 동시에 읽어들이는 것과 동일하다고 보면 된다.

시스템 프로그래밍과 어느정도 겹치는 부분이 있지만. 비동기 방식은 다음의 함수들이 필요하다.


1. 비동기 옵션의 소켓

2. 비동기로 송수신이 가능한 함수


1.  비동기 옵션의 소켓을 생성하는 함수.

SOCKET WSASocket(

  _In_  int af, -> 프로토콜 체계 정보 전달.

  _In_  int type, -> 소켓의 데이터 전송방식에 대한 정보 전달.

  _In_  int protocol, -> 두 소켓 사이에 사용되는 프로토콜 정보 전달.

  _In_  LPWSAPROTOCOL_INFO lpProtocolInfo, -> 생성되는 소켓의 특성 정보를 담고 있는 WSAPROTOCOL_INFO 구조체 변수의 주소 값 전달,

     필요 업는 경우 NULL 전달.

  _In_  GROUP g, -> 함수의 확장을 위해서 예약되어 있는 매개변수, 따라서 0 전달.

  _In_  DWORD dwFlags -> 소켓의 속성정보 전달.

); 성공시 소켓의 핸들, 실패시 INVALID_SOCKET 반환.


2. 비동기로 송수신이 가능한 함수


2-1. 비동기 송신함수
int WSASend( 
  _In_   SOCKET s, -> 소켓의 핸들 Overlapped IO 속성의 핸들 전달시 Overlapped IO 방식으로 송신한다.
  _In_   LPWSABUF lpBuffers, -> 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로 이루어진 배열의 주소 값 전달.
  _In_   DWORD dwBufferCount, -> 두 번째 인자로 전달된 배열의 길이정보 전달.
  _Out_  LPDWORD lpNumberOfBytesSent, -> 전송된 바이트 수가 저장될 변수의 주소 값 전달 (이는 잠시 후 별도로 설명)
  _In_   DWORD dwFlags, -> 함수의 데이터 전송특성을 변경하는 경우에 사용, 예로 MSG_OOB를 전달하면 OOB 모드 데이터 전송.
  _In_   LPWSAOVERLAPPED lpOverlapped, 
-> WSAOVERLAPPED 구조체 변수의 주소 값 전달, Event 오브젝트를 사용해서 데이터 전송의 완료를 확인하는 경우에 사용되는 매개변수.
  _In_   LPWSAOVERLAPPED_COMPLETION_ROUTINE 
-> lpCompletionRoutine -> Completion Routine라는 함수의 주소 값 전달. 이것을 통해서도 데이터 전송 완료를 확인이 가능하다.
);

비동기 방식에서는 함수가 반환되는 것이 모든 데이터의 전송을 의미하는 것이 아니다.
데이터는 시시각각 전송되고 완료되므로 최종적인 데이터가 전송됐을때의 상황을 체크하는 것이 가장 중요하며.
다음의 함수를 통해서 현재까지 전송된 함수의 데이터 크기를 확인이 가능하다.
BOOL WSAAPI WSAGetOverlappedResult(
  _In_   SOCKET s, -> Overlapped가 진행중인 소켓의 핸들
  _In_   LPWSAOVERLAPPED lpOverlapped, -> Overlapped가 진행 중인 WSAOVERLAPPED 구조체 변수의 값 전달.
  _Out_  LPDWORD lpcbTransfer, -> 실제 송수신된 바이트 크기를 저장할 변수의 주소 값 전달.
  _In_   BOOL fWait, -> 여전히 IO가 진행중인 상황의 경우 , TRUE 전달시 IO가 완료될 때까지 대기를 하게 되고 , FALSE 전달시 FALSE를 반환하면서 함수를 빠져나온다.
  _Out_  LPDWORD lpdwFlags -> WSARecv함수가 호출된 경우, 부수적인 정보(수신된 메시지가 OOB 메세지인지와 같은)를 얻기 위해 사용된다. 불필요하면 NULL을 전달한다.
);

2-2. 비동기 수신함수
int WSARecv(
  _In_     SOCKET s, -> 소켓의 핸들 Overlapped IO 속성의 핸들 전달
  _Inout_  LPWSABUF lpBuffers, -> 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로 이루어진 배열의 주소 값 전달.
  _In_     DWORD dwBufferCount, -> 두 번째 인자로 전달된 배열의 길이정보 전달.
  _Out_    LPDWORD lpNumberOfBytesRecvd, -> 전송된 바이트 수가 저장될 변수의 주소 값 전달 (이는 잠시 후 별도로 설명)
  _Inout_  LPDWORD lpFlags, -> 함수의 데이터 전송특성을 지정하거나 수신하는 경우에 사용된다..
  _In_     LPWSAOVERLAPPED lpOverlapped, ->WSAOVERLAPPED 구조체 변수의 주소 값 전달
  _In_     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
-> Completion Routine라는 함수의 주소 값 전달. 이것을 통해서도 데이터 전송 완료를 확인이 가능하다.
);

3. 오류의 확인
만약 비동기 방식으로 진행중이던 작업이 오류가 난다면 다음의 함수를 통해서 확인이 가능하다.
int WSAGetLastError(void);
-> 오류 상황에 대한 상태 값(오류의 원인을 알리는 값) 반환.
비동기 방식은 항상 오류에 대한 값이 반환된다.
항상 모든 데이터가 전송되는것이 아니라 끊겨서 전송되기 때문이다.
아직 데이터가 모두 전송되지 않은 상태를 위의 함수에서는 WAIT_IO_COMPLETION으로 반환하며 그 이외의 상황에 대한 처리를 해주면 될 것이다.

IOCP란? -------------------------------------------------------------------------------------------------------


소켓통신이란 일반적으로 정해진 통신규약이고 운영체제도 그에 따르고 있다. 

즉 윈도우와 리눅스방식이 현재까지 크게 다르지 않았던 이유는 운영체제의 특징적인 부분보다는 기본적인 규격을 지키는 방식을 사용했기 때문이다.

하지만 운영체제란 분명히 그에 맞는 특색이 있고 특색에 따라서 더 많은 것을 지원해 줄수 있을 것이다.


IOCP란 윈도우에서 지원해주는 비동기 통신방식을 의미한다. 설명을 하자면 다음과 같은 방식을 가지고 있다.


1. 비동기방식을 사용하는 소켓을 다수 생성한다.

2. 쓰레드를 통해서 소켓에 발생하는 이벤트를 병렬 관찰 한다.

3. 각각의 소켓은 비동기로 입출력을 실행한다. 


종합 -> 즉 다수의 소켓이 다수의 쓰레드에 의해서 관리 받으며 비동기로 데이터 송수신을 하게 되는 구조를 의미한다.


* 비동기 모드의 서버소켓 구성하기


accept함수는 현재까지 동기화구조로 구성되어 있었다. 하지만 이 서버 소켓도 비동기화 방식으로 구현이 가능하다.

비동기 소켓의 생성 방법은 WSASocket으로 생성을 하면 하지만. ioctlsocket함수를 통해서 실제 비동기 방식의 옵션을 켜줘야 한다.


int mode = 1;


hLisnSock = WSASocket(함수 호출)


ioctlsocket(hLisnSock, FIONBIO, &mode)


의미를 보면 다음과 같다.

"서버소켓 hLisnSock의 FIONBIO(입출력모드)를 mode의 주소에 저장된 값의 형태로 변경한다."

"mode의 값이 0이 아닌 수라면 두번째 인자값의 기능을 on시킨다고 보면 된다."


* IOCP방식으로 소켓을 관리할 포트 생성하기


포트가 무엇이냐면 소켓이 10개 있다고 했을때 윈도우 차원에서 이 소켓을 관리해줄 오브젝트를 생성하는 것을 의미한다.

다음의 함수를 통해서 생성이 가능하다.


HANDLE WINAPI CreateIoCompletionPort(

  _In_        HANDLE FileHandle, -> CP 오브젝트 생성시에는 INVALID_HANDLE_VALUE 전달

  _In_opt_  HANDLE ExistingCompletionPort, -> CP 오브젝트 생성시에는 NULL 전달.

  _In_        ULONG_PTR CompletionKey, -> CP 오브젝트 생성시에는 0 전달.

  _In_        DWORD NumberOfConcurrentThreads -> CP오브젝트에 할당되어 완료된 IO를 처리할 쓰레드의 수를 전달한다.

); 성공시 CP오브젝트 핸들 반환 실패시 NULL 반환


생성시라고 적혀있는 이유는 이 함수가 CP오브젝트와 소켓을 연결하는 역할로도 사용되기 때문이다.


HANDLE WINAPI CreateIoCompletionPort(

  _In_        HANDLE FileHandle, -> CP 오브젝트에 연결할 소켓의 핸들 전달.

  _In_opt_  HANDLE ExistingCompletionPort, -> 소켓과 연결할 CP 오브젝트 전달.

  _In_        ULONG_PTR CompletionKey, -> 완료된 IO관련 정보의 전달을 위한 매개변수,

  _In_        DWORD NumberOfConcurrentThreads -> 어떠한 값이 전달되건, 이 함수의 두번째 매개변수가 NULL이 아니라면 무시된다.

); 성공시 CP오브젝트의 핸들, 실패시 NULL 반환


세번째 정보는 IO는 CP 오브젝트와 그와 연결된 함수를 체크하는 항목과도 관련되어 있으니 확인해보도록 하자.


BOOL WINAPI GetQueuedCompletionStatus(

  _In_   HANDLE CompletionPort, -> 완료된 IO정보가 등록되어있는 CP오브젝트의 핸들 전달.

  _Out_  LPDWORD lpNumberOfBytes, -> 입출력 과정에서 송수된 데이터의 크기 정보를 저장할 변수의 주소 값 전달.

  _Out_  PULONG_PTR lpCompletionKey, 

-> CreateIoCompletionPort변수에서 연결 방식으로 사용할때의 세번째 변수의 값을 반환받을 변수의 주소 값 전달.

  _Out_  LPOVERLAPPED *lpOverlapped, 

-> WSASend, WSARecv 함수 호출시 전달하는 OVERLAPPED 구조체 변수의 주소 값이 저장된 변수의 주소 값 전달.

  _In_   DWORD dwMilliseconds 

-> 타임 아웃 전달, 여기서 지정한 시간이 완료되면 FALSE를 반환하면서 함수를 빠져나간다. 

INFINITE를 전달하면 완료된 IO가 CP오브젝트에 등록될 때까지 블로킹 상태에 있게 된다.

);


IOCP 과정을 요약해 보자면 다음고 같다.

1. CreateIoCompletionPort함수로 포트를 생성하고 그안에 소켓을 연결한다.

2. CreateIoCompletionPort함수로 생성된 포트로 관리하고 싶은 소켓을 연결한다.

3. GetQueuedCompletionStatus함수로 소켓들중 상태에 변화가 있는 소켓을 반환받아서 그와 관련된 작업을 한다.

Posted by JJOREG

동기화와 비동기화 --------------------------------------------------------------------------------------------------



일단 동기화에 대한 설명

1. 동기화란 일단 실행순서의 동기화 개념으로 이해하면 편하다.

2. 간단히 설명하자면 함수는 함수내의 연산이 끝나기 전에는 함수는 반환되지 않는다.

3. 단순하게 본다면 당신의 프로그램은 정지한다. (물론 파일을 읽어오는 연산을 열심히 하고 있기는 하겠지만.)

4. 10기가짜리 파일을 읽어오는 연산은 10분이 걸린다고 치자. 10기가짜리 파일을 처음부터 끝까지 다 읽어야 함수가 반환된다.

5. 당신의 컴퓨터는 10분동안 정지한다.


그렇다면 비동기화란?

1. 비동기화란 하나의 실행흐름을 또 만들어내는 작업이라고 생각해보자. 그리고 그것을 OS에서 지원해준다고 생각해보자.

2. 10기가짜리 파일을 읽어오는 연산은 10분이 걸린다고 치자. 10기가짜리 파일을 처음부터 끝까지 다 읽어야 함수가 반환된다.

3. 당신의 컴퓨터는 CPU에 파일을 읽어오는 연산에 대한 명령을 내린다.

4. 당신의 컴퓨너는 한편으로는 10기가짜리 파일에 대한 입출력 연산을 하면서 동시에 당신의 프로그램또한 실행시킬 것이다.

5. 10기가짜리 파일 입출력과 이후의 프로그램은 함께 실행될 것이다.


이해가 가는가?


Asynchronous(비동기) Notification(알림) IO 방식 ---------------------------------------------------------------------


비동기로 알림을 처리한다?

무슨 말인지 선뜻 이해가 안간다면 RECV와 함수를 좀더 생각해 보자.

RECV함수는 한번 실행되면 신호가 있건 없건 연락이 올때까지 무한정 기다린다.

하지만 이것을 자신의 입력버퍼에 데이터가 있을때만 체크해준다면 좀더 중간중간의 입출력에 관련된 작업에 효율을 기할 수 있지 않을까?

즉 상대가 데이터를 분명히 보내서  꼭 필요할때만 RECV함수를 호출할 수 없는가? 라는 것이다.

이러려면 소켓의 상태를 파악해줄 방법이 필요하다. 이에 대한 방법으로는 2가지 방법이 가능한다.


WSAEventSelect

WSAAsyncSelect

두가지 함수를 이용하는 것이며 책에서는 WSAEventSelect의 방식이 나와있으므로 그에 대해서 설명한다.


WSAEventSelect를 이용한 방식이란 윈도우에서는 이벤트라는 쓰레드 동기화용 커널오브젝트를 생성하는 기능이 이를 소켓에 적용하여 소켓의 신호호를 이벤트로 보고 처리하는 방식을 말한다.


이 비동기 방식에는 다음의 단계를 거치게 된다.


1. 이벤트와 소켓의 연결 -> 변화가 생긴 소켓을 체크하기 위해서 이벤트와 소켓을 하나로 엮는 방법을 알아야 한다.

2. 이벤트를 통한 소켓의 상태 체크 -> 엮여진 소켓들을 체크해가면서 실제 변화가 있는 녀석이 있는지 감시해야 한다.

3. 이벤트의 종류 판별 -> 소켓에 발생한 이벤트가 무엇인지 판단해야하고 그에 따라 분류해서 처리해줘야 한다.


1. 이벤트와 소켓의 연결.


int WSAEventSelect(

  _In_  SOCKET s,                        -> 이벤트와 연결된 소켓을 인자로 준다.

  _In_  WSAEVENT hEventObject,   -> 소켓과 연결한 이벤트핸들을 설정한다.

  _In_  long lNetworkEvents           -> 감시하고자하는 이벤트의 유형 정보를 전달한다.

);


세번째 인자의 이벤트 정보값

FD_READ -> 수신할 데이터가 존재하는가?

FD_WIRTE -> 블로킹 없이 데이터 전송이 가능한가?

FD_OOB -> Out-of-band 데이터가 수신되었는가?

FD_ACCEPT -> 연결요청이 있었는가?

FD_CLOSE -> 연결의 종료가 요청되었는가?


2. 이벤트를 통한 소켓의 상태 체크


DWORD WSAWaitForMultipleEvents(

  _In_  DWORD cEvents, -> signaled 상태로의 전이여부를 확인한 Event 오브젝트 개수 정보 전달.

  _In_  const WSAEVENT *lphEvents, -> Event 오브젝트의 핸들을 저장하고 있는 배열의 주소 값 전달.

  _In_  BOOL fWaitAll, -> TRUE시 모든 Event가 signaled 상태일 때 반환, FALSE 전달시 하나만 signaled 상태가 되어도 반환.

  _In_  DWORD dwTimeout, -> TRUE 전달시, alertable wait 상태로 진입

  _In_  BOOL fAlertable -> 반환된 정수 값에서 상수값 WSA_WAIT_EVENT_0을 빼면, 두 번째 매개변수로 전달된 배열을 기준으로, signaled 상태가 된 event오브젝트의 핸들이 저장된 인덱스가 계산된다. 만약 둘 이상의 Event 오브젝트가 signaled 상태로 전이 된다면, 그중 작은 인덱스 값이 계산되고 타임아웃이 발생하면 WAIT_TIMEOUT가 반환된다.

);


3. 이벤트의 종류 판별


이벤트 종류 판별용 구조체.
int WSAEnumNetworkEvents(
  _In_   SOCKET s,                            -> 이벤트를 체크할 소켓을 확인한다.
  _In_   WSAEVENT hEventObject,       -> 소켓과 연결된 이벤트를 넣는다.
  _Out_  LPWSANETWORKEVENTS lpNetworkEvents -> 이벤트의 분류된 정보를 넣어줄 구조체를 넣는다.
);

세번째 인자값에 들어갈 구조체의 내용을 확인해보자.
typedef struct _WSANETWORKEVENTS {

       long lNetworkEvents; -> 이벤트의 종류

       int iErrorCode[FD_MAX_EVENTS]; -> 에러코드를 담을 배열

} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;


두번째 값인 iErrorCode[FD_MAX_EVENTS];의 내용를 확인해보자.

FD_READ -> 수신할 데이터가 존재하는가?

FD_WIRTE -> 블로킹 없이 데이터 전송이 가능한가?

FD_OOB -> Out-of-band 데이터가 수신되었는가?

FD_ACCEPT -> 연결요청이 있었는가?

FD_CLOSE -> 연결의 종료가 요청되었는가?

위와 같은 옵션중 한가지에 오류가 발생한다면 그 배열의 순서의 비트를 0이 아닌 값으로 채워 넣는다.


FD_READ_BIT      0

FD_WRITE_BIT     1

FD_OOB_BIT       2

FD_ACCEPT_BIT    3

FD_CONNECT_BIT   4

FD_CLOSE_BIT     5

이런식으로 처리가 되는 것이다.

Posted by JJOREG

Select 함수 -------------------------------------------------------------------------------------------------------

하나의 소켓 하나의 쓰레드로 여러개의 소켓을 관리하는 방식인 멀티플렉싱은 다음의 Select함수를 사용하여 구현할 수 있다.(다른 함수나 로직은 아직 배우지 않았다.)

함수의 구조는 다음과 같다.


int select(

  _In_     int nfds, -> 동시에 관리할 소켓의 개수

  _Inout_  fd_set *readfds, -> fd_set형 수신된 데이터의 존재여부에 관심 있는 소켓 정보를 모두 등록해서 그 변수의 주소값을 전달한다.

  _Inout_  fd_set *writefds, -> fd_set형 블로킹 없는 데이터 전송의 기능여부에 관심있는 소켓정보를 모두 등록해서 그 변수의 주소 값을 전달한다.

  _Inout_  fd_set *exceptfds, -> fd_set형 '예외상황의 발생여부'에 관심이 있는 파일 디스크립트 정보를 모두 등록해서 그 변수의 값을 전달한다.

  _In_     const struct timeval *timeout -> select 함수 호출 이후 무한정 블로킹 상태에 빠지지 않도록 타임아웃을 설정하기 위한 인자를 전달한다.

); -> 오류발생시에는 -1이 반환되고 타임아웃에 의한 반환 시에는 0이 반환된다. 그리고 관심 대상으로 등록된 소켓에 해당 관심에 관련된 변화가 발생하면 0보다 큰 값이 반환된다. 이 값은 변화가 발생한 소켓의 수를 의미한다.


fd_set 구조체 -----------------------------------------------------------------------------------------------------


fd_set 구조체에 대해서 알아봅시다.


typedef struct fd_set {
  u_int  fd_count; -> 현재 구조체 안에 등록되어있는 파일 개수
  SOCKET fd_array[FD_SETSIZE]; -> 소켓을 등록할 수 있는 소켓 배열
} fd_set;

이 구조체를 위한 전용매크로 함수가 존재한다.
FD_ZERO(fd_set* fdset) 인자로 전달된 fd_set구조체의 모든 비트를 0으로 만든다.
FD_SET(int fd, fd_set* fdset) 매개변수 fdset로 전달된 주소의 변수에 매개변수 fd로 전달된 소켓 정보를 등록한다.
FD_CLR(int fd, fd_set* fdset) 매개변수 fdset로 전달된 주소의 변수에 매개변수 fd로 전달된 소켓 정보를 삭제한다.
FD_ISSET(int fd, fd_set* fdset) 매개변수 fdset로 전달된 주소의 변수에 매개변수 fd로 전달된 소켓에 말그대로 정보(Select의 세가지 체크값에 대한 변화값이 있으면 체크한다) 양수를 반환한다.




Posted by JJOREG

멀티프로세스와 멀티플랙싱 ------------------------------------------------------------------------------------------------------------


시스템 프로그램에서 멀티프로세스에 대한 설명을 했으니 이해가 어렵지는 않을 것이다. 멀티프로세스란 말그대로 클라이언트 1개당 하나의 프로세스를 생성하여 각 클라이언트의 입출력을 진행하는 것이다. 멀티프로세스 서버는 클라이언트 연결요청이 있을 때마다, 새로운 프로세스를 생성하는 기법으로, 상당히 많은 양의 연산이 요구되고, 필요한 메모리 공간도 비교적 큰 편이다.

 

따라서 프로세스의 생성을 동반하지 않고, 다수의 클라이언트들에게 서비스를 제공할 수 있는 방법으로 나온 것이 멀티 플렉싱기법이다.

 

멀티플렉싱은 물리적 장치의 효율성을 높이기 위해 최소한의 물리적인 요소만 사용해서 최대한의 데이터를 전달하기 위해 사용하는 기술이다.

 

기존의 자식프로세스를 생성해서 서비스를 하는 멀티프로세스 서버와는 달리,

 

멀티플렉싱개념을 사용하면,



자식프로세스가 각각의 소켓을 처리하는게 아니고, 서버하나가 각각의 소켓에 대한 서비스를 제공하는게 멀티플렉싱의 개념이다.

 멀티플렉싱과 멀티프로세스의 차이는 서비스를 제공하는 프로세스 갯수의 차이가 각각 한개와, 클라이언트수만큼이라는 것이다.

 

 

멀티플렉싱 서버를 사용하기 적합한 경우.

1. 클라이언트와 서버간의 송수신 데이터 용량이 작은 경우 적합 

2. 송수신이 연속적이지 않은 경우에 적합 

3. 멀티 프로세스 기반에 비해 많은 수의 클라이언트 처리에 적합

Posted by JJOREG

소켓의 다양한 옵션 -------------------------------------------------------------------------------------------------


소켓어 적용할수 있는 다양한 옵션들을 정리해봤다.



GET은 함수를 통해서 이 인자값을 가져올수 있는지에 대한 표이며, SET은 함수를 통해서 이 옵션이 변경 가능한 것인지를 나타낸다. 

즉 에러와 소켓 타입은 변경이 불가능하다. TCP소켓을 UDP소켓으로 변경하는건 함수를 통해서는 안된다는 것이다.

(다른 방법이 있을수도 있으니 확언은 안한다. 아예 기계어 수준에서 제어해버린다던가 항상 방법은 존재하는 법이니까.)


옵션의 확인 함수 -------------------------------------------------------------------------------------------------


sock: 설정 상태를 확인해 보고 싶은 소켓의 파일 디스크립터를 인자로 전달한다.

level: 확인할 옵션의 프로토콜 레벨(Protocol Level)을 인자로 전달한다.

optname: 확인할 옵션의 이름을 전달한다.

oprval: 확인 결과를 저장할 버퍼를 가리키는 포인터를 전달한다.

optlen: optval 포인터가 가리키는 버퍼의 크기를 전달한다. 함수 호출이 완료되면, 전달된 포인터가 가리키는 변수에는 버퍼에 저장된 확인 결과의 길이가 바이트 단위로 저장된다.

 

옵션의 변경 -------------------------------------------------------------------------------------------------



sock: 설정 상태를 변경하고자 하는 소켓의 파일 드스크립터를 인자로 전달한다.

level: 변경할 옵션의 프로토콜 레벨을 인자로 전달한다.

optname: 변경할 옵션의 이름을 전달한다.

optval: 변경할 옵션의 값을 저장한 버퍼의 포인터를 전달한다.

optlen: 전달하는 옵션의 바이트 단위 길이를 전달한다.


중요한 인자값들 -------------------------------------------------------------------------------------------------

SO_SNDBUF & SO_RCVBUF

소켓이 생성되면 기본적으로 입력버퍼와 출력버퍼가 생성된다. 이를 제어할수 소켓의 옵션이다.


SO_REUSEADDR

Time_wait상태에 대한 이해가 필요한 옵션이다.


Time_wait란?

TCP연결을 종료하기 위해서는 FIN패킷 교환이 이루어진다. 이때 우아한 종료가 이루어지기 위해서는 총 4번의 패킷교환이 필요하다.


우아한 종료라는 것은 연결된 호스트 양쪽이 모두 연결이 종료되었음을 알게 되는 상태다. 만약 FIN 패킷을 보냈는데, 상대 호스트에서 ACK 패킷을 보내지 않고 종료해버리는 경우 FIN을 보낸측은 우아한 종료를 위해서 일정시간 ACK를 기다리게 된다. 리눅스의 경우 대략 90초 정도를 기다린다. netstat(1)로 확인해 보면 TIME_WAIT인 상태로 나타난다.


TIME_WAIT상태일 경우 해당 포트를 계속 점유하는데, 연결이 빈번한 네트워크 서비스일 경우 연결거부와 관련된 문제가 발생할 수 있다.

호스트 A와 호스트B가 있다고 했을때 호스트 B가 FIN메세지를 보냈다. FIN메세지를 보낸후 호스트 A가 그에 대한 ACK 메세지를 보내줘야 한다. 그런데 그전에 호스트A가 메세지를 전송하자마자 소켓을 바로 소멸시킨다면? 그럼 마지막 메세지는 전달되지 못하고 소멸된다. 그럼 호스트 B는 호스트 A에게 메세지를 다시 보내달라고 또 FIN메세지를 전송할 것이다. 하지만 이제 호스트A가 없다? 영원히 ACK메세지를 받지 못하는 상태에 놓이게 되는 것이다. 그러나 호스트 A가 TIME_WAIT일때는 마지막 메세지를 보낼때까지 말그대로 시간을 기다리게 된다.

이로서 호스트 B는 정상적으로 종료가 가능하게 된다.


이런면으로 보면 TIME_WAIT상태는 매우 바람직한 동작으로 된다. 마땅히 보내야할 메세지를 보내고 소켓을 종료하는 것이니 말이다. 하지만 이는 반대로 말하자면 TIME_WAIT상태가 끝날때 까지는 소켓이 포트번호를 점유하고 있다는 것을 의미한다. 만약 통신회선이 좋지 못하다면 얼마까지 TIME_WAIT 상태를 기다려야 할지 알수가 없는 것이다.


이를 해결하기 위해서 필요한 것이. SO_REUSEADDR상태를 변경하는 것이다. 


SO_REUSEADDR의 인자값은 0(FALSE)인데 이는 TIME_WAIT상태에서 PORT번호가 할당 불가능 하다는 것을 의미한다.

이 값을 1로 변경해주면 해제가 된다.


TCP_NODELAY

Nagle 알고리즘의 사용여부를 설정하는 옵션이다.

Nagle 알고리즘이란 네트워크 상에서 돌아다니는 패킷들이 흘러넘치는 것을 막기 위한 알고리즘다.

"Nagle 알고리즘이란 앞서 전송한 데이터에 대한 ACK 메세지를 받아야만 다음 데이터를 전송하는 알고리즘이다."

즉 기본적으로 사용하고 있는 알고리즘이다. 쓰리 웨이 핸드 쉐이킹이 기본적으로 이 알고리즘을 쓰기 때문이다.

하지만 이는 짧은 데이터를 연속적으로 보낼때는 신뢰성을 높여주는 알고리즘 이지만. 한번한번의 메세지를 보내는 처리 또한 연산이며 패킷이므로 이 옵션을 끄면 데이터 송수신의 속력이 올라가게 된다.

대표적인 경우는 대용량 파일입출력으로 거의 항상 출력버퍼를 가득채워넣기 때문에 이 옵션을 켰을 경우 상당한 연산을 하게 된다. 

필요한 경우 이 옵션을 변경해 주는것으로 성능향상을 기대할 수 있다.


윈도우용 함수들 -------------------------------------------------------------------------------------------------


int setsockopt(

  _In_  SOCKET s,  ->옵션 확인을 위한 소켓의 핸들 전달.

  _In_  int level, -> 확인할 옵션의 프로토콜 레벨설정

  _In_  int optname, -> 확인할 옵션의 이름 전달.

  _In_  const char *optval, -> 확인결과의 저장을 위한 버퍼의 주소 값 전달.

  _In_  int optlen -> 네번째 매개변수로 전달된 버퍼크기를 담고 있는 변수의 주소 값 전달. 함수 호출이 완료되면 네번째 인자값의 반환된 옵션정보의 크기가 바이트 단위로 계산되어 저장된다.

);


int getsockopt(
  _In_     SOCKET s, -> 옵션 확인을 위한 소켓의 핸들 저장
  _In_     int level, -> 변경할 옵션의 프로토콜 레벨 설정
  _In_     int optname, -> 확인할 옵션의 이름 전달.
  _Out_    char *optval, -> 변경할 옵션 정보를 저장할 버퍼의 주소 값 전달.
  _Inout_  int *optlen -> 네번째 매개변수 optval로 전달될 옵션 정보의 바이트 크기 전달.
);


Posted by JJOREG

도메인이름으로 IP주소 얻기----------------------------------------------------------------------------------------


도메인 주소를 통해서 IP주소를 얻어올 수 있는 함수가 존재한다. 이 함수가 존재하면 서버의 IP주소로부터 클라이언트 프로그램은 자유로울 수 있다.

도메인 주소만 안다면 그를 통해서 현재 그 도메인주소에 해당하는 IP주소를 얻어올 수 있기 때문이다.

함수의 내용도 굉장히 간단하다.


-> 윈도우형 인자값으로 했으나 거의 차이가 없으므로 그대로 소개한다.

struct hostent* FAR gethostbyname(

  _In_  const char *name -> 도메인 주소를 입력한다.

); -> 성공시 hostent 구조체 변수의 주소 값, 실패 시 NULL 포인터 반환


여기서 반환되는 구조체의 구조를 살펴보자,


struct hostent

{

  char *h_name;                 /* Official name of host.  */ 

  char **h_aliases;             /* Alias list.  */ 

  int h_addrtype;               /* Host address type.  */

  int h_length;                 /* Length of address.  */

  char **h_addr_list;           /* List of addresses from name server.  */

#define h_addr  h_addr_list[0]  /* Address, for backward compatibility.  */

};



struct hostent

{

  char *h_name;                 /* Official name of host.  */ 

-> 공식 도메인 이름

  char **h_aliases;             /* Alias list.  */ 

-> 같은 메인페이지 인데도 다른 도메인 페이지로 접근하는 경우 공식 도메인 이름 이외에 해당 메인 페이지에 접속할 수 있는 다른 도메인 이름의 지정이 가능하다.

  int h_addrtype;               /* Host address type.  */

->IPv4프로토콜 타입만 아니라 IPv6도 지원하기 때문에 h_addr_list로 반환된 IP주소의 주소체계에 대한 정보를 이 멤버를 통해 반환한다.

IPv4일 경우에는 AF_INET가 입력된다. IPv6의 경우에는 AF_INET6이 저장된다.

  int h_length;                 /* Length of address.  */

-> 함수 호출 결과로 반환된 IP주소의 크기 정보가 담긴다. IPv4는 4바이트 이므로 4가 저장되고 IPv6의 경우에는 16바이트 이므로 16이 저장된다.

  char **h_addr_list;           /* List of addresses from name server.  */

-> 가장 중요한 맴버변수로 이 멤버를 통해서 도메인 이름에 대한 IP주소가 정수의 형태로 반환된다. 참고로 접속자수가 많은 서버는 하나의 도메인 이름에 대응하는 여러 IP를 두기 때문에 둘이상의 서버로 부하를 분산시키는 경우가 있는데 list가 의미하듯 이 맴버를 통해서 IP의 주소 정보를 모두 얻을 수 있다.

#define h_addr  h_addr_list[0]  /* Address, for backward compatibility.  */

};


IP주소로 도메인 정보 얻기----------------------------------------------------------------------------------------


gethostbyname함수의 반대 역할을 한다고 보면 된다.


-> 윈도우형 인자값으로 했으나 거의 차이가 없으므로 그대로 소개한다.

struct hostent* FAR gethostbyaddr(

  _In_  const char *addr,

  _In_  int len,

  _In_  int type

);


  _In_  const char *addr, -> ip주소를 지니는 in_addr 구조체 변수의 포인터를 전달한다. IPv4 이외의 다양한 정보를 전달 받을 수 있도록 일반화하기 위해서 매개변수를 char형 포인터로 선언한다.

  _In_  int len, -> 첫번째 인자로 전달된 주소정보의 길이다. IPv4는 4바이트 이므로 4를 전달하고 IPv6의 경우에는 16바이트 이므로 16이 전달한다.

  _In_  int type -> 주소체계 정보 전달. IPv4일 경우에는 AF_INET가 입력된다. IPv6의 경우에는 AF_INET6이 전달한다.



Posted by JJOREG

TCP기반의 연결 종료 방법 ------------------------------------------------------------------------------------------


int closesocket(

  _In_  SOCKET s -> 연결을 종료하고자 하는 소켓을 인자로 넘긴다.

); -> 성공시 0반환 실패시 SOCKET_ERROR반환

소켓의 연결을 종료하기 위한 closesocket 함수의 호출은 완전종료를 의미한다.

말그대로 묻지도 따지지도 않고 그대로 호출한 쪽의 소켓을 완전히 끊어 버리는 것이다.

이는 현재 전송되고 있던 데이터까지 소멸시키면서 연결을 종료시킨다는 것을 의미한다. 즉 통보나 뭣도 없이 갑자기 연락을 끊어버리기 때문에 어떤 오류가 생길 수도 있는 것이다.


이를 막기 위해서 TCP기반 종료함수로 다음과 같은 함수가 존재한다.


int shutdown(

  _In_  SOCKET s, -> 연결을 종료하고자 하는 소켓을 입력

  _In_  int how -> 종료 방법에 대한 플래그 전달.

); -> 성공시 0 실패시 -1 반환.


첫번째는 이해가 가지만 두번째인자값은 쉽게 이해가 가지 않을 것이다.


전에 이야기 했던 입출력 스트림을 한번더 보자.

하나의 소켓은 입력버퍼와 력버퍼를 가지고 있다. 이 버퍼들은 입출력스트림을 생성한다.

여기에서 소켓을 닫는다는 것은 두개의 버퍼를 모두 정지시킨다는 이야기이다.

하지만 두개를 개별적으로 닫을수 있다면? 그리고 데이터 처리가 완전히 끝나면 닫을 수 있다면?

그러기 위해서 존재하는게 shutdown함수다.


두번째 인자값으로 넣을 수 있는 파라미터는 다음과 같다.

SHUT_RD -> 입력스트림을 종료한다.

SHUT_WR -> 출력스트림을 종료한다.

SHUT_RDWR -> 입출력스트림을 종료한다.


Half_close ------------------------------------------------------------------------------------------

위에서 나온 인자값으로 하나씩 스트림을 닫는것을 하프 클로즈라고 한다. 여기에서 위의 인자값의 의미를 파악해보자.


SHUT_RD -> 입력스트림을 종료한다. 단 데이터가 입력버퍼에 전달되어도 그냥 지워버릴 뿐만 아니라. 입력 관련 함수의 호출도 허용하지 않는다.

SHUT_WR -> 출력스트림을 종료한다. 단 출력버퍼에 데이터가 남아있다면 그 데이터를 출력시키고 스트림을 종료한다.

SHUT_RDWR -> 입출력스트림을 종료한다. 단 위의 RD와 WR을 순차적으로 실행한다.


윈도우에서는 ------------------------------------------------------------------------------------------

윈도우에서도 동일한 셧다운 함수가 존재하지만 다음과 같이 두번째 인자값에 차이가 있다.

SD_RECEIVE -> SHUT_RD의 역할을 한다.

SHUT_SEND -> SHUT_WR의 역할을 한다.

SD_BOTH -> SHUT_RDWR의 역할을 한다.







Posted by JJOREG

UDP의 상세한 설명 -------------------------------------------------------------------------------------------------

앞선 장에선 TCP의 데이터 전송에 있어서 흐름제어 즉 데이터를 손실하지 않는 방법에 대해서 배웠다.

이번에는 반대로 UDP의 데이터 전송방식과 UDP는 어째서 데이터 손실을 보장하지 않는지에 대해서 알아볼 차례이다.


UDP방식에서 소켓이란? ---------------------------------------------------------------------------------------------

 TCP방식에서는 하나의 클라이언트에 전담자를 두어서 두개의 소켓이 데이터 송수신시 서로간에 신호를 통해서 데이터 전송의 신뢰성을 획득했다.


 A와 B의 관계에서 서로간에 송신과 수신이 잘 이루어졌다는 신호를 보내는 쓰리 웨이 핸드쉐이킹이 존재하기 때문이다.


 하지만 이것이 가능하기 위해서 1개의 클라이언트 소켓과 1개의 담당 소켓은 항시 서로 신호를 보내며 연결 상태를 유지해야 한다. 

 즉 1개의 서버에 10개의 클라이언트가 접속한다면 서버에서는 10명의 담당 소켓을 만들어줘야 신뢰성 있는 데이터 송수신이 가능한 구조였다.


 그렇다면 UDP의 경우에는 어떨가?


 UDP는 구성에 따라서 1개의 서버 1개의 소켓이 10개의 클라이언트를 맡아서 처리할 수도 있다. 

 거기다가 기존의 연결을 위한 listen함수도 필요가 없다. 소켓을 생성하고 보내기만 하면 된다. 

말로하는 것보다는 TCP처럼 각 소켓간의 송수신이 잘 되었는지에 대한 신호는 필요가 없다. 말로 설명하는 것보다는 함수를 보면서 이해해보자.


UDP방식 소켓 생성 ---------------------------------------------------------------------------------------------

UDP방식이라고 소켓을 다른 방식으로 생성하지는 않는다. socket 함수를 통해서 생성한다.

다만 함수에 들어가는 인자값이 다를 뿐이다. socket함수에 대해서 다시한번 살펴보자.


SOCKET socket(

  _In_  int af, -> 소켓이 사용할 프로토콜 체계 

  _In_  int type, -> 소켓이 사용할 연결방식 (연결 지향형 비연결지향형)

  _In_  int protocol -> 소켓이 사용할 실제적인 프로토콜.

);


SOCKET socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

IPv4 인터넷 프로토콜 체계에서 동작하는 비 연결지향형 UDP 소켓을 생성하여 반환하라.

식으로 UDP소켓을 생성해주기만 하면 이 소켓을 통해서 데이터를 보낼 준비가 된것이다. 


UDP방식 데이터 전송  ---------------------------------------------------------------------------------------------


int sendto(

  _In_  SOCKET s, -> 데이터 전송에 필요한 UDP소켓

  _In_  const char *buf, -> 전송할 데이터를 저장하고 있는 버퍼의 주소 값

  _In_  int len, -> 전송할 데이터의 크기를 바이트 단위로 전달.

  _In_  int flags, -> 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달.

  _In_  const struct sockaddr *to, -> 목적지의 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값 전달.

  _In_  int tolen 매개변수 to으로 전달될 주소에 해당하는 구조체 변수의 크기 정보를 담고 있는 변수의 주소 값 전달.

); -> 성공시 전송된 바이트 수, 실패시 -1을 반환한다.


TCP기반의 send함수와 비교되는 점은 당연히 주소값을 넣어주는 곳이다. sendto함수는 연결되어있지 않기 때문에 어디로 보내야할지를 함수 자체에서 결정한다. 그러므로 bind함수를 통한 주소전달 작업이 필요없다.

이는 좀더 자세히 말하면 *to에 입력될 주소값만 바꿔주면 어떤 데이터든 전송이 가능하다는 말이다.


UDP방식 데이터 수신 ---------------------------------------------------------------------------------------------


int recvfrom(

  _In_         SOCKET s, -> 데이터를 수신할 UDP소켓

  _Out_        char *buf, -> 데이터 수신에 사용될 버퍼의 주소 값 전달.

  _In_         int len, -> 수신할 최대 바이트 수 전달, 당연히 2번째 인자값으로 전달되 버퍼의 크기를 넘을 수 없다.

  _In_         int flags, -> 옵션 지정에 사용되는 매개변수 지정할 옵션이 없다면 0 전달.

  _Out_        struct sockaddr *from, 발신지 정보를 채워넣을 sockaddr구조체 변수의 주소 값 전달.

  _Inout_opt_  int *fromlen -> 매개변수 from으로 전달될 주소에 해당하는 구조체 변수의 크기 정보를 담고 있는 변수의 주소 값.

); -> 성공시 받아온 데이터의 바이트 수 반환, 실패시 -1을 반환한다.


TCP기반의 recv함수와 비교되는 점은 당연히 주소값을 넣어주는 곳이다. 이는 좀더 자세히 말하면 *fom에 입력될 주소값만 바꿔주면 어떤 데이터든 수신이 가능하다는 말이다.


여기서 잠깐 데이터 경계가 뭐지? ---------------------------------------------------------------------------------



2번째 항목이 보면 데이터 경계를 구분하지 않는 다는 말이 확 와닫지 않는다.

자세히 설명한다면 이렇다. TCP같은 경우 100바이트의 데이터를 보낸다고 하면

20 20 20 20 20 바이트로 5번을 끊어서 보내건 100바이트를 한번에 보내건

recv함수 한번으로 현재까지 보낸 데이터를 수신이(한번에 혹은 여러번에) 가능하다. 

입출력 버퍼와 내부의 쓰리 웨이 핸드 쉐이킹이라는 과정이 존재하기 때문에 내부의 남아있는 데이터와 수신된 데이터를 파악이 가능하기 때문이다.

하지만 UDP방식은 다르다.

상대가 3번의 함수를 호출했다면 UDP방식의 소켓은 recv함수를 3번 호출해야만 한다.

이 차이는 굉장히 크다. UDP방식으로 통신했을 때는 상대가 보낸 함수의 호출횟수만큼 그리고 그 길이만큼 정확히 호출해줘야 한다.


CONNECTED UDP방식 데이터 전송  ----------------------------------------------------------------------------------

connect udp 소켓에 대해서 먼저 설명해야겠다.

대부분의 소켓이 일반적으로 하나의 소켓과 연결해서 사용하는 경우가 많다. 즉 sendto recvfrom함수를 사용한다고 하더라도 일반적으로 같은 주소로 계속 보내는 경우가 많다는 것이다. 하지만 위의 두 함수는 항시 주소값을 내부에서 체크하여 데이터를 전송한다. 그리고 이러한 과정은 지속적인 데이터 처리를 한다는 것이고 당연히 함수의 연산이 많아진다는 것을 의미한다.


그렇다면 애초에 UDP소켓을 하나의 주소로 연결시켜 놓으면 그 과정을 단축시킬 수 있게 된다.

소켓에 주소를 연결시키는 방식은 의외로 간단하다.

UDP소켓으로 커넥트함수를 호출하여 주소값으로 커넥트 시켜주면 이는 connected UDP소켓이 된다.

반대의 의미로 연결되지 않은 소켓은 unconnected UDP 소켓으로 불린다.


이렇게 연결된 소켓은 이미 연결이 완료되었기 때문에

send함수와 recv함수만 사용해서 데이터 송수신이 가능하다.




Posted by JJOREG

데이터 입출력의 이론적인 내용 ---------------------------------------------------------------------------------------------------------------------------




호스트와 호스트간의 연결에서 입출력의 시점은 언제일까?

read()읽는 함수와 write()는 양쪽에서 서로 데이터를 전달받는 함수다. 그렇다면 데이터 전송은 과연 어느때 일어날까?

위의 그림을 보면서 아래의 내용을 비교해보면 좀더 이해가 편할 것이다.


1. A가 Write()함수를 호출

2. A의 출력 버퍼에 Write()에 인자로 들어간 데이터가 저장된다.

3. A의 출력 버퍼는 대기하고 있는데.

4. B가 Read()함수를 호출한다. -> 동시에 A의 출력버퍼에 나 읽을 준비가 있어! 라고 신호를 보낸다.

5. A의 출력 버퍼가 신호를 받고 데이터를 전송한다.

6. B의 입력버퍼가 그 데이터를 받아들인다.

7. 입출력 반복.


의 과정을 거친다.


Write의 함수가 반환되는 시점 ----------------------------------------------------------------------------------------

위에서 설명했던 것을 기억한다면 데이터 전송 함수가 반환하는 시점은 전송이 완료된 시점이다. 그렇다면 전송이 완료된 시점이란 어느때 일까?

일반적으로 출력버퍼에 데이터가 모두 올라가게 되면 전송이 완료된 시점이라고 생각할수 있겠지만(UDP같은 경우) TCP의 기본 개념은 A와 B가 지속적인 연결상태에 있는 것을 의미한다. 즉 지속적으로 연결된 상태에서 전송이완료된 시점이란 출력버퍼에 있는 데이터가 모두 전송된 상태 즉 출력 버퍼가 완전히 비어있는 상태를 의미한다고 보면 된다.


입출력 버퍼 ------------------------------------------------------------------------------------------------------

입출력 버퍼는 다음과 같이 생성된다.

1. 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재한다.
2. 입출력 버퍼는 소켓생성시 자동으로 생성된다.
3. 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이루어진다.
4. 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸되어 버린다.


TCP의 내부 동작 원리 ----------------------------------------------------------------------------------------------

TCP의 생성부터 소멸까지의 과정은 다음을 거친다.

1. 상대 소켓과의 연결

2. 상대 소켓과의 데이터 송수신.

3. 상대 소켓과의 연결종료.

위의 세가지 과정에서 TCP 프로토콜에서는 모두 신호를 보내준다.

TCP 프로토콜은 서로간의 데이터에 대한 신호를 보냄으로 해서 신뢰성을 획득한다고 설명했다. 즉 신호가 잘 갔는지 아니면 못갔는지 내가 접속 요청을 했는데 받아줬는지 들어가도 되는지. 내가 종료할건데 괜찮은지. 

등등을 모두 신호로서 서로간에 전송하고 확인해야만 그 과정들이 이루어 지게 된다. 


Three way handshaking -------------------------------------------------------------------------------------------

위에서 말했던 과정들을 다음과 같이 표현된다. TCP기반의 송수신에서는 송수신 전에 서로 신호를 보내는 과정이 있다.

쓰리 웨이 핸드쉐이킹이라는 과정이며 요약해서 설명하자면 다음과 같다.


Sender 내가 신호를 보내는데 이름이 1200신호고 접속을 요청한다. 승인된다면 1201신호를 보내달라.

Receiver 알았다 잘 받았다 여기 이름이 4800인 신호로 접속승인신호 1201을 보낸다 다음에는 4801로 신호를 보내라.

Sender 오케이 아까 내가 1200으로 신호 보냈으니 이번에는 1201로 신호 보낸다. 아까 요청했던 4801신호를 보낸다. 다음에는 1202로 보내라 알겠지?

같은 과정에 의해서 데이터가 입출력 된다.


TCP의 신호의 종류 ----------------------------------------------------------------------------------------------


위와 같은 신호를 통해서 TCP는 주기적으로 연락을 하고 만약 신호를 수신했다는 신호가 오지 않으면 지속적으로 같은 신호를 보내서 상대가 받았는지 받지 않았는지 확인합니다.

Posted by JJOREG

서버의 생성 -------------------------------------------------------------------------------------------------------


단계 1 SOCKET socket(_In_  int af,_In_  int type,_In_  int protocol); 서버의 소켓을 생성한다 -----------------------


단계 2 int bind(_In_  SOCKET s, _In_  const struct sockaddr *name, _In_  int namelen); 소켓에 주소를할당 ----------


여기까지는 앞서 자세히 설명했던 내용이다. 다음부터가 실제 서버의 기능을 실행해주는 함수이다.


단계 3 int listen(_In_ SOCKET s, _In_ int backlog ); 클라이언트의 연결 요청을 받아줄 소켓을 할당한다 -----------------

int listen(

  _In_  SOCKET s, -> 클라이언트의 연결 요청을 받아줄 소켓인자나 파일 디스크럽터 호출.

  _In_  int backlog -> 연결 요청을 최대 몇명까지 대기하며 받아줄지에 대한 인자 값 (연결요청은 Queue로 처리된다)

);

listen 함수는 연결 요청을 대기해주는 함수이다. 단순하게 이야기 한다면 다음과 같다.

클라이언트가 서버에 연결을 원한다면 어떠한 신호를 서버에 보낸다고 생각해보자 이는 소켓과 소켓의 통신이라고 볼수 있다.

그를 위해서 하나의 소켓을 연결을 요청하는 클라이언트의 메세지를 받기위한 함수로 할당하는 것을 의미한다.

즉 매표소의 직원으로 손님이 오면 손님에게 표를 나눠주는 역할을 하는 소켓을 할당하는 함수이다.


* 여기에서 두번째 backlog는 동시에 연결요청이 왔을때 몇명까지 한번에 처리할 것인지 그 개수를 정하는 함수이다. 일반적으로 이 함수는 번잡한 서버일 경우 15정도의 인자를 준다고 한다.

* 이 함수를 통해서 클라이언트들은 Queue(자료구조를 생각하면 된다)에 의해서 줄을 서게된다. 그리고 한명한명씩 처리가된 클라이언트들이 실제적으로 서버에 입장을 하게 된다고 생각하면 편하다.


SOCKET accept( _In_ SOCKET s, _Out_ struct sockaddr *addr, _Inout_ int *addrlen ); 접속한 클라이언트의 담당 소켓 생성 --

SOCKET accept( 

 _In_     SOCKET s, -> 접속승인된 클라이언트를 담당할 파일디스크럽터나 소켓을 생성 

 _Out_    struct sockaddr *addr, -> 연결 요청된 클라이언트의 주소 정보를 담을 구조체 

 _Inout_  int *addrlen -> 두번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달하는 함수

                                  함수호출이 완료되면 크기정보로 채워져 있던 변수에 클라이언트의 주소정보가 바이트단위로 계산되어 채워진다. 

);


 일단 소켓과 소켓의 데이터송수신은 1vs1이라고 생각하자 listen함수의 소켓은 문지기라고 설명했다. 매표소 직원이 가계안에 들어온 손님의 서빙이나 기타잡무까지 같이 처리하려면 너무 바쁠 것이다. 이 가계는 손님이 들어오면 담당직원을 한명씩 붙여주는 구조이다.

  accept함수는 서버에 입장한 클라이언트와 직접적으로 데이터송수신을 할 소켓을 생성해주는 함수이다. 이 새로 생성된 소켓을 통해서 클라이언트와 실질적인 데이터 송수신을 하게 된다.

여기까지가 서버의 생성과 클라이언트의 접속을 허용하고 클라이언트를 받아들이는 서버의 생성단계였다. 이후 본격적인 데이터 송수신과 read write (리눅스)혹은 rend recv 함수를 통한 데이터 송수신은 추후 다시 다룬다.


클라이언트의 생성 -------------------------------------------------------------------------------------------------

단계 1 SOCKET socket(_In_ int af,_In_ int type,_In_ int protocol); 클라이언트의 소켓을 생성한다 -----------------


단계 2 int connect(_In_  SOCKET s, _In_  const struct sockaddr *name, _In_  int namelen); ------------------------

int connect(

_In_ SOCKET s, -> 클라이언트 소켓의 파일디스크립터나 소켓 전달. _In_ const struct sockaddr *name, 연결요청 할 서버의 주소정보를 담은 변수의 주소 값 전달. _In_ int namelen sockaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달 );


listen함수에 대해서 기억한다면 listen함수는 클라이언트의 연결신호를 기다린다고 했다. 바로 이 함수가 클라이언트에 연결신호를 보내주는 함수이다. 일단 이 함수가 호출되면 다음의 상황이 와야 함수의 종료루틴이 호출된다.

1. 서버가 연결요청을 허락해주거나.

2. 네트워크 단절 등 오류상황이 발생하여 연결요청이 중단되거나.


* 클라이언트는 Bind함수를 이용할 필요가 없나?

서버를 구현하면서 반드시 거쳤던 과정 중 하나가 서버 소켓에 IP와 PORT를 할당하는 것이었다. connect함수는 자동으로 클라이언트의 소켓에 IP와 PORT가 할당된다. 따라서 클라이언트 프로그램을 구현할때는 BIND 함수를 명시적으로 호출할 필요가 없다.



연결의 과정 생성 ---------------------------------------------------------------------------------------------------

위의 그림을 보면 서버와 클라이언트의 생성 과정과 그 사이에 대한 이해가 될것이다. 서버가 생성되고 listen함수에 의해서 대기 상태에 들어가야 클라이언트가 connect함수를 통해서 연결이 가능해지는 구조가 눈에 보일 것이다.

Posted by JJOREG
이전버튼 1 2 이전버튼