• Protocol Buffers

    : 하나의 언어만으로 서버를 구성하는 것은 한계가 있다. 특히, java와 C#등으로 패킷을 만드는 것은 굉장히 불편하다.
    : 이런 언어적인 차이를 해결해주는 구글의 오픈소스 Protocol Buffers가 있다. 
    : 프로토콜 자체를 언어 독립적으로 구현할 수 있다.

    : node.js에는 npm
    https://developers.google.com/protocol-buffers/


  • Snappy
    : 패킷을 만드는 것 만큼 패킷의 양을 줄이는 것도 중요하다. 특히 모바일에서는 패킷의 양을 줄이는데 신경써야한다.
    : 패킷을 줄이려면 압축과 압축해제를 해야하는데 이 과정에서 속도도 중요하다.
    : 구글 오픈소스 Snappy
    - https://google.github.io/snappy/


  • IOCP
    Input/Ouptput Completion Port
    : Proactor 방식의 고성능 I/O Notification Model로 asynchronous I/O 지원
    : Windows OS가 직접 효율적인 스레드 풀링 제공으로 context switching을 줄임
    : Overlapped I/O를 확장 시킨 개념으로 커널영역과 유저영역의 버퍼 공유 (memory page-locking)

     

    - 기본 동작 구조 (출처: http://www.slideshare.net/sm9kr/windows-registered-io-rio)
     : I/O initiation -> I/O processing -> I/O completion





    - 1_IOCP
     : worker thread 도입

     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #pragma comment(lib, "ws2_32.lib") 
    
    #pragma comment(linker, "/subsystem:console")
    
    // Overlapped 작업의 종료를 대기할 스레드
    DWORD __stdcall EchoThread( void* p )
    {
    	// 여기서 비동기작업의 종료를 대기하면 됩니다.
    	// 즉 WSAWait....()
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    	//-------------------------------------------
    	// 1. Overlapped io를 위한 소켓 생성
    	// 표준 C 네트워크 함수 : 소문자()
    	// WSAxxxx() 함수들 : windows socket 2.0 부터 지원되는
    	//					windows만의 개념들..
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    								
    
    
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr; 
    
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    
    	listen(listen_sock,  5); 
    
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    
    
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// Overlapped로 수신 하는 코드
    		WSAEVENT ev = WSACreateEvent();
    		WSAOVERLAPPED ov = {0};
    		ov.hEvent = ev;
    
    		// 수신 버퍼..
    		char s[1024] = {0};
    		WSABUF buf;
    		buf.buf = s;
    		buf.len = 1024;
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &buf, 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 &ov, // overlapped 구조체
    						 0);
    		// 새로운 스레드를 생성해서 비동기 작업의 완료를 대기한다.
    		CreateThread( 0, 0, EchoThread, (void*)ev, 0, 0);
    	}
    
    
    	closesocket( link_sock);
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 2_IOCP
     : worker thread 구현
     : IOCP 생성
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #pragma comment(lib, "ws2_32.lib")
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		// IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								cnt++,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// Overlapped로 수신 하는 코드
    		WSAEVENT ev = WSACreateEvent();
    		WSAOVERLAPPED ov = {0};
    		ov.hEvent = ev;
    
    		// 수신 버퍼..
    		char s[1024] = {0};
    		WSABUF buf;
    		buf.buf = s;
    		buf.len = 1024;
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &buf, 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 &ov, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 3_IOCP
     : overlapped 구조체 확장 -> 입출력당 1개의 자료 구조
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #include <stdlib.h>  
    #pragma comment(lib, "ws2_32.lib")
    
    #define READ_MODE  1
    #define WRITE_MODE 2
    
    // OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
    // 입출력당 1개의 자료 구조
    struct IO_DATA
    {
    	WSAOVERLAPPED ov;
    	WSABUF        wsaBuf;
    	char          buff[1024];
    	int           mode;
    };
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		// IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    
    		IO_DATA* ioData = (IO_DATA*)pov;
    
    		printf("수신된 data : %s\n", ioData->buff);
    
    		CloseHandle( ioData->ov.hEvent);
    		free(ioData);
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								cnt++,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// IO작업당 아래 구조체를 만들어서 사용한다.
    		IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
    
    		memset(ioData, 0, sizeof(IO_DATA));
    
    
    		ioData->wsaBuf.buf = ioData->buff;
    		ioData->wsaBuf.len = 1024;
    		ioData->mode = READ_MODE;
    		ioData->ov.hEvent = WSACreateEvent();
    
    
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 (WSAOVERLAPPED*)ioData, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 4_IOCP
     : 소켓 구조체 구현 
     : write 구현 -> WSASend 도 완료되면 GetQueuedCompletionStatus로 통보된다.
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>
    #include <stdlib.h>  
    #pragma comment(lib, "ws2_32.lib") 
    
    #define READ_MODE  1
    #define WRITE_MODE 2
    
    // 클라이언트와 연결된 소켓정보를 관리하는 구조체
    struct SOCKET_DATA
    {
    	int sock;
    	SOCKADDR_IN addr;
    };
    
    // OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
    // 입출력당 1개의 자료 구조
    struct IO_DATA
    {
    	WSAOVERLAPPED ov;
    	WSABUF        wsaBuf;
    	char          buff[1024];
    	int           mode;
    };
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    
    		SOCKET_DATA* pSock = (SOCKET_DATA*)key;
    
    		IO_DATA* ioData = (IO_DATA*)pov;
    
    		if ( ioData->mode == READ_MODE )
    		{
    			printf("수신된 data : %s\n", ioData->buff);
    
    			// 수신에 사용한 버퍼를 사용해서 송신한다.
    			// 수신중에 overlapped 구조체의 내용은 변경되어 있게됩니다
    			memset( &(ioData->ov), 0, sizeof(WSAOVERLAPPED));
    
    			strcat( ioData->buff, " from server");
    			ioData->mode = WRITE_MODE;
    			//-----------------------------------
    			WSASend( pSock->sock, 
    				 &(ioData->wsaBuf), 1,
    				 &bytes, 0, &(ioData->ov), 0);
    			//-----------------------------
    		}
    		else
    		{
    			closesocket(pSock->sock);
    			free( pSock	);
    			CloseHandle( ioData->ov.hEvent);
    			free(ioData);
    		}
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    
    		// 소켓당 하나의 구조체
    		SOCKET_DATA* pSock = (SOCKET_DATA*)malloc(
    									sizeof(SOCKET_DATA));
    		pSock->sock = link_sock;
    		pSock->addr = addr2;
    
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								(ULONG_PTR)pSock,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// IO작업당 아래 구조체를 만들어서 사용한다.
    		IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
    
    		memset(ioData, 0, sizeof(IO_DATA));
    
    
    		ioData->wsaBuf.buf = ioData->buff;
    		ioData->wsaBuf.len = 1024;
    		ioData->mode = READ_MODE;
    		ioData->ov.hEvent = WSACreateEvent();
    
    
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 (WSAOVERLAPPED*)ioData, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }


    - PAGE_LOCKING
    (참고: 
    http://www.slideshare.net/sm9kr/windows-registered-io-rio,
    http://ozt88.tistory.com/26)
     
     : 완벽한 통지모델로 보이는 IOCP에도 문제는 있다.
     : 하나의 I/O operation마다 버퍼 영역에 대한 page-lock/unlock
      -> 특정 메모리에 대한 pin/unpin은 많은 CPU cycle 요구
      -> 그래서 RECV를 posting 할 때, page-locking을 피하여 CPU cycle을 줄이기 위해 zero-byte recv로 최소화
     : 하나의 I/O operation마다 시스템콜 호출
      -> 유저모드-커널모드 전환 발생


     ■ Zero Byte Recv?
      : 실제 수행을 시작하는 때를 감지할 수 있다면 이 문제를 해결할 수 있다.
      : 비동기 작업을 명령하기 전에 먼저 0바이트를 읽는 Recv작업을 요청하는 것이다. 0바이트 읽는 작업이므로 필요한 버퍼도 0바이트, 그러므로 PAGE_LOCKING이 발생하지 않는다. 비동기 명령 프로세서를 사용하기 때문에 이 명령이 요청된 시점에는 작업이 바로 가능한지 불확실하지만, 이 명령이 완료된 시점에는 다음 작업을 바로 시작할 수 있을 가능성이 매우 높다. 그러니까 이때부터 본격적인(큰 용량의 버퍼를 사용하는) 작업을 요청하는 것이다. 이렇게 하면 최대한 쓸데없이 LOCKING된 메모리를 줄일 수 있다.

     ■ SO_RCVBUF 옵션
      이 옵션은 소켓 버퍼의 크기를 설정하는 옵션이다. 이 옵션을 사용하여 버퍼 크기를 0으로 설정하는 경우, 커널이 따로 소켓버퍼(send, recv 버퍼)를 사용하지않고 직접 유저가 설정한 버퍼(메모리)에 직접 I/O를 때려박는다. 필자는 Memory-mapped file같은 느낌이라고 이해하고 있다. 0으로 설정하면 커널 버퍼를 거치지 않고 직접 유저의 버퍼에 데이터가 저장되기 때문에, 불필요해 보이는 복사가 수행되지 않아서 성능상에 이점이 있다. 위에서 살짝 언급한것처럼 가상메모리의 영역이 하드웨어 메모리 영역과 깊은 커플링을 맺게되면서, PAGE_LOCKING의 원인이 된다. 그렇다면 유저는 복사를 하지 않는 성능 이점과 PAGE_LOCKING의 이슈를 저울질 하여 옵션을 선택해야 할 것이다.



  • 이벤트 모델
    - 1_event
    // 현재 프로젝트는 GUI 프로그램이 아니라고 링커에게 알려주는것
    #pragma comment(linker, "/subsystem:console")
    #include <stdio.h>
    #include <Windows.h>
    #include <conio.h>
    
    // 윈도우 "event"개념은 linux 의 "conditional variable"가 거의
    // 유사합니다. 
    
    // 실제로 포팅할때 서로 대응되는 개념입니다. 
    
    
    DWORD __stdcall foo( void* p )
    {
    	HANDLE h = (HANDLE)p;
    
    	// 이벤트이 signal 상태 조사하기
    	DWORD ret = WaitForSingleObject( h, 0);
    
    	if ( ret == WAIT_OBJECT_0)
    		printf("시그널 상태(1) 입니다.\n");
    	else
    		printf("시그널이 아닙니다.\n");
    
    	// 시그널 될때까지 대기하는 방법
    	ret = WaitForSingleObject( h, INFINITE); // 시간(무한)
    
    	if ( ret == WAIT_OBJECT_0)
    		printf("시그널 상태(1) 입니다.\n");
    	else
    		printf("시그널이 아닙니다.\n");
    
    	return 0;
    }
    
    int main()
    {
    	HANDLE hEvent = CreateEvent( 0, 0, FALSE, // 초기 signal을
    								0);			  // 0으로
    
    	CreateThread( 0, 0, foo, (void*)hEvent, 0, 0);
    	getch();
    
    	SetEvent( hEvent); // event의 signal을 1로 변경한다.
    
    	getch();
    }
    


    - 2_event
      
    WSAAsyncSelect
       : non-blocking, 비동기 통지방식 (통지 방식으로 윈도우 메시지를 사용하므로 윈도우 프로시저에서만 사용할 수 있다.)
       : 커널에게 미리 등록만 해두면 유저는 따로 커널에게 확인하여 동기화하지 않더라도 알아서 메시지가 날아온다.
       : 내부적으로 따로 체크하는 것이 아니라 운영체제가 I/O 상황이 될때 인터럽트를 사용하는 방식으로 구현되기 때문에 운영체제 수준에서도 연산량이 많이 줄어든다. 대신 다른 운영체제에서 지원하지 않는 기능이기 때문에, 다른 구동환경에서 같은 프로세스를 사용할 수가 없다는 단점은 있다.

     ■  WSAEventSelect
       이 통지방식은 다소 애매한 구석이 있다. 우선 이벤트 오브젝트를 사용하여 signal을 체크한다는 점이 동기랑 비슷한 점이 있어보인다. 그리고 wait함수로 이벤트가 발생할 때까지 대기한다는점이 blocking같기도 하다. 하지만 select나 epoll처럼 유저가 리소스를 사용하여 체크하는 것이 아니라 Wait함수를 사용하여 이벤트가 발생할 때 활성화 된다는 점이 비동기 방식에 가깝다고 생각한다.
       :
    WSAWaitForMultipleEvent() 함수에서 timeout 옵션이 있기 때문에 select나 epoll 처럼 어떻게 사용하느냐에 따라 blocking 방식으로 사용할 수도 non-blocking 방식으로 사용할 수도 있다.
       : 하지만 다른 점이 있다면, blocking 방식을 사용해도 멀티플렉싱이 가능하다는 점이다. 하나의 Wait에서 여러개의 I/O를 동시에 감지하고 있기 때문에, 쓰레드 하나만 wait를 통해 blocking한 상태에서 대기시키면 멀티플렉싱이 가능하다. 결과적으로는 Blocking 이면서 Non-Blocking이기도 하다.

    #pragma comment(linker,"/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h>
    #include <Windows.h>
    #include <conio.h>
    #pragma comment(lib, "ws2_32.lib") 
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    
    	int listen_sock = socket( PF_INET,  
    						SOCK_STREAM, 
    						0);
    
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    	listen(listen_sock,  5); 
    	//------------------
    	WSAEVENT ev = WSACreateEvent(); // CreateEvent()의 네트워크
    									// 버전.
    	// 소켓을 비동기 소켓으로 변경한다.
    	WSAEventSelect( listen_sock, ev, FD_ACCEPT);
    	//-----------------------------
    
    	// 모든 소켓 핸들과 이벤트 핸들을 배열에 보관해야 합니다.
    	int sockArr[256] = { listen_sock };
    	WSAEVENT evArr[256] = { ev };
    
    	int cnt = 1;
    
    	while( 1 )
    	{
    		int pos = WSAWaitForMultipleEvents( cnt, evArr,  // event배열이름
    						FALSE, // 하나라도 signal 되면
    						WSA_INFINITE,0);
    
    
    		// 배열의 몇번째 요소인지 조사한다
    		int idx = pos - WSA_WAIT_EVENT_0;
    
    		printf("%d 번째 이벤트가 signal\n", idx);
    
    
    		WSANETWORKEVENTS netEv;
    		WSAEnumNetworkEvents( sockArr[idx], evArr[idx], &netEv);
    
    		
    		if ( netEv.lNetworkEvents & FD_READ )
    		{
    			char s[1024] = {0};
    			int n = recv( sockArr[idx], s, 1024, 0);
    			printf("도착한 data : %s\n", s);
    			strcat( s, " from Server");
    			send( sockArr[idx], s, 1024, 0);
    
    		}
    
    
    		if ( netEv.lNetworkEvents & FD_CLOSE )
    		{
    			closesocket( sockArr[idx]);
    			WSACloseEvent( evArr[idx]);
    
    			// 배열에서 제거 해야 합니다.
    			sockArr[idx] = sockArr[cnt-1];
    			evArr[idx] = evArr[cnt-1];
    
    			--cnt;
    			printf("Client 접속 끊어짐\n");
    			continue;
    		}
    
    
    
    
    		if ( netEv.lNetworkEvents & FD_ACCEPT)
    		{
    			printf("접속요청\n");
    
    
    			SOCKADDR_IN caddr;
    			int sz = sizeof caddr;
    
    			int link_sock = accept( listen_sock, 
    							(SOCKADDR*)&caddr, &sz);
    
    			WSAEVENT ev2 = WSACreateEvent();
    			WSAEventSelect( link_sock, ev2, 
    								FD_READ | FD_CLOSE);
    			
    			sockArr[cnt] = link_sock;
    			evArr[cnt] = ev2;
    			++cnt;
    
    			printf("클라이언트 접속 : %s\n", 
    						inet_ntoa( caddr.sin_addr));
    		}
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

  • Overlapped IO
    : 중첩 입출력 모델
    : non-blocking, asynchronous I/O (비동기적 완료 통보)
    : 데이터 입출력이 진행되는 동안에도 다른일을 할 수 있다.
    : 원리는 리눅스의 I/O multiplexing, RTS과 비슷하다.

     
    : 리눅스의 I/O multiplexing, RTS와 다른점은 모드 변환이 발생하지 않는다. (user <-> kernel 여러번 데이터 복사 x)


    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    
    #pragma comment(lib, "ws2_32.lib") 
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    	//-------------------------------------------
    	// 1. Overlapped io를 위한 소켓 생성
    	// 표준 C 네트워크 함수 : 소문자()
    	// WSAxxxx() 함수들 : windows socket 2.0 부터 지원되는
    	//					windows만의 개념들..
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    								
    
    
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr; 
    
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    
    
    	listen(listen_sock,  5); 
    	struct sockaddr_in addr2;
    	int sz = sizeof addr2;
    
    
    	int link_sock = accept( listen_sock, 
    						  (struct sockaddr*)&addr2, &sz);
    
    	printf("클라이언트가 접속되었습니다\n");
    
    
    	closesocket(listen_sock); // 대기 소켓은 이제 필요없다.
    
    	// Overlapped로 수신 하는 코드
    	WSAEVENT ev = WSACreateEvent();
    	WSAOVERLAPPED ov = {0};
    	ov.hEvent = ev;
    
    
    	// 수신 버퍼..
    	char s[1024] = {0};
    	WSABUF buf;
    	buf.buf = s;
    	buf.len = 1024;
    
    	DWORD flag = 0;
    	DWORD recvBytes = 0;
    
    	int n = WSARecv( link_sock,
    					 &buf, 1,  // 버퍼와 버퍼 갯수
    					 &recvBytes, // 받을 data 크기
    					 &flag,
    					 &ov, // overlapped 구조체
    					 0);
    
    	if ( n == SOCKET_ERROR ) // -1 
    	{
    		if ( WSAGetLastError() == WSA_IO_PENDING )
    		{
    			printf("data 를 수신하고 있는 중입니다.\n");
    
    			// 비동기의 종료를 대기 해야 합니다.
    			// 비동기 IO의 종료시에는 overlapped구조체 안에 있는
    			// event가 signal 됩니다.
    			WSAWaitForMultipleEvents( 1, &ev, TRUE, 
    								WSA_INFINITE, 0);
    
    			printf("비동기 IO로 수신 완료\n");
    		}
    	}
    	else 
    		printf("data를 동기적으로 수신 : %d\n", n); 
    
    	printf("수신된 data : %s\n", buf.buf);
    
    	closesocket( link_sock);
    	//------------------------------
    	WSACleanup(); 
    }

참고: http://ozt88.tistory.com/22

  • Server
    - 1_server ~ 3_server
     
    #define WIN32_LEAN_AND_MEAN
    #include <stdio.h>
    
    #include <Windows.h>
    
    #include <WinSock2.h>
    
    #pragma comment (lib, "ws2_32.lib")
    
    int main()
    {
    	WSADATA w;
    	WSAStartup(MAKEWORD(2, 2), &w);
    	//-----------------------------------
    
    	// 1. 소켓 생성
    	SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr = { 0, };
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY;
    
    	bind(sock, (SOCKADDR*)&addr, sizeof addr);
    
    	// 3. 소켓을 대기 상태로, 5라는 arg는 무시 됨
    	listen(sock, 5);
    
    	// 4. 클라이언트 요청을 수락
    	SOCKADDR_IN caddr = { 0, };
    	int sz = sizeof(caddr);
    
    	SOCKET csock = accept(sock, (SOCKADDR*)&caddr, &sz);
    
    	printf("클라이언트가 접속되었습니다.\n");
    
    
    	char buf[1024];
    	// 핵심 : read() 를 통해 수신하지 않습니다. 리턴값이 가장 중요하다.
    	int ret;
    	while (1) {
    		ret = recv(csock, buf, 1024, 0);
    		if (ret == 0) {
    			printf("연결이 정상적으로 종료되었습니다.\n");
    			break;
    		}
    		else if (ret == -1) {
    			printf("연결이 비정상적으로 종료되었습니다.\n");
    			break;
    		}
    
    		// ...
    		ret = send(csock, buf, ret, 0);
    		if (ret < 0)
    			break;
    	}
    
    	printf("클라이언트가 접속이 해지되었습니다.\n");
    
    	closesocket(sock);
    	closesocket(csock);
    
    	//-----------------------------------
    	WSACleanup();
    }
    

  • Client
    - 1_client
     : 초기 셋팅, 주의 사항

    // 1_client.cpp
    
    // 1. 윈도우즈의 모든 네트워크 관련 함수는
    //		winsock2.h에 있습니다. (헤더만)
    //		기존의 소켓 API를 확장한 형태
    // 2.모든 구현은 sw2_32.dll에 있습니다. 링킹이 필요
    // 3. Windows.h와 같이 사용할 경우 반드시 WinSock2.h를 먼저
    //	  포함해야 합니다.
    // 4. #define WIN32_LEAN_AND_MEAN
    // 5. 환경설정에서 링커 설정
    // 3~5 중 선택적으로 사용
    
    // Windows.h의 헤더에서 자주 사용하지 않는 것을 제외해달라.
    #define WIN32_LEAN_AND_MEAN
    
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib") // 링킹
    
    #include <Windows.h> // 모든 시스템 함수가 헤더파일에 선언되어 있습니다.
    
    int main()
    {
    	return 0;
    }
    

    - 2_client

    #include <stdio.h>
    #include <WinSock2.h>
    #include <Windows.h> 
    
    #pragma comment(lib, "ws2_32.lib")
    
    // 1. Network 함수를 사용하기 전에 WSAStartup을 통해
    //	라이브러리 초기화를 수행해야 한다.
    
    // 2. WSA : Windows Socket API
    // 3. WSACleanup() 을 통해 자원을 정리해야 한다.
    
    int main()
    {
    	// 찰스 시모니의 헝가리안 표기법 이해 필요, 변수명에 타입에 대한 정보가 나타남
    	// JAVA의 m~은? 안드로이드 표준
    	WSADATA w;
    	int ret = WSAStartup(MAKEWORD(2, 2), &w);
    	// 0x0202 word가 2byte
    
    	if(ret != 0)
    		printf("소켓 라이브러리를 사용할 수 없다.\n");
    
    	// w에 있는 정보는 현재까지 정확하지 않습니다. 사용되지 않습니다.
    	printf("최대 소켓 갯수: %d\n", w.iMaxSockets);
    	printf("소켓 버전: %d.%d\n", w.wHighVersion, w.wVersion);
    
    	// 소켓 라이브러리가 사용하는 자원을 정리
    	WSACleanup();
    }
    

    - 3_client
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <WinSock2.h>
    #include <Windows.h>
    
    #pragma comment(lib, "ws2_32.lib")
    
    #include <locale.h>
    
    int main()
    {
    	setlocale(LC_ALL, "korean"); // 유니코드 셋팅
    	
    	WSADATA w;
    	WSAStartup(MAKEWORD(2, 2), &w);
    	//--------------
    	// 1. 소켓 생성, 리눅스는 fd가 retrun, 윈도우는 SOCKET return
    	SOCKET sock = socket(PF_INET, SOCK_STREAM, 0); // int도 되지만 정확한 타입을 사용!
    
    	// 2. 서버 주소 지정
    	// struct sockaddr_in addr = { 0, };
    	SOCKADDR_IN addr = { 0, };
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(4000);
    	//addr.sin_port = htons(80);
    	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    	//addr.sin_addr.s_addr = inet_addr("203.249.22.240");
    
    	// 3. 서버 접속
    	int ret = connect(sock, (SOCKADDR*)&addr, sizeof addr);
    	if (ret == 0)
    		printf("접속 성공\n");
    	else {
    		int err = WSAGetLastError(); // 함수 호출하면 
    		// 1. WSAGetLastError() / 도구 -> 오류 조희 
    		printf("접속 실패 : %d\n", err);
    
    		// 2. WSAGetLastError() 오류 번호를 저장하지 않고 다른 함수를 호출하면 오류 번호가 초기화 되버린다. 
    		WCHAR buf[512];
    		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
    					  0, 
    					  err, 
    					  LANG_SYSTEM_DEFAULT,
    					  buf, 
    					  512, 0);
    		// L은 유니코드라는 표기
    		wprintf(L"에러 발생 : %s", buf);
    
    		// 3. Debug Mode - 조사식
    		// @err
    		// @err, hr
    
    		// 비정상종료 테스트, 자원해지(소멸자 등) 테스트
    		//Sleep(1000 * 10); 
    		//exit(0);
    	}
    
    	//--------------
    
    	closesocket(sock);	// close(sock);
    	
    	WSACleanup();
    }
    


  •  Event-Driven Server Architectures
    : alternative to synchronous blocking I/O
    the mapping of a single thread to multiple connections
    new events are queued and the thread executes a so-called event loop--dequeuing events from the queue, processing the event, then taking the next event or waiting for new events to be pushed.

    - thread-based vs. event-driven

    thread-basedevent-driven
    connection/request statethread contextstate machine/continuation
    main I/O modelsynchronous/blockingasynchronous/non-blocking
    activity flowthread-per-connectionevents and associated handlers
    primary scheduling strategypreemptive (OS)cooperative
    scheduling componentscheduler (OS)event loop
    calling semanticsblockingdispatching/awaiting events
    Table 4.2: Main differences between thread-based and event-driven server architectures.


  • Non-blocking I/O Multiplexing Patterns
    event-based I/O multiplexing

    - Reactor Pattern
     : 주로 리눅스의 구현
     : select, epoll, kqueue, Node.js, Ruby EventMachine, Scala Akka’s IO 모듈 등
     : synchronous, non-blocking I/O handling and relies on an event notification interface.
     : event나 socket 같은 리소스들의 집합을 등록해놓고, 각 리소스들을 콜백 또 후킹하는 적절한 event handler가 필요하다.
     : 핵심 구성요소인 synchronous event demultiplexer blocking event notification interface를 사용하여 리소스의 이벤트를 기다린다.
     : synchronous event demultiplexer가 받는 이벤트는 언제든지 dispatcher로 알리고, 다음 event를 기다린다.
     : dispatcher는 관련 event handler 선택하고, callback/hook execution을 triggering함으로써 event를 처리한다.
     : 리엑터 패턴은 event handling 과 multiplexing 분리한다. 이 패턴은 싱글 스레드를 사용하므로 blocking 동작은 전체적은 application의 동작을 지연시킬 수 있다. 그래서 다양한 Reactor Pattern들이 event handler에 대한 thread poll(thread 개수는 CPU 개수*2 + 1)을 사용한다.

    *장점
    : 리엑터 패턴은 리엑터 구현에서 애플리케이션에 관련된 코드가 완벽하게 분리됨. 그래서 애플리케이션 컴포넌트들이 모듈화되고 재사용 가능한 부분들로 분리될 수 있음. 시스템을 멀티스레드로 인한 복잡성에서 보호해 줌.

    *단점
    : 디버깅하기 어려움. 요청 핸들러를 동기적으로 호출하기 때문에 최대 동시성에 제한이 있음. 요청 핸들러 뿐만 아니라 디멀티플렉서에 의해서도 확장성이 제한됨. (OS 스케줄링 이용 불가, event queue에 대한 성능 이슈)




      ■ 1_epoll
       : select, poll의 문제점 해결

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>   // epoll api
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    // select, poll 문제점
    // 1) 관찰 대상 디스크립터의 정보를 매번 운영체제에 전달해야 한다.
    // 2) 1024 개 이상의 디스크립터를 등록하는 것이 불가능하다.
    
    // epoll
    // - 2.5.44 부터 사용 가능하다.
    // - $ cat /proc/sys/kernel/osrelease
    
    // epoll api 3가지
    // 1) epoll_create : 파일 디스크립터 저장소 생성
    // 2) epoll_ctl    : 디스크립터 등록 & 삭제
    // 3) epoll_wait   : 파일 디스크립터의 변화를 감지하는 함수
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    
    	// 1. 초기화
    	const int EPOLL_SIZE = 1024;
    	int efd = epoll_create(EPOLL_SIZE);
    	// 최신 커널에서는 사이즈는 무시됩니다.
    
    	// 2. 이벤트 등록
    	struct epoll_event events[EPOLL_SIZE];
    
    	struct epoll_event event;
    	event.events = EPOLLIN;
    	event.data.fd = sock;
    	epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event); 
    
    	while (1) 
    	{
    		int count = epoll_wait(efd, events, EPOLL_SIZE, -1);
    
    		for (int i = 0 ; i < count ; ++i)
    		{
    			if (events[i].data.fd == sock)
    			{
    				struct sockaddr_in caddr = {0, };
    				socklen_t clen = sizeof(caddr);
    				int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    				char* cip = inet_ntoa(caddr.sin_addr);
    				printf("Connected from %s\n", cip);
    
    				// 새로운 연결을 등록해주어야 한다.
    				event.events = EPOLLIN;
    				event.data.fd = csock;
    				epoll_ctl(efd, EPOLL_CTL_ADD, csock, &event); 
    			} 
    			else
    			{
    				int csock = events[i].data.fd;
    				char buf[1024];
    				int n = read(csock, buf, sizeof buf);
    
    				if (n <= 0)
    				{
    					printf("연결 종료!!\n");
    					close(csock);
    
    					// 종료된 디스크립터를 등록 해지해야 한다.
    					epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    				} 
    				else
    					write(csock, buf, n);
    			}
    		}
    	}
    
    	close(sock);
    }
    


      ■ 2_epoll
       : Level Triggering vs. Edge Triggering

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <errno.h>
    #include <fcntl.h>       // fcntl()
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>   // epoll api
    
    // epoll 이벤트 처리 방식
    // Level Triggering - Default
    // : 수신 버퍼에 데이터가 있다면, 이벤트가 발생
    // Edge Triggering
    // : 수신 버퍼에 데이터가 없다가, 존재하는 시점에 이벤트가 발생
    // 핵심 : 데이터를 처리하는 디스크립터를 Non-blokcing 모드로 설정해야 한다.
    
    
    // ET 장점
    // 1. 데이터를 처리하기 위해 커널에 진입하는 횟수가 적다.
    // 2. 데이터를 수신하는 로직과 데이터를 처리하는 로직을 분리할 수 있다.
    //   - 서버의 구조화에 유리하다.
    
    
    void setNonBlokcing(int fd)
    {
    	// 아래 코드는 문제점이 있습니다.
    	// fcntl(fd, F_SETFL, O_NONBLOCK);
    
    	// Edge Triggering은 연결에 대한 데이터가 들어오는 시점에서 발생하므로 다시 데이터가 올라올때까지 발생하지 않는다. 
    	// ex) aaaa -> aaaa, aaaab -> aaaa
    	// -> blocking이 생길 수 있다. -> read를 non-blocking 되도록 해야한다.
    	// -> Edge trigger + non-blocking -> fcntl
    
    	int flags = fcntl(fd, F_GETFL, 0);
    	flags |= O_NONBLOCK;
    
    	fcntl(fd, F_SETFL, flags);
    }
    
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    
    	// 1. 초기화
    	const int EPOLL_SIZE = 1024;
    	int efd = epoll_create(EPOLL_SIZE);
    	// 최신 커널에서는 사이즈는 무시됩니다.
    
    	// 2. 이벤트 등록
    	struct epoll_event events[EPOLL_SIZE];
    
    	struct epoll_event event;
    	event.events = EPOLLIN;
    	event.data.fd = sock;
    	epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event); 
    
    	while (1) 
    	{
    		int count = epoll_wait(efd, events, EPOLL_SIZE, -1);
    		printf("epoll_wait()\n");
    
    		for (int i = 0 ; i < count ; ++i)
    		{
    			if (events[i].data.fd == sock)
    			{
    				struct sockaddr_in caddr = {0, };
    				socklen_t clen = sizeof(caddr);
    				int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    				char* cip = inet_ntoa(caddr.sin_addr);
    				printf("Connected from %s\n", cip);
    
    				// Non-Blocking 으로 설정  
    				setNonBlokcing(csock);
    
    				// 새로운 연결을 등록해주어야 한다.
    				event.events = EPOLLIN | EPOLLET;
    				event.data.fd = csock;
    				epoll_ctl(efd, EPOLL_CTL_ADD, csock, &event); 
    			} 
    			else
    			{
    				int csock = events[i].data.fd;
    				// char buf[1024];
    				char buf[4];
    				while (1)
    				{
    					int n = read(csock, buf, sizeof buf);
    					if (n == 0)
    					{
    						printf("연결 종료!!\n");
    						close(csock);
    
    						// 종료된 디스크립터를 등록 해지해야 한다.
    						epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    					} 
    					else if (n == -1)
    					{
    						int err = errno;
    						if (err == EAGAIN)   // 데이터가 존재하지 않는다.
    							break;
    						else {
    							close(csock);
    
    							epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    						}
    
    					}
    					else {
    						printf("read() : %d\n", n);
    						write(csock, buf, n);
    					}
    				}
    			}
    		}
    	}
    
    	close(sock);
    }


    - Proactor Pattern
     : 주로 윈도우의 구현
     : POSIX AIO, Boost.Asio (C++), 
    Adaptive Communication Environment (C++), libbitcoin (C++), RJR (Ruby), win32 IOCP
     : asynchronous, non-blocking I/O operations
     : Reactor pattern의 asynchronous 방식으로 보기도 함
     : blocking event notification interfaces 대신에 completion events
     : proactive initiator는 main application thread로 
    asynchronous I/O 동작들을 초기화하는 역할
     : 동작이 이슈될때 항상 completion handler와 completion dispatcher에 등록된다.
     : 비동기적인 동작의 수행은 asynchronous operation processor의해 관장된다. 이 프로세스는 OS에 의해 구현된다. 
     : I/O 작업이 완료되었을때, completion dispatcher로 notifiy 되고, 다음으로 completion handler는 그 결과를 처리한다.

     




    - 1_blocking_queue
     : mutex는 커널단 컨텍스트 스위칭이므로 오버헤드가 큼 -> atomic operation

     
    #include <unistd.h>
    
    #include <mutex>
    #include <condition_variable> // Event
    #include <deque>
    
    #include <thread>
    #include <iostream>
    using namespace std;
    
    template <typename T>
    class blocking_queue
    {
    	deque<T> d_queue;
    	condition_variable d_condition;
    	mutex d_mutex;
    
    public:
    	void push(const T& value)
    	{
    		{
    			unique_lock<mutex> lock(d_mutex);
    			d_queue.push_front(value);		
    		}
    		d_condition.notify_one();
    	}
    	
    	T pop()
    	{
    		unique_lock<mutex> lock(d_mutex);
    		d_condition.wait(lock, [=] { return !d_queue.empty(); } );
    
    		T rc(std::move(d_queue.back())); // move fast
    		d_queue.pop_back();
    
    		return rc;
    	}
    };
    

    - 2_lockfree_queue
     : boost를 이용한 생산자 소비자 패턴
     
    #include <iostream>
    
    #include <boost/lockfree/queue.hpp>
    // h + cpp = hpp
    
    #include <boost/thread/thread.hpp>
    #include <boost/atomic.hpp>
    
    using namespace std;
    using namespace boost;
    
    atomic_int producer_count(0);
    atomic_int consumer_count(0);
    
    lockfree::queue<int> queue(128);
    
    const int iterations = 100000000;
    const int producer_thread_count = 4;
    const int consumer_thread_count = 4;
    
    void producer(void)
    {
    	for (int i = 0 ; i < iterations ; ++i) {
    		int value = ++producer_count;
    		while (!queue.push(value))
    			;
    	}
    }
    
    atomic<bool> done(false);
    void consumer(void)
    {
    	int value;
    	while (!done)
    	{
    		while (queue.pop(value))
    			++consumer_count;
    	}
    
    	while (queue.pop(value))
    		++consumer_count;
    }
    
    int main()
    {
    	cout << "queue is ";
    	if (!queue.is_lock_free())
    		cout << "not ";
    	cout << "lockfree" << endl;
    
    	thread_group producer_threads, consumer_threads;
    	for (int i = 0 ; i < producer_thread_count ; ++i)
    		producer_threads.create_thread(producer);
    
    	for (int i = 0 ; i < consumer_thread_count ; ++i)
    		consumer_threads.create_thread(consumer);
    
    	producer_threads.join_all();
    	done = true;
    
    	consumer_threads.join_all();
    
    	cout << "produced " << producer_count << endl;
    	cout << "consumed " << consumer_count << endl;
    


    - 4_asio_echo_server
     : block에 대한 개념이 존재하지 않음
     : boost asio에서 핵심은 io_service
     : 각각의 연산 자체가 비동기로 이루어짐

    #include <boost/asio.hpp>
    using namespace boost;
    
    #include <iostream>
    #include <memory>    // shared_ptr<>
    #include <utility>
    using namespace std;
    
    using boost::asio::ip::tcp;
    class Session : public std::enable_shared_from_this<Session>
    {
    public:
    	Session(tcp::socket s) : socket(std::move(s)) {}
    
    	void start() { doRead(); }
    
    	void doRead() {
    		auto self(shared_from_this());  // 참조 계수 증가, self를 참조안하면 객체가 바로 파괴되버림
    		// 1. async_read      : readn
    		// 2. async_read_some : read
    
    		socket.async_read_some(
    				asio::buffer(data, 1024),
    				[this, self](system::error_code ec, size_t length) {
    				
    				if (!ec) 
    					doWrite(length);
    		});
    	}
    
    	void doWrite(size_t length)
    	{
    		auto self(shared_from_this());
    
    		asio::async_write(
    				socket, asio::buffer(data, length),
    				[this, self](system::error_code ec, size_t) {
    					if (!ec)
    						doRead();
    				});
    	}
    
    
    private:
    	tcp::socket socket;
    	char data[1024];
    };
    
    
    class Server
    {
    public:
    	Server(asio::io_service& io, short port)
    		: acceptor(io, tcp::endpoint(tcp::v4(), port)),
    			socket(io) {
    		
    		doAccept();
    	}
    
    	void doAccept()
    	{
    		acceptor.async_accept(socket, [this](system::error_code ec) {
    			// 클라이언트의 접속 요청이 완료된 시점에 호출되는 핸들러	
    			if (!ec) {
    				// socket...
    				make_shared<Session>(std::move(socket))->start();
    			}
    		});
    
    	}
    
    private:
    	tcp::acceptor acceptor;
    	tcp::socket socket;
    	
    };
    
    
    int main()
    {
    	try {
    		asio::io_service io_service;
    		Server s(io_service, 4000);
    		io_service.run();
    
    	} catch (std::exception& e) {
    		cerr << e.what() << endl;
    	}
    }

  • Reactor, Proactor 간단한 도식화
    - Reactor


    - Proactor




참고: http://berb.github.io/diploma-thesis/original/042_serverarch.html, http://ozt88.tistory.com/25

  • I/O multiplexing
    : synchronous, Blocking I/O 형태의 멀티 프로세스 서버, 멀티 스레드 서버 모두 고성능 서버로는 부적합하다.
    : 클라이언트마다 프로세스나 스레드를 할당하면 Context Switching 문제로 메모리적인 문제가 발생한다.
    -> 멀티플렉싱 개념 필요

    - poll
      ■ 1_poll
       : mkfifo fifo 파이프 만들기
       : IPC 전용 메커니즘
       : cat > myfifo로 리다이렉션

    #include <unistd.h>
    
    #include <stdio.h>
    #include <fcntl.h>
    
    int main()
    {
    	char buff[100];
    	int ret;
    
    	int fd = open("myfifo", O_RDWR);
    
    	while (1)
    	{
    		ret = read(0, buff, sizeof buff);
    		buff[ret] = '\0';
    		printf("Keyboard : %s\n", buff);
    
    		ret = read(fd, buff, sizeof buff);
    		buff[ret] = '\0';
    		printf("myfifo : %s\n", buff);
    	}
    }
    

      ■ 2_poll
    #include <unistd.h>
    
    #include <stdio.h>
    #include <fcntl.h>
    
    #include <poll.h>
    
    int main()
    {
    	char buff[100];
    	int ret;
    
    	int fd = open("myfifo", O_RDWR);
    
    
    	// 비동기적으로 이벤트를 처리할 디스크립터 배열
    	struct pollfd fds[2];
    
    	while (1)
    	{
    		fds[0].fd = 0;
    		fds[0].events = POLLIN;  // 관심 있는 이벤트의 종류
    														 // read  : POLLIN
    														 // write : POLLOUT
    
    		fds[1].fd = fd;
    		fds[1].events = POLLIN;
    
    
    		poll(fds, 2, -1);
    
    
    		// 주의할 점 : 이벤트가 동시에 발생할 수 있으므로
    		//             절대 else 로 묶으면 안된다.
    		if (fds[0].revents & POLLIN) 
    		{
    			ret = read(0, buff, sizeof buff);
    			buff[ret] = '\0';
    			printf("Keyboard : %s\n", buff);
    		}
    
    		if (fds[1].revents & POLLIN)
    		{
    			ret = read(fd, buff, sizeof buff);
    			buff[ret] = '\0';
    			printf("myfifo : %s\n", buff);
    		}
    	}
    }
    


    - select (참고: http://ozt88.tistory.com/21)
     : select는 싱글스레드로 다중 I/O를 처리하는 멀티플렉싱 통지모델의 가장 대표적인 방법이다.
     : 해당 파일 디스크립터가 I/O를 할 준비가 되었는지 알 수 있다면, 그 파일 디스크립터가 할당받은 커널 Buffer에 데이터를 복사해주기만 하면 된다. 이런 목적하에 통지모델은 파일디스크립터의 상황을 파악할 수 있게 하는 기능을 할 수 있어야한다. select는 많은 파일 디스크립터들을 한꺼번에 관찰하는 FD_SET 구조체를 사용하여 빠르고 간편하게 유저에게 파일 디스크립터의 상황을 알려준다.
     : select를 사용해서 I/O의 상황을 알기 위해서는 프로세스가 커널에게 직접 상황 체크를 요청해야한다. 프로세스가 커널의 상황을 지속적으로 확인하고 그에 맞는 대응을 하는 형태로 구성되기 때문에 프로세스와 커널이 서로 동기화된 상태에서 정보를 주고 받는 형태로 볼 수 있다. 따라서 select의 통지형태를 동기형 통지방식이라 부를 수 있다. 그리고 select 그 자체는 I/O를 담당하지 않지만, 통지하는 함수의 호출방식이 timeout에 따라 non-blocking 또는 blocking 형태가 된다. timeout을 설정하지 않으면, 관찰 대상이 변경되지 않는 이상 반환되지 않으므로 blocking 함수가 되고, timeout이 설정되면 주어진 시간이 지나면 시간이 다되었다는 정보를 반환하므로 non-blocking 함수가 된다. 

    #include  <stdio.h>
    
    #include  <unistd.h>
    #include  <sys/types.h>
    #include  <sys/select.h>
    #include  <sys/socket.h>
    
    #include  <arpa/inet.h>
    #include  <netinet/in.h>
    
    inline int max(int a, int b)
    {
    	return a > b ? a : b;
    }
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	// fd_set : bit array
    	fd_set socks;					// source data
    	fd_set readsocks;
    
    	FD_ZERO(&socks);
    	FD_SET(sock, &socks);
    
    	int maxfds = sock;
    
    	while (1) 
    	{
    		readsocks = socks;
    
    		int ret = select(maxfds + 1, &readsocks, 0, 0, 0);
    		printf("ret : %d\n", ret);
    
    		for (int i = 0 ; i < maxfds + 1 ; ++i)
    		{
    			if (FD_ISSET(i, &readsocks))
    			{
    				if (i == sock)
    				{
    					struct sockaddr_in caddr = {0, };
    					socklen_t clen = sizeof(caddr);
    					int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    					char* cip = inet_ntoa(caddr.sin_addr);
    					printf("Connected from %s\n", cip);
    
    					// 새로운 연결을 등록해주어야 한다.
    					FD_SET(csock, &socks); 
    					maxfds = max(maxfds, csock);
    				}
    				else
    				{
    					int csock = i;
    					char buf[1024];
    					int n = read(csock, buf, sizeof buf);
    					
    					if (n <= 0)
    					{
    						printf("연결 종료!!\n");
    						close(csock);
    
    						// 종료된 디스크립터를 등록 해지해야 한다.
    						FD_CLR(csock, &socks);
    					} 
    					else
    						write(csock, buf, n);
    
    				}
    			} 
    		}
    
    #if 0
    		while (1)
    		{
    			char buf[1024];
    			int n = read(csock, buf, sizeof buf);
    			if (n == 0)
    				close(csock);
    			else if (n == -1)
    				close(csock);
    
    			write(csock, buf, n);
    		}
    #endif
    	}
    
    	close(sock);
    }
    


  • Synchronous Blocking I/O
    : 지금까지의 서버는 하나의 클라이언트에 대해서만 요청을 처리할 수 있다.
    : 하나의 클라이언트 요청이 끝나기 전까지 다음 요청을 처리할 수 없다.
    => Iteration Server를 도입해야한다.

  • Multi Process Model


    - 7_server
     : fork()를 통한 프로세스 생성
     : 독립된 주소를 갖으므로 안정적이지만, 프로세스 복제에 대한 오버헤드가 크고, Context Switching 비용이 높다.
     : 데이터를 공유하기 위해 별도의 작업이 필요하다. (IPC)

    #include <stdio.h>
    #include <unistd.h>
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <linux/tcp.h>
    
    #include <arpa/inet.h>
    #include <string.h>
    
    #include <signal.h>    // signal
    #include <sys/wait.h>  // wait()
    
    // 4. 시그널은 중첩되서 발생하지 않는다. - 루프 도입
    // 5. 시그널 핸들러는 비동기적으로 동작합니다.
    //    핸들러 내부에서 블록킹 연산을 절대 수행하면 안됩니다.
    //  - waitpid
    
    void handleClient(int csock);
    void handleChildProcess(int signo)
    {
    	printf("Cleanup child Prcess!\n");
    	// while (wait(0) > 0)
    	while (waitpid(-1, 0, WNOHANG) > 0) 
    		;
    }
    
    int main() {
    	// 3. 자식 프로세스의 상태를 수거해야 한다.
    	//  -> 좀비 프로세스로 인한 메모리 누수를 방지할 수 있다.
    	// signal handler 등록
    
    	// sigaction
    	signal(SIGCHLD, handleChildProcess);
    
    
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr;
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY;
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	int ret = bind(sock, (struct sockaddr *)&saddr, sizeof saddr);
    	if (ret != 0) {
    		perror("bind()");
    		return -1;
    	}
    
    	listen(sock, 5);
    
    	// 하나의 클라이언트만 요청을 처리할 수 있다.
    	//  => Iteration Server를 도입해야 한다.
    
    	while (1)
    	{
    		struct sockaddr_in caddr;
    		socklen_t size = sizeof(caddr);
    
    		int csock = accept(sock, (struct sockaddr *)&caddr, &size);
    
    		char *client_ip = inet_ntoa(caddr.sin_addr);
    		printf("클라이언트 %s 가 접속되었습니다.\n", client_ip);
    
    
    		// 하나의 클라이언트 요청이 끝나기 전까지 다음 요청을 처리할 수 없다.
    		//  1) pid = fork() - 리턴값을 통한 부모 / 자식 분기
    		//   - 자식 프로세스 : 0
    		//   - 부모 프로세스 : 자식 프로세스 pid 
    
    		//  2) fork()가 수행되었을 경우, 사용하지 않는 핸들을 닫아야 한다.
    
    		pid_t pid = fork();
    		if (pid == 0)
    			handleClient(csock);
    
    		close(csock);
    	}
    	
    	close(sock);
    }
    
    #include <stdlib.h>
    
    void handleClient(int csock)
    {
    	int n;
    	char buf[1024];
    
    	while (1) {
    		n = read(csock, buf, sizeof(buf));
    
    		if (n <= 0) {
    			printf("연결 종료!\n");
    			break;
    		}
    
    		n = write(csock, buf, n);
    	}
    
    	// 연결 종료
    	close(csock);
    	exit(0);
    } 

  • Multi Thread Model
    - 8_server
     : pthread 도입
     : kernel 영역을 제외한 복제로 스레드 생성에 대한 오버헤드가 낮고, Context Switching 비용도 낮다. 또 주소를 공유하기 때문에 자원공유가 편리하다.
     :하나의 Thread가 문제가 발생할 경우 전체 Process가 죽을 수 있다.

    // # g++ 8_server.cc -o server -lpthread
    #include <stdio.h>
    #include <unistd.h>
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <linux/tcp.h>
    
    #include <arpa/inet.h>
    #include <string.h>
    
    #include <signal.h>    // signal
    #include <sys/wait.h>  // wait()
    
    #include <pthread.h>
    // 멀티 프로세스 모델은 안전성은 뛰어나지만, 반면에 
    // 주소 공간의 분리에 의한 데이터 공유가 힘듭니다.
    // - IPC를 사용해야 한다.
    
    // => 스레드 모델을 도입하자. (pthread)
    void* handleClient(void* arg);
    
    int main() {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr;
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY;
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	int ret = bind(sock, (struct sockaddr *)&saddr, sizeof saddr);
    	if (ret != 0) {
    		perror("bind()");
    		return -1;
    	}
    
    	listen(sock, 5);
    
    	while (1)
    	{
    		struct sockaddr_in caddr;
    		socklen_t size = sizeof(caddr);
    
    		int csock = accept(sock, (struct sockaddr *)&caddr, &size);
    
    		char *client_ip = inet_ntoa(caddr.sin_addr);
    		printf("클라이언트 %s 가 접속되었습니다.\n", client_ip);
    
    		pthread_t thread;
    		pthread_create(&thread, 0, handleClient, &csock);
    
    		// 종료 처리를 해야 한다. - join, detach
    		// exit(0); 는 프로세스의 종료 -> return 으로 변경
    		// ★ 스레드가 온전히 종료할수 있도록 만드는 것이 중요
    		// 스레드도 결과적으로 fork 를 사용하므로 스레드에 관한 종료가 필요 join 또는 detach
    
    		// join vs detach?
    		// pthread_join을 통하여 정상적으로 종료된 보조스레드의 자원을 정상적으로 반환시켜주며,
    		// ptrhead_detach함수를 통하여 스레드가 종료시 자원을 정상적으로 반환시켜주도록 하는 함수로
    		// 메모리릭을 방지할 수 있다.
    
    		// - pthread_detach
    		// 생성된 thread에서 pthread_detach를 하면 join할 필요없이 생성된 thread가 할일을 다 마치고
    		// pthread_exit를 하는 순간 자원이 OS에 반납(보장됨)
    		// --> detach(떼어내다), 생성된 스레드를 메인 스레드에서 분리시킨다.
    		// - 호출방법 : 선호출, 후호출 2가지 방식
    		// 선호출 :  int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    		// 후호출 : pthread_detach(p_thread);
    		// --> detach를 하려면 선호출로 하는것을 추천한다.
    		// why? 후호출을 하기전에 thread가 종료된다면 메모리릭 발생(희박한 가능성이지만...)
    
    		// - pthread_join
    		// 보조 스레드의 종료까지 대기해주는 함수(즉, 메인스레드의 종료 대기)
    		// int pthread_join(pthread_t th, void **thread_return);
    		// 첫번째 아규먼트 th는 기다릴(join)할 쓰레드 식별자이며, 두번째 아규먼트 thread_return은 쓰레드의 리턴(return) 값이다. thread_return 이 NULL 이 아닐경우 해다 포인터로 쓰레드 리턴 값을 받아올수 있다.
    		pthread_detach(thread);
    
    	}
    	
    	close(sock);
    }
    
    #include <stdlib.h>
    
    void* handleClient(void* arg)
    {
    	int csock = *(int*)arg;
    
    	int n;
    	char buf[1024];
    	while (1) {
    		n = read(csock, buf, sizeof(buf));
    
    		if (n <= 0) {
    			printf("연결 종료!\n");
    			break;
    		}
    
    		n = write(csock, buf, n);
    	}
    
    	// 연결 종료
    	close(csock);
    
    	return 0;
    }
    

    - 7_client ~ 8_client
    #include <unistd.h>
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    
    #include <stdio.h>
    #include <string.h>
    
    #include <linux/tcp.h>
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in addr = {0, };
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr   = inet_addr("0.0.0.0");
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	int ret = connect(sock, (struct sockaddr*)&addr, sizeof addr);
    
    	if (ret != 0) {
    		printf("접속 실패!!\n");
    		perror("connect()");
    		return -1;
    	}
    
    	while (1) {
    		char buf[1024];
    		scanf("%s", buf);
    
    		ret = write(sock, buf, strlen(buf));
    		if (ret <= 0)
    			break;
    
    		ret = read(sock, buf, sizeof buf);
    		if (ret <= 0)
    			break;
    
    		write(1, buf, ret);
    		putchar('\n');
    	}
    
    	close(sock);
    }
    


  • I/O Models
    - blocking I/O vs. nonblocking I/O
     ■ blocking I/O
      : I/O 작업은 유저 영역에서 직접 수행할 수 없고, 커널 영역에서만 가능하다. 따라서 I/O 작업을 하기 위해서는 커널에 I/O 작업을 요청해야한다.
      : I/O 작업이 진행되는 동안 유저 영역 (process)의 작업은 중단한채 대기해야한다. 이처럼 I/O 동작 (데이터를 받을 준비, 데이터를 커널 영역에서 유저 영역으로 복사 등)으로 인해 프로세스가 block 상태가 되는 것을 blocking 된다고 한다.




     ■ nonblocking I/O
      : I/O 작업을 진행하는 동안 유저 프로세스의 작업을 중단시키지 않는다. 
      : 유저 프로세스가 커널의 read를 기다리는 것이 아니라 system call을 이용하여 반복적으로 요청하고, 이에 대한 버퍼값이 존재하면 유저 영역으로 복사해준다.
      : 반복적인 system call로 리소스가 남용




    - I/O multiplexing (select, poll) 

     : non-blocking 모델의 문제를 해결하기 위해 고안

     : I/O 처리가 필요한 파일 디스크립터 등을 가려내서 알려준다.




    - signal driven I/O (epoll, SIGIO), RTS(Real-Time Signal)
       Reactor Pattern
        : synchronous, non-blocking I/O handling and relies on an event notification interface

      
     Proactor Pattern
        : 
    asynchronous, non-blocking I/O operations, as provided by interfaces such as POSIX AIO




    synchronous I/O vs. asynchronous I/O (POSIX aio_functions)
     : 동기(synchronous)와 비동기(asynchronous)는 서로 메시지를 주고받는 상대방이 어떤 방식으로 통신을 하는가에 대한 개념이다.
     : I/O 통지모델에서 대화하는 주체들은 커널과 프로세스이다. 프로세스는 커널에게 I/O처리를 요청하고, 커널은 프로세스에게 I/O 상황을 통지한다. 우선 I/O 요청은 반드시 동일하게 처리될 수 밖에 없는 부분이고, 결국에 커널이 프로세스에게 어떤 방식으로 통지하느냐에 따라 동기형이냐 비동기형이 결정될 것이다.
     

      ■ synchronous I/O
       : 동기형 통지모델의 프로세스는 커널에게 지속적으로 현재 I/O 준비 상황을 체크한다.
       : 즉 커널이 준비되었는지를 계속 확인하여 동기화 하는 것이다. 따라서 동기형 통지모델에서 Notify를 적극적으로 진행하는 주체는 유저의 프로세스가 되며 커널은 수동적으로 유저 프로세스의 요청에 따라 현재의 상황을 보고한다.
     

      ■ asynchronous I/O

       : 비동기형 통지모델은 일단 커널에게 I/O작업을 맡기면 커널의 작업 진행사항에 대해서 프로세스가 인지할 필요가 없는 상황을 말한다.
       : 유저의 프로세스가 I/O 동기화를 신경쓸 필요가 없기에 비동기형이라고 부를 수 있다. 따라서 비동기형 통지모델에서 Notify의 적극적인 주체는 커널이 되며, 유저 프로세스는 수동적인 입장에서 자신이 할일을 하다가 통지가 오면 그때 I/O 처리를 하게 된다. 




  • I/O Model 비교


참고: http://ozt88.tistory.com/20


  • Server
    - 5_server
     : 클래스로 설계

     
    #include <stdio.h>
    #include <string.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    #include "Packet.h"
    
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
    	int n;
    	int ret;
    
    	n = len;
    	while (n > 0) 
    	{
    		ret = read(sd, buf, len);
    		if (ret < 0) return -1;
    		if (ret == 0) return len - n;
    
    		buf += ret;
    		n -= ret;
    	}
    
    	return len;
    }
    
    
    
    // 클래스를 설계할 때 지켜야하는 원칙 : SRP
    
    // 객체 지향 5대 원칙 - 로버트 C 마틴(클린 코드)
    // SRP: 단일 책임 원칙
    // OCP: 개방 폐쇄 원칙
    // LSP: 리스코프 치환 원칙
    // ISP: 인터페이스 분리 원칙
    // DIP: 의존관계 역전 원칙
    
    
    class NetAddress
    {
    	sockaddr_in addr;
    public:
    	NetAddress(const char* ip, short port)
    	{
    		memset(&addr, 0, sizeof addr);
    
    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(port);
    		addr.sin_addr.s_addr = inet_addr(ip);
    	}
    
    	sockaddr* getRawAddress()
    	{
    		return (sockaddr*)&addr;
    	}
    
    };
    
    class Socket
    {
    	int sock;
    public:
    	Socket() { sock = socket(PF_INET, SOCK_STREAM, 0); }
    
    	explicit Socket(int sockFd) : sock(sockFd) {}
    
    	void bind(NetAddress* address)
    	{
    		// 다시 C 함수롤 호출해야 한다.	
    		::bind(sock, address->getRawAddress(), sizeof(sockaddr_in));
    	}
    
    
    	void setReuseAddress(bool b)
    	{
    		int option = b;
    		socklen_t optlen = sizeof(option);
    		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    	}
    
    	void listen(int backlog)
    	{
    		::listen(sock, backlog);
    	}
    
    	Socket accept()
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = ::accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		return Socket(csock);
    	}
    
    	inline int read(char* buf, size_t size)
    	{ return ::read(sock, buf, size); }
    
    	inline int write(char* buf, size_t len)
    	{ return ::write(sock, buf, len); }
    
    	inline int readn(char* buf, int len)
    	{ return ::readn(sock, buf, len); }
    
    	inline int close()
    	{ return ::close(sock); }
    
    };
    
    
    int main()
    {
    	// int sock = socket(PF_INET, SOCK_STREAM, 0);
    	Socket sock;
    
    	NetAddress address("0.0.0.0", 4000); 
    	// struct sockaddr_in saddr = {0, };
    	// saddr.sin_family = AF_INET;
    	// saddr.sin_port = htons(4000);
    	// saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	sock.setReuseAddress(true);
    	// int option = true;
    	// socklen_t optlen = sizeof(option);
    	// setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	sock.bind(&address);
    	// bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    
    	sock.listen(5);
    	// listen(sock, 5);
    
    	while (1) 
    	{
    
    		Socket csock = sock.accept();
    		// struct sockaddr_in caddr = {0, };
    		// socklen_t clen = sizeof(caddr);
    		// int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		// char* cip = inet_ntoa(caddr.sin_addr);
    		// printf("Connected from %s\n", cip);
    
    		while (1) 
    		{
    			int len;
    			int ret = csock.readn((char*)&len, sizeof(len));
    
    			printf("len : %d\n", len);
    			if (ret != sizeof len) {
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				break;
    			}
    
    			char buf[4096];
    			ret = csock.readn(buf, len);
    			if (ret != len) {
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				break;
    			}
    
    			// 이제 데이터를 파싱하면 됩니다.
    			Packet packet(buf);
    			char op = packet.getByte();
    			int value1 = packet.getInt32();
    			int value2 = packet.getInt32();
    
    			printf("%2d%2c%-2d\n", value1, op, value2);
    			int result = calculate(op, value1, value2);
    
    			csock.write((char*)&result, sizeof(result));
    		}
    
    		//-----------------
    		csock.close();
    	}
    
    	sock.close();
    }
    


    - 6_server
     : SRP - Facade Pattern 적용

     
    #include <stdio.h>
    #include <string.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    #include "Packet.h"
    
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
    	int n;
    	int ret;
    
    	n = len;
    	while (n > 0) 
    	{
    		ret = read(sd, buf, len);
    		if (ret < 0) return -1;
    		if (ret == 0) return len - n;
    
    		buf += ret;
    		n -= ret;
    	}
    
    	return len;
    }
    
    
    
    // 클래스를 설계할 때 지켜야하는 원칙 : SRP
    
    // 객체 지향 5대 원칙 - 로버트 C 마틴(클린 코드)
    // SRP: 단일 책임 원칙
    // OCP: 개방 폐쇄 원칙
    // LSP: 리스코프 치환 원칙
    // ISP: 인터페이스 분리 원칙
    // DIP: 의존관계 역전 원칙
    
    
    class NetAddress
    {
    	sockaddr_in addr;
    public:
    	NetAddress(const char* ip, short port)
    	{
    		memset(&addr, 0, sizeof addr);
    
    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(port);
    		addr.sin_addr.s_addr = inet_addr(ip);
    	}
    
    	sockaddr* getRawAddress()
    	{
    		return (sockaddr*)&addr;
    	}
    
    };
    
    class Socket
    {
    	int sock;
    public:
    	Socket() { sock = socket(PF_INET, SOCK_STREAM, 0); }
    
    	explicit Socket(int sockFd) : sock(sockFd) {} // explicit로 지정함으로써 암시적 형변환을 막는다.
    
    	void bind(NetAddress* address)
    	{
    		// 다시 C 함수롤 호출해야 한다.	
    		::bind(sock, address->getRawAddress(), sizeof(sockaddr_in));
    	}
    
    
    	void setReuseAddress(bool b)
    	{
    		int option = b;
    		socklen_t optlen = sizeof(option);
    		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    	}
    
    	void listen(int backlog)
    	{
    		::listen(sock, backlog);
    	}
    
    	Socket accept()
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = ::accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		return Socket(csock);
    	}
    
    	inline int read(char* buf, size_t size)
    	{ return ::read(sock, buf, size); }
    
    	inline int write(char* buf, size_t len)
    	{ return ::write(sock, buf, len); }
    
    	inline int readn(char* buf, int len)
    	{ return ::readn(sock, buf, len); }
    
    	inline int close()
    	{ return ::close(sock); }
    
    };
    
    // TCP 서버 프로그래밍을 하는데 있어서 필요한 클래스와 절차를
    // 단순화 시켜주는 상위 개념의 클래스를 제공하자.
    // -> Facade Pattern
    
    class TCPServer
    {
    	Socket sock;
    public:
    	void start(short port)
    	{
    		NetAddress address("0.0.0.0", port); 
    		sock.setReuseAddress(true);
    		sock.bind(&address);
    		sock.listen(5);
    
    		while (1) 
    		{
    
    			Socket csock = sock.accept();
    
    			while (1) 
    			{
    				int len;
    				int ret = csock.readn((char*)&len, sizeof(len));
    
    				printf("len : %d\n", len);
    				if (ret != sizeof len) {
    					break;
    				} else if (ret == -1) {
    					perror("readn()");
    					break;
    				}
    
    				char buf[4096];
    				ret = csock.readn(buf, len);
    				if (ret != len) {
    					break;
    				} else if (ret == -1) {
    					perror("readn()");
    					break;
    				}
    
    				// 이제 데이터를 파싱하면 됩니다.
    				Packet packet(buf);
    				char op = packet.getByte();
    				int value1 = packet.getInt32();
    				int value2 = packet.getInt32();
    
    				printf("%2d%2c%-2d\n", value1, op, value2);
    				int result = calculate(op, value1, value2);
    
    				csock.write((char*)&result, sizeof(result));
    			}
    
    			//-----------------
    			csock.close();
    		}
    
    		sock.close();
    
    
    	}
    
    };
    
    
    int main()
    {
    	TCPServer server;
    	server.start(4000);
    }
    


  • 계산기 Server - Client 만들기


  • Server
    - 1_server
     : 계산기 서버 초기 구조
     : log 메세지를 확인할 때, \n 빼먹으면 문제가 생길 가능성이 크다.
     : 프로토콜 설계
     : 데이터 파싱

    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
     
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	while (1) 
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		char* cip = inet_ntoa(caddr.sin_addr);
    		printf("Connected from %s\n", cip);
    
    
    		// TCP 는 데이터의 경계가 존재하지 않는다.
    		//-------------
    		char buf[1024];
    		int ret = read(csock, buf, 1024);
    		printf("ret : %d\n", ret);
    		if (ret == 0) {
    			close(csock);
    			break;
    		} else if (ret == -1) {
    			perror("read()");
    			close(csock);
    			break;
    		}
    
    		char* p = buf;
    		char op = *p;
    		++p;
    
    		int value1 = *(int*)p;
    		p += sizeof(int);
    
    		int value2 = *(int*)p;
    		p += sizeof(int);
    
    		printf("%2d%2c%2d\n", value1, op, value2);
    		int result = calculate(op, value1, value2);
    
    		write(csock, &result, sizeof result);
    
    		//-----------------
    		close(csock);
    	}
    
    	close(sock);
    }
    


    - 2_server
     : 수식 여러번 받아서 계산을 할 수 있는 구조
     : tcp는 write와 read가 병합되는 형태의 스트림 단위의 처리방식, 데이터의 경계가 존재하지 않는다.(반면 udp는 패킷 덩어리로 보낸만큼 간다.)
     : 100번 보낸다고해서 100번을 받는것이 아니다. -> 패킷을 분리해야한다. -> 무조건 패킷의 길이가 들어가야한다.
     : 길이는 약 3배정도 여유있게 잡는 것이 좋다.
     : 서버는 온전하게 동작하게 만든 뒤, 고성능 서버로 바꾸는 것!

    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	while (1) 
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		char* cip = inet_ntoa(caddr.sin_addr);
    		printf("Connected from %s\n", cip);
    
    
    		// TCP 는 데이터의 경계가 존재하지 않는다.
    		//-------------
    
    		// int len;
    		// int ret = read(csock, &len, sizeof len);
    
    		char buf[4096];
    		int ret = read(csock, buf, sizeof buf);
    		printf("ret : %d\n", ret);
    		if (ret == 0) {
    			close(csock);
    			break;
    		} else if (ret == -1) {
    			perror("read()");
    			close(csock);
    			break;
    		}
    
    
    		while (ret > 0) {
    			char* p = buf;
    
    			int len = *(int*)p;
    			p += sizeof(int);
    
    			char op = *p;
    			++p;
    
    			int value1 = *(int*)p;
    			p += sizeof(int);
    
    			int value2 = *(int*)p;
    			p += sizeof(int);
    
    			ret -= len;
    
    			printf("%2d%2c%2d\n", value1, op, value2);
    			int result = calculate(op, value1, value2);
    
    			write(csock, &result, sizeof result);
    		}
    
    		//-----------------
    		close(csock);
    	}
    
    	close(sock);
    }
    


    - 3_server

    9

    '10'

    '20'

    '-'


     
    : 2_server은 위의 패킷(스트림)이 올 경우 9에서 끊어질 수가 있다. -> readn 구현이 필요 -> block이 필요
    : TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를 해결하기 위해서는 readn을 도입하면 된다.

    : ★★readn, tcp 패킷자체에 대한 프로토콜이 잘못될 가능성은 거의 없어진다. 즉, 길이 base로 만들어야한다.

    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
    	int n;
    	int ret;
    
    	n = len;
    	while (n > 0) 
    	{
    		ret = read(sd, buf, len);
    		if (ret < 0) return -1;
    		if (ret == 0) return len - n;
    
    		buf += ret;
    		n -= ret;
    	}
    
    	return len;
    }
    
    
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	while (1) 
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		char* cip = inet_ntoa(caddr.sin_addr);
    		printf("Connected from %s\n", cip);
    
    
    		while (1) 
    		{
    			int len;
    			int ret = readn(csock, (char*)&len, sizeof(len));
    			if (ret == 0) {
    				close(csock);
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				close(csock);
    				break;
    			}
    
    			char buf[4096];
    			ret = readn(csock, buf, len);
    			if (ret == 0) {
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				break;
    			}
    
    			// 이제 데이터를 파싱하면 됩니다.
    			char* p = buf;
    			char op = *p;
    			++p;
    
    			int value1 = *(int*)p;
    			p += sizeof(int);
    
    			int value2 = *(int*)p;
    			p += sizeof(int);
    
    			printf("%2d%2c%-2d\n", value1, op, value2);
    			int result = calculate(op, value1, value2);
    
    			write(csock, &result, sizeof result);
    		}
    	
    		//-----------------
    		close(csock);
    	}
    
    	close(sock);
    }
    

    - 4_server
     : 패킷 객체화

    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    #include "Packet.h"
    
    int calculate(char op, int value1, int value2)
    {
    	switch (op) {
    		case '+': return value1 + value2;
    		case '-': return value1 - value2;
    		case '*': return value1 * value2;
    
    		default:
    			return -1;
    	}
    }
    
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
    	int n;
    	int ret;
    
    	n = len;
    	while (n > 0) 
    	{
    		ret = read(sd, buf, len);
    		if (ret < 0) return -1;
    		if (ret == 0) return len - n;
    
    		buf += ret;
    		n -= ret;
    	}
    
    	return len;
    }
    
    
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	while (1) 
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		char* cip = inet_ntoa(caddr.sin_addr);
    		printf("Connected from %s\n", cip);
    
    
    		while (1) 
    		{
    			int len;
    			int ret = readn(csock, (char*)&len, sizeof(len));
    
    			printf("len : %d\n", len);
    			if (ret != sizeof len) {
    				close(csock);
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				close(csock);
    				break;
    			}
    
    			char buf[4096];
    			ret = readn(csock, buf, len);
    			if (ret != len) {
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				break;
    			}
    
    			// 이제 데이터를 파싱하면 됩니다.
    			Packet packet(buf);
    			char op = packet.getByte();
    			int value1 = packet.getInt32();
    			int value2 = packet.getInt32();
    
    			printf("%2d%2c%-2d\n", value1, op, value2);
    			int result = calculate(op, value1, value2);
    
    			write(csock, &result, sizeof result);
    		}
    	
    		//-----------------
    		close(csock);
    	}
    
    	close(sock);
    }
    

    ■ 패킷 클래스 

     ∴ 게임에서는? 

      : 패킷을 만드는 클래스를 하나 둔다.
      : Builder pattern? 객체가 만들어지는 공정은 동일하지만, 내부적으로 표현이 달라지는 형태의 객체를 만들 때 사용(ex. StringBuilder), 코드 반복을 줄일 수 있다.

    #ifndef PACKET_H
    #define PACKET_H
    
    class Packet
    {
    public:
    	inline Packet(char* buf) : start(buf), pos(buf) {}
    
    	inline int getInt32() const
    	{
    		int ret = *(int*)pos;
    		pos += sizeof(int);
    
    		return ret;
    	}
    
    	inline char getByte() const 
    	{
    		char ret = *(char*)pos;
    		++pos;
    
    		return ret;
    	}
    
    	inline Packet& putByte(char value)
    	{
    		*pos = value;
    		++pos;
    
    		return *this;
    	}
    
    	inline Packet& putInt32(int value)
    	{
    		*(int*)pos = value;
    		pos += sizeof(int);
    
    		return *this;
    	}
    
    	inline int build()
    	{
    		int len = pos - start;
    		return len;
    	}
    
    
    private:
    	char* start;
    	mutable char* pos;
    };
    
    
    #endif
    
    


  • Client
    - 1_client
     : 계산기 클라이언트 초기 구조
     : 패킷 만들기

     
     #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <iostream>
    using namespace std;
    
    // 계산기 클라이언트를 만들어보자.
    // 1. 프로토콜을 설계해야 한다.
    //  [op:1][lvalue:4][rvalue:4]
    int main()
    {
    	int sd = socket(PF_INET,
    			SOCK_STREAM,  
    			IPPROTO_TCP);
    
    	struct sockaddr_in addr = {0, };
    	addr.sin_family = AF_INET; 
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    	int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); 
    	if (ret == -1) {
    		perror("connect()");
    		return -1;
    	}
    
    	printf("Connection succeed!!\n");
    	//-------------
    	char op = '+';
    	int value1 = 10, value2 = 20;
    	// cout << "value 1: "; cin >> value1;
    	// cout << "value 2: "; cin >> value2;
    	// cout << "op : ";     cin >> op;
    
    	// 패킷을 만들어야 한다.
    	char buf[1024];
    	char* p = buf;
    
    	*p = op;
    	++p;
    
    	*(int*)p = value1;
    	p += sizeof(int);
    
    	*(int*)p = value2;
    	p += sizeof(int);
    	int size = p - buf;
    
    	for (int i = 0 ; i < 100; i++) {
    		write(sd, buf, size);
    	}
    
    	for (int i = 0 ; i < 100; i++) {
    		int result;
    		int n = read(sd, &result, sizeof result);
    		cout << i+1 << "- result : " << result << endl;
    	}
    
    	close(sd);
    }
    


    - 2_client ~ 3_client
     : 수식을 여러번 보내서, 여러 결과값을 받을 수 있는 구조
     : 패킷의 길이까지 붙여서 패킷 만들기

     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <iostream>
    using namespace std;
    
    // 계산기 클라이언트를 만들어보자.
    // 1. 프로토콜을 설계해야 한다.
    //  [op:1][lvalue:4][rvalue:4]
    int main()
    {
    	int sd = socket(PF_INET,
    			SOCK_STREAM,  
    			IPPROTO_TCP);
    
    	struct sockaddr_in addr = {0, };
    	addr.sin_family = AF_INET; 
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    	int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); 
    	if (ret == -1) {
    		perror("connect()");
    		return -1;
    	}
    
    	printf("Connection succeed!!\n");
    	//-------------
    	char op = '+';
    	int value1 = 10, value2 = 20;
    	// cout << "value 1: "; cin >> value1;
    	// cout << "value 2: "; cin >> value2;
    	// cout << "op : ";     cin >> op;
    
    	// 패킷을 만들어야 한다.
    	char buf[1024];
    	char* p = buf;
    
    	*p = op;
    	++p;
    
    	*(int*)p = value1;
    	p += sizeof(int);
    
    	*(int*)p = value2;
    	p += sizeof(int);
    	int size = p - buf;
    	for (int i = 0 ; i < 1000; i++) {
    		write(sd, &size, sizeof size);
    		write(sd, buf, size);
    	}
    
    	for (int i = 0 ; i < 1000; i++) {
    		int result;
    		int n = read(sd, &result, sizeof result);
    		cout << i+1 << "- result : " << result << endl;
    	}
    
    	close(sd);
    }
    

    - 4_client
     : 패킷 클래스로 만들기
     : 체이닝 방식 권장

     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <iostream>
    using namespace std;
    
    #include "Packet.h"
    
    
    int main()
    {
    	int sd = socket(PF_INET,
    			SOCK_STREAM,  
    			IPPROTO_TCP);
    
    	struct sockaddr_in addr = {0, };
    	addr.sin_family = AF_INET; 
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    	int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); 
    	if (ret == -1) {
    		perror("connect()");
    		return -1;
    	}
    
    	printf("Connection succeed!!\n");
    	//-------------
    	char op = '+';
    	int value1 = 10, value2 = 20;
    
    	// 패킷을 만들어야 한다.
    	char buf[1024];
    
    	// 체이닝 방식 - 완전한 객체로
    	Packet packet = Packet(buf).putByte(op)
    								.putInt32(value1)
    								.putInt32(value2);
    	// vs
    	// 불완전한 객체로 존재
    	// Packet packet(buf);
    	// packet.putByte(op);
    	// packet.putInt32(value1);
    	// packet.putInt32(value2);
    
    	int size = packet.build();
    
    	/*
    	char* p = buf;
    	*p = op;
    	++p;
    
    	*(int*)p = value1;
    	p += sizeof(int);
    
    	*(int*)p = value2;
    	p += sizeof(int);
    	int size = p - buf;
    	*/
    	for (int i = 0 ; i < 10000; i++) {
    		write(sd, &size, sizeof size);
    		write(sd, buf, size);
    	}
    
    	for (int i = 0 ; i < 10000; i++) {
    		int result;
    		int n = read(sd, &result, sizeof result);
    		cout << i+1 << "- result : " << result << endl;
    	}
    
    	close(sd);
    }
    


+ Recent posts