전체 글 (99)

  • 2022.01.27
  • 2022.01.26
  • 2022.01.21
  • 2022.01.19
  • 2022.01.17
  • 2022.01.14
  • 2022.01.11
  • 2022.01.10
  • 2022.01.10
  • 2022.01.07
  • 2022.01.06
  • 2022.01.06
  • 01
    27

    https://docs.microsoft.com/ko-kr/sql/odbc/microsoft-open-database-connectivity-odbc?view=sql-server-ver15

     

    Microsoft ODBC (Open Database Connectivity) - Open Database Connectivity (ODBC)

    Microsoft ODBC(Open Database Connectivity)

    docs.microsoft.com

     

    Oracle
    MySQL
    MSSQL
    MariaDB
    - 오라클에서 만들어 판매중인 상업용 데이터베이스
    - 윈도우, 리눅스, 유닉스 등 다양한 운영체제(OS)에서 설치 가능
    - MySQL, MSSQL보다 대량의 데이터 처리 용이
    - 대기업에서 주로 사용하며, 글로벌 DB 시장 점유율 1위
    - 비공개 소스, 폐쇄적인 운영
    - 가장 널리 사용되는 RDBMS
    - MySQL사에서 개발, 썬마이크로시스템즈를 거쳐 현재 오라클에 인수합병
    - 윈도우, 리눅스, 유닉스 등 다양한 운영체제(OS)에서 설치 가능
    - 오픈소스로 이루어져있는 무료 프로그램(상업적 사용 시 비용 발생)
    - 가격 등의 장점을 앞세워 다수의 중소기업에서 사용중
    - RDBMS
    - 마이크로소프트(MS)사에서 개발한 상업용 데이터베이스
    - 다른 운영체제에서도 사용가능하지만 윈도우에 특화됨
    - 비공개 소스로 폐쇄적인 운영(리눅스 버전은 오픈소스)
    - 중소기업에서 주로 사용중
    - RDBMS
    - MySQL이 오라클에 인수합병된 후 불확실한 라이선스 문제를 해결하려고 나온 오픈소스 RDBMS
    - 구현언어 : C++
    - MySQL과 동일한 소스 코드 기반
    - MySQL과 비교해 애플리케이션 부분 속도가 약 4~5천배 정도 빠름

    출처 : https://m.blog.naver.com/sundooedu/221301384166

     

    위는 DBMS 데이터베이스 관리 프로그램 종류이다. 

    응용프로그램에서는 DBMS와 데이터를 주고받는 통신을 한다.

    근데 DBMS의 종류가 여러개이고, 각자 프로토콜이 다르다.

    또한 DBMS사에서 프로토콜을 공개하지 않는다. 

     

    Vender API

     

    프로토콜을 공개하지 않는데, 어떻게 프로그램이랑 DB랑 통신을 하냐면

    Vender API를 사용한다.

    DBMS 마다 서로 다른 Vender API로 DBMS에 종속적인 문제가 있다. 

     

    ODBC API

     

    MS가 최초로 시도한 데이터베이스 연결을 위한 표준이다.

    앞서 말한 문제점을 가지고 MS사에서 ODBC API 규격으로

    모든 DBMS에 통합된 접근 방법을 만들었다.

     

    DBMS는 본인사의 ODBC Driver를 만들어 제공한다. 

     

    ODBC API <-> 해당DBMS해당 DBMS Driver <-> 해당 DBMS Vender API <-> 해당 DBMS

     

    이런 식으로 통신한다. 비종속적으로 ODBC은 여러 DBMS 기능을 가지고 있음

    단점으로는 Vender API를 쓰는 것보다 여러 인터페이스를 거치기 때문에

    직접 이용하는 것보다는 속도가 느리다. 

     

    OCBC 데이터 타입에는 Sql문과 C언어형 두가지 형태의 데이터 타입을 지원한다.

    예를 들어 long int 타입은 [C타입 - SQL_C_ULONG , SQL - SQLINTEGER ] 

    두 가지 모두 사용할 수 있다. 이렇게 두 가지를 지원하는 이유는 

    JAVA나 c#과 C언어의 데이터 형이 다르기 때문이다. 

     

    ODBC, DAO, RDO, OLE DB, ADO

     

    이름 내용
    ODBC 데이터 엑세스를 위해 C언어에서 사용하도록 설계된 표준 하위 애플리케이션 프로그래밍 인터페이스 (API)이다. SQL 쿼리문으로 그 데이터베이스 언어로 사용한다.  
    DAO ODBC 이후 발표된 것, Jet Database Engine에 접근하기 위해 프론트 엔드 애플리케이션 개발에 필요한 c++ 인터페이스
    로컬 DB용으로 많이 쓰임
    RDO 서버에 기반을 둔 데이터베이스 기술
    OLE DB OLE라고 불리는데, COM인터페이스의 집합체이다. 객체 연결 삽입 데이터 베이스로 ODBC를 높은 수준으로 대체하면서 그 뒤를 잇는 설계, 마이크로소프트 데이터 엑세스 구성 요소(MDAC)스택의 일부로 파일 시스템, 스프레트시트,ASP 같은 다양한 문서를 사용가능
    ADO ActiveX Data Objects로 웹 기반의 애플리케이션이나 데이터 기반에서 쓰일 수 있는 객체 기반 인터페이스 

     

    설치

     

    앞으로 MS Access Database 데이터베이스 응용프로그램으로

    Visual Studio에 연결하여 데이터베이스를 관리할 것이다.

     

    ODBC를 사용해여 DBMS(MS Access Database)와 Visual studio 통신하려면 드라이버를 설치해야 한다.  

    Microsoft Access Database Engine 2010 재배포 가능 패키지

    https://www.microsoft.com/ko-kr/download/details.aspx?id=13255 

     

    Download Microsoft Access Database Engine 2010 재배포 가능 패키지 from Official Microsoft Download Center

    중요! 아래에서 언어를 선택하면 전체 페이지 내용이 해당 언어로 신속하게 변경됩니다. 독일어스페인어영어이탈리아어일본어중국어(간체)중국어(번체)프랑스어한국어 다운로드 이 다운로드

    www.microsoft.com

     

    설치 완료해서 ODBC 데이터 관리자에서 확인이 되면 설치 완료된 것,

     

    COMMENT
     
    01
    26
    모델 내용
    Select 모델 Select 모델은 동기 입출력을 수행하기 위해 필요한 경우 대기 중인 하나 이상의 소켓 상태를 결정한다.
    소켓을 만들고, Select함수를 통해 FD_SET으로 해당 소켓을 등록하여 FD_ISSET으로 관찰할 수 있는 방식이다.
     
    fd_set -> Readfds – 수신할 데이터가 있을 때
             Writefds – 쓰기 가능한 상태일 때, 연결 성공했을 때
             Exceptfds – 연결이 실패했을 때
     
    소켓모드와 상관없이 한 스레드에서 여러 소켓을 처리할 수 있고, 윈도우, 유닉스 등 여러 운영체제에서 사용할 수 있어서 이식성이 좋지만, 성능은 여섯 가지 모델 중 가장 좋지 않다.
     64개 이상의 소켓을 처리하려면 여러 개의 스레드를 사용해야한다.
    WSAAsyncSelect 모델 Async는 비동기적이라는 의미로 윈속의 소켓이벤트를 윈도우 메시지를 통해서 비동기적으로 받는다. 윈도우 메시지를 통해 성공 여부를 판단한다. 윈도우 메시지 큐에 의존해 소켓의 처리가 이루어져 많은 수의 소켓 이벤트를 처리해야 하는 경우 큐가 고갈될 수 있다. 윈도우 메시지를 사용자 정의하여 원하는 네트워크 이벤트를 발생시킨다.
     
    네트워크 이벤트
    FD_WRITE – WSAConnect 함수 호출하고 소켓이 처음 연결되었을 때, WSAAccept 호출 뒤 연결 수락되었을 때 send, WSASend, sendto, WSASendTO 함수 호출 후에 WSAEWOULDBLOCK이 리턴이 돼서 전송 버퍼가 비워졌을 때 이벤트 발생한다. , 보낼 수 있는 상태가 되면 발생한다.
    FD_ACCEPT – 클라이언트가 접속했을 때
    FD_READ – 수신 가능할 때
    FD_CLOSE – 접속 종료할 때
    FD_CONNECT – 접속 완료될 때
    FD_OBB – 데이터가 도착할 때
     
    소켓 이벤트를 윈도우 메시지 형태로 처리해서 윈도우 친화적이다. 서버보다 클라이언트에 유리하다. 하지만 하나의 윈도우 프로시저에서 일반 윈도우 메시지와 소켓 메시지를 처리해야 해서 성능저하의 요인이 된다. 또한 윈도우여야만 적용이 가능하다는 단점이 있다.
    WSAEventSelect 모델 두개 이상의 스레드가 협력해서 작업을 하는 경우에 상황에 따라 동기화가 필요하다. 그때 동기화 객체가 쓰이는데, event객체는 그 중 하나이며, 커널 오브젝트이기도 하다.
    소켓과 WSACreateEvent()로 이벤트 객체를 생성하여 짝지어서 네트워크 이벤트가 발생함을 애플리케이션에서 알 수 있게 되는 방식이다.
     
    WaitForMultipleEvent() 함수로 신호 상태가 된 커널 오브젝트에 네트워크 이벤트가 발생했다는 사실을 알 수 있기 때문이다. 반환 값은 신호 상태로 변화된 이벤트 배열의 인덱스이다.
    또한, WSAEnumNetworkEvents()로 구체적인 네트워크 이벤트를 알려주는 함수도 있다.
     
    Select 모델과 WSAAsyncSelect 모델의 특성을 혼합한 형태로 비교적 뛰어난 성능을 가짐에도 WSAAsyncSelect와 달리 윈도우 메시지가 아닌 이벤트 오브젝트를 사용해 윈도우가 아니어도 구현 가능하다.
    단점으로는64개 이상의 소켓을 처리하려면 여러 개의 스레드를 사용해야한다.
    Overlapped 모델 I
    (Overlapped Event)
    Overlapped은 중첩 입출력으로 응용 프로그램은 입출력 함수를 호출한 후에도 입출력 작업의 완료 여부와 무관하게 다른 작업을 하는 비동기 입출력을 지원한다.
    IO가 여러개 중첩되는 것, 요청하고 기다릴 필요없이 리턴하고
    다른 일을 수행하는 것이 Overlapped IO이고, 통지하는 방법에 따라 종류가 나뉜다.
     



     
    WSAEventSelect 모델과 비슷하게 이벤트 객체를 사용한다. 소켓 입출력 작업이 완료되면, 운영체제는 응용 프로그램이 등록한 이벤트 객체를 신호 상태로 바꾼다. 이벤트 객체를 관찰함으로써 입출력 작업 완료를 감지할 수 있다.
     
    비동기 입출력을 지원하는 소켓을 생성하고 WSACreateEvent() 함수로 대응하는 이벤트 객체도 같이 생성한다. 비동기 입출력을 지원하는 소켓 함수를 호출할 때 WSAOVERLAPPED 구조체의 hEvent변수에 이벤트 개체 핸들 값을 넣어 전달한다.
    동기 입출력이 완료가 되지 않았다면 오류를 리턴 하며. 오류 코드는 WSA_IO_PENDING으로 된다. 입출력이 완료가 되면 운영체제는 이벤트 객체를 신호 상태로 만들어 통보한다.
    WSAWaitForMultipleEvent() 함수가 리턴 되면 WSAGetOverlappedResult()함수로 비동기 입출력 결과를 확인하고 데이터를 처리한다.
     
    비동기 입출력을 통해 성능이 뛰어나지만, 이벤트 객체로 64개 이상의 소켓을 처리하려면 여러 개의 스레드를 사용해야 하는 단점이 있다.
    Overlapped 모델 II
    (Overlapped Callback)
    소켓 입출력이 완료되면 등록해둔 함수를 자동으로 호출한다. 일반적으로 운영체제가 호출하는 응용 프로그램 함수를 콜백함수라고 하는데, Overlapped 모델에서는 완료 루틴(completion routine) 이라고 한다.
     
    비동기 입출력을 지원하는 소켓을 생성한다. 비동기 입출력 함수(WSASend, WSARecv)를 호출할 때 완료 루틴의 시작 주소를 함수 인자로 전달한다. 비동기 입출력 작업이 완료되지 않으면 WSA_IO_PENDING이 된다. 운영체제에 입출력 작업요청을 한다. WaitForSingleObjectEx(), WaitForMultipleObjectEx(), SleepEx(), WSAWaitForMultipleEvent() 등 함수로 해당 스레드는 완료 루틴이 호출될 수 있는 상태인 Alertable wait 상태로 진입하게 한다.
    비동기 입출력 작업이 완료되면, 운영체제는 스레드의 APC큐에 결과를 저장하고 완료 루틴을 호출한다.
     
    APC : asynchronous procedure call queue는 비동기 입출력 결과를 저장을 위해 운영체제가 각 스레드에 할당하는 메모리 영역이다.
     
    비동기 입출력 함수를 호출한 스레드가 Alertable wait 상태에 있으면 운영체제는 APC 큐에 저장된 정보를 참조하여 완료 루틴을 호출한다. 완료 루틴 호출이 끝나면, 스레드는 Alertable wait 상태에서 빠져나온다. 스레드가 비동기 입출력 결과를 계속 처리하기 위해서 다시 Alertable wait 상태에 진입하는 동작 원리를 갖는다.
     
    장점으로는 비동기 입출력을 통해 뛰어난 성능을 갖는다. 단점으로는 모든 비동기 소켓 함수에 대해 완료 루틴을 사용할 수 있는 건 아니다. 콜백 함수의 결과를 해당 콜백 함수를 수행한 스레드 만이 알 수 있다.
    IOCP
    Completion Port
    비동기 입출력으로 입출력을 요청하고도 다른 일을 수행하는 것이 Overlapped IO이다. Overlapped IO를 이벤트와 콜백을 사용해 작업완료 여부를 체크하는 방법 중 하나로 윈도우에서 제공하는 일반적인 I/O 모델 중 최고 성능을 갖는 IOCP이다.
     
    커널 객체(운영체제의 핵심적인 기능 프로세스, 스레드 메모리 관리 기능 제공하는 객체)이다. IOCP 시스템을 관리하는 객체를 커널이 만들어서 우리에게 HANDLE를 넘겨주는 것이다.
     

    IOCP 특징 중 하나가, 쓰레드 풀로 쓰레드를 재사용하는 것인데,
    IOCP 본인의 스레드 큐를 가지고 작업을 하고 완료된 IO가 있다면 알려준다.
    재사용이 가능한 스레드를 유지하는 스레드 풀링은
    WSASend/ WSARecv같은 비동기 입출력 함수를 쓰면
    스레드 내부적으로 APC 큐가 생성이 된다. 여기에 완료된 결과를 저장하는데
    APC큐는 자동으로 생성되고 파괴된다. 그리고 그 큐는 본인 스레드에서만
    확인이 가능하는데, 반면에 관리자 역할을 하는 IOCP는 모든 걸 확인이 가능하며, CloseHandle()함수 호출하여 파괴한다. 입출력 완료 포트를 접근하는 스레드를 별도로 두는데 이를 작업 스레드(WorkerThread라고 한다.)
     
    예를 들어 관리자(IOCP)가 일꾼(1번 입출력), 일꾼(2번 입출력)
    1번 일꾼의 IO작업이 끝났 다하면 이를 처리하기 위해 Waiting Thread Queue
    가장 최근에 들어온 스레드를 깨운다. I/O가 완료될 때마다 기다리고 쓰레드가 알아서 I/O작업을 해 성능이 좋다. -> 스레드 풀
     
    또한, 특정 스레드에서 작업을 하던 간에IOCP 큐자체가 기타 스레드 결과가 저장이 된다.
    , A 스레드에서 작업해도 B스레드에서도 확인이 가능하다.
     
    IOCP 자료구조 설명
    Device list hDevice, Completion Key로 구성되어 키 값으로 값을 얻을 수 있게 됨
    IO Completion Queue FIFO로 앞에서부터 자료를 빼서 처리, I/O가 완료 되면 정보(이벤트)를 저장함
    Waiting Thread Queue LIFO로 마지막에 사용된 스레드를 다시 사용함, 스레드 ID가 저장됨  스레드 풀 역할
    Released Thread List Waiting Thread Queue(스레드 풀)에서 꺼내 온 쓰레드 정보 
    Paused Thread List Release쓰레드가 Suspend상태되면 저장되고, Suspend상태가 해제되면 다시 Released Thread List로 올라감

     

    소켓 입출력 모델 (Select, WSAAsyncSelect, WSAEventSelect는 동기 입출력과 비동기 통지를 결합한 형태

    비동기 소켓 입출력 모델은 (Overlapped, Completion Port)는 비동기 입출력과 비동기 통지를 결합한 형태

    COMMENT
     
    01
    21

    std::fucnction

    c의 함수포인터를 대체하는 c++11부터 추가된 기능이다.

    용도 : 함수포인터 반환, 함수를 다른 함수에 전달

     

    전역 함수를 function로 받아옴

     

    전역으로 선언된 Func 함수를 std::function 기능으로 받아온 것이다. 

    전역함수가 아니라 클래스의 멤버함수의 경우에는

     

    클래스의 맴버함수를 function로 받아옴

     

    클래스 객체를 하나 만들어서 함수의 주소를 전달해서 사용해야한다.

    또한 그렇게 만든 함수를 사용할때도 객체를 전달해야한다. 


    std::bind

    Functional과 마찬가지로 c++11부터 표준이다.

    용도 : 함수의 일부 매개변수를 고정 값으로 정해, 사용할 수 있게 한다. 

     

     

    위 코드는 bind로 1, 2.0f, false로 함수의 매개변수를 고정시킨 것이다.

    FuncA(5,6,false) 로 넣든간에 120의 결과로 나오는 걸 볼 수 있다.  

    bind시 인자를 변수로 변경하고 싶으면 std::placeholders를 사용해야한다. 

     

     

     function을 사용하지않고 간편하게 auto로 bind를 할 수 있다.  

     

      1) function Class를 사용

        std::function<함수의 형태> 변수명  = bind(함수명, 인자1, 인자 2,...);


      2) auto 키워드 사용

        auto 변수명  = bind(함수명, 인자 1, 인자 2,...);

    auto가 편하니 auto를 쓰자


    std::placeholders

    bind시 인자를 변수로 변경하고 싶으면 std::placeholders를 사용해야한다. 

     

     

    std::placeholders::_1 의 숫자는 인자의 순서를 뜻한다.

    고로 첫번째인자에는 _1이 들어가고, 두번째인자에는 _2가 들어간다.

    bind 마지막 인자에 _1를 넣었는데, 이렇게 되면 첫번째 인자가 불값으로 들어간다.

    결과 121이 나오는 걸 확인 할 수 있음

     

     

    COMMENT
     
    01
    19

     

    이전 포스팅에서 Overlapped 구조체를 사용해서 

    비동기 파일 입출력을 구현했었다.

    WriteFile함수와 ReadFile 함수에 Overlapped 구조체를 넣고 IO를 중첩시키고,

    WSASend 하면 바로 리턴됨 (ERROR_IO_PENDING)

    GetOverlappedResult함수로 해당 IO의 결과를 얻을 수 있게 해주는 함수도 있었다.

     

    IOCP는 Overlapped IO로 바탕으로 결과를 받아 처리하는 하나의 방식으로

    Overlapped IO를 기본적으로 알고 있어야 한다.  

    IO가 여러개 중첩되는 것, 요청하고 기다릴 필요없이 리턴하고

    다른 일을 수행하는 것이 Overlapped IO이고, 

     

    중첩

     

    이 작업 완료 여부를 체크하는 방법 중 하나가 IOCP인 것이다.

    Overlapped구조체, 이벤트 객체멤버 등 다른 방법들이 있지만

    IOCP는 입출력 요청한 스레드는 계속 다른 작업이 가능하다.

     

    IOCP 개요

     

    IOCP는 윈도우에서 제공하는 일반적인 I/O 모델 중 최고 성능을 갖고 있다 한다.

    커널 객체(운영체제의 핵심적인 기능 프로세스, 스레드 메모리 관리 기능 제공하는 객체)이다. 

    https://docs.microsoft.com/ko-kr/windows/win32/sysinfo/kernel-objects

     

    커널 개체 - Win32 apps

    커널 개체 핸들은 프로세스 마다 다릅니다.

    docs.microsoft.com

     

    위 MSDN에 IOCP가 있는 것을 볼 수 있다.

    IOCP 시스템을 관리하는 객체를 커널이 만들어서 우리에게 HANDLE를 넘겨주는 것이다.

     

    IOCP 자료구조 설명
    Device list hDevice, CompletionKey로 구성되어 키값으로 값을 얻을 수 있게됨
    IO Completion Queue FIFO로 앞에서 부터 자료를 빼서 처리, I/O가 완료 되면 정보(이벤트)를 저장함
    Waiting Thread Queue LIFO로 마지막에 사용된 스레드를 다시 사용함, 스레드 ID가 저장됨  스레드 풀 역활
    Released Thread List Waiting Thread Queue(스레드 풀)에서 꺼내온 쓰레드 정보 
    Paused Thread List Release쓰레드가 Suspend상태되면 저장되고, Suspend상태가 해제되면 다시 Released Thread List로 올라감

     

    IOCP 특징 중 하나가, 쓰레드 풀로 쓰레드를 재사용하는 것인데,

    IOCP 본인의 스레드 큐를 가지고 작업을 하고 완료된 IO가 있다면 알려준다.

     

    재사용이 가능한 스레드를 유지하는 스레드 풀링은

    WSASend/ WSARecv같은 비동기 입출력 함수를 쓰면

    스레드 내부적으로 APC 큐가 생성이된다. 여기에 완료된 결과를 저장하는데

    APC큐는 자동으로 생성되고 파괴된다. 그리고 그 큐는 본인 스레드에서만

    확인이 가능하는데, 관리자 역활을 하는 IOCP는 모든걸 확인이 가능하다.

     

    예를들어 관리자(IOCP)가 일꾼(1번 입출력), 일꾼(2번 입출력) 중

    1번 일꾼의 IO작업이 끝났다하면 이를 처리하기 위해 Waiting Thread Queue에

    가장 최근에 들어온 스레드를 깨운다. I/O가 완료될때마다 기다리고 쓰레드가 알아서 I/O작업을 해

    성능이 좋다. -> 스레드 풀

     

    또한, 특정 스레드에서 작업을 하던간에

    IOCP 큐자체가 기타 스레드 결과가 저장이 된다.

    즉, A 스레드에서 작업해도 B스레드에서도 확인이 가능하다.

     

    1. IOCP 객체 핸들 생성

    1
    2
    3
    4
    5
    6
    HANDLE WINAPI CreateIoCompletionPort(
      _In_      HANDLE FileHandle,
      _In_opt_  HANDLE ExistingCompletionPort,
      _In_      ULONG_PTR CompletionKey,
      _In_      DWORD NumberOfConcurrentThreads
    );
    cs

     

    CreateIoCompletionPort 함수는 세 가지의 역활을 한다.

     - IOCP 포트만 생성

     - 기존 IOCP 포트와 파일,소켓과 연결

    -  생성 및 연결 모두 수행

     

    처음 IOCP 객체 핸들 생성할때는 이런 식으로 생성한다.

    1
    2
        g_hIOCP = CreateIoCompletionPort(
            INVALID_HANDLE_VALUE, 000);
    cs

     

    마지막 인자 NumberOfConcurrentThreads는 0이면 시스템 프로세서에 있는 만큼

    스레드를 허용하는 것이다. CPU 갯수보다 일부로 낮게 넣지 않는 이상, 0을 넣으면 된다.

     

    2. IOCP와 소켓(파일) 연결

    위의 CreateIoCompletionPort()는 세가지 역활을 할 수 있다고 위에 있다.

    1
    CreateIoCompletionPort((HANDLE)clientSock, m_hIOCP, (ULONG_PTR)user, 0);
    cs

     

     첫번째 인자 FileHandle에 소켓을 넣고 기존에 생성한 IOCP 핸들을 넣는다.

    그리고 세번째 인자는 키값으로 입출력 완료가 되었을때 어떤 소켓이 완료

    되었는지 알 수 있다.

     

    3. WorkerThread 생성

    IOCP는 스레드가 사실 없어도 동작을 한다. 하지만 IOCP가 여러 스레드를

    동시에 컨트롤 한다는 이점으로 성능을 위해서는 필수이다.

     

     쓰레드 갯수는 MSDN에서 CPU*2+1이란 숫자를 추천하는데,

    이는 스레드가 Suspend되어 대기상태에 빠졌을때, 새로운 스레드를 꺼내기

    위함이다라고 이해했다.

    Iocp 기본 구조 이해 (slideshare.net)

     

    Iocp 기본 구조 이해

    IOCP IO Completion Port NHN NEXT 남현욱

    www.slideshare.net

     

     

    4. GetQueuedCompletionStatus()

     

    완료큐에 완료된 입출력이 있다면 WorkerThread에서 작업을 하게 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    DWORD WINAPI WorkerThread(LPVOID param)
    {
        KLobbyServer* pServer = (KLobbyServer*)param;
        DWORD dwTransfer;
        ULONG_PTR KeyValue;
        OVERLAPPED* pOverlapped;
        while (1)
        {
            //지정된 킬이벤트가 신호를 받았다면 break
            if (WaitForSingleObject(pServer->m_hKillEvent, 1== WAIT_OBJECT_0)
            {
                break;
            }
            //완료큐에 데이터가 있으면 작업시작
            BOOL bRet = ::GetQueuedCompletionStatus(
                pServer->m_hIOCP, &dwTransfer, &KeyValue, &pOverlapped, 1);
     
            KNetworkUser* pUser = (KNetworkUser*)KeyValue;
            KOV* pOV = (KOV*)pOverlapped;
     
            if (bRet == TRUE && pUser && pOV)
            {
                //작업
                if (pOV->type == 1000 && dwTransfer == 0)
                {
                    if(pUser->m_bConnect == true)
                    pUser->m_bConnect = false;
                }
                else if(pOV->type == 1000)
                {
                    pUser->Dispatch(dwTransfer, pOV);
                }
            }
            else
            {
                //오류
                if (GetLastError() != WAIT_TIMEOUT)
                {
                    ::SetEvent(pServer->m_hKillEvent);
                    break;
                }
            }
        }
        return TRUE;
    }
    cs

     

    //로비서버 IOCP 적용 예시 

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    #include "KLobbyServer.h"
    DWORD WINAPI WorkerThread(LPVOID param)
    {
        KLobbyServer* pServer = (KLobbyServer*)param;
        DWORD dwTransfer;
        ULONG_PTR KeyValue;
        OVERLAPPED* pOverlapped;
        while (1)
        {
            //지정된 킬이벤트가 신호를 받았다면 break
            if (WaitForSingleObject(pServer->m_hKillEvent, 1== WAIT_OBJECT_0)
            {
                break;
            }
            //완료큐에 데이터가 있으면 작업시작
            BOOL bRet = ::GetQueuedCompletionStatus(
                pServer->m_hIOCP, &dwTransfer, &KeyValue, &pOverlapped, 1);
     
            KNetworkUser* pUser = (KNetworkUser*)KeyValue;
            KOV* pOV = (KOV*)pOverlapped;
     
            if (bRet == TRUE && pUser && pOV)
            {
                //작업
                if (pOV->type == 1000 && dwTransfer == 0)
                {
                    if(pUser->m_bConnect == true)
                    pUser->m_bConnect = false;
                }
                else if(pOV->type == 1000)
                {
                    pUser->Dispatch(dwTransfer, pOV);
                }
            }
            else
            {
                //오류
                if (GetLastError() != WAIT_TIMEOUT)
                {
                    ::SetEvent(pServer->m_hKillEvent);
                    break;
                }
            }
        }
        return TRUE;
    }
     
    bool KLobbyServer::Init(int port)
    {
        KServer::Init(port);
        m_hIOCP = CreateIoCompletionPort(
                INVALID_HANDLE_VALUE, 000);
     
        SYSTEM_INFO system_info;
     
        GetSystemInfo(&system_info);
     
        //쓰레드 여러개 생성
        for (int i = 0; i < system_info.dwNumberOfProcessors *2; i++)
        {
            DWORD id;
            //자기 자신의 서버 인자를 넘김. this 
            m_hWorkThread[i] = CreateThread(00, WorkerThread, this0&id);
        }
     
        return true;
    }
     
    //게임서버는 new delete를 막아 
    //동적할당을 미리 메모리 메니져로 만들어서 해야함
    bool KLobbyServer::AddUser(SOCKET clientSock, SOCKADDR_IN clientAddr)
    {
        KNetworkUser* user = new KNetworkUser();
        user->Set(clientSock, clientAddr);
     
        u_long on = 1;
        ioctlsocket(clientSock, FIONBIO, &on);
     
        //유저리스트 추가
        EnterCriticalSection(&m_cs);
            m_UserList.push_back(user);
        LeaveCriticalSection(&m_cs);
     
        //비동기 작업을 해야하니까 유저가 접속이되면 리시브를 걸어놔라
        //유저에 대한 포인터를 넘긴다.
        ::CreateIoCompletionPort((HANDLE)clientSock, m_hIOCP, (ULONG_PTR)user, 0);
        //WSARecv를 건다. 
        user->Recv();
     
        //delete user;
        return true;
    }
     
    bool KLobbyServer::Run()
    {
        while (1)
        {
            //임계구역
            EnterCriticalSection(&m_cs);
     
            //패킷 타입을 판별해서 해당 맞는 작업을 하면 됨
            std::list<KPacket>::iterator iter_packet;
            for (iter_packet = m_lPacketPool.begin();
                iter_packet != m_lPacketPool.end();)
            {
                switch ((*iter_packet).m_uPacket.ph.type)
                {
                    case PACKET_USER_POSITION:
                    {
                        
                    }break;
                    case PACKET_CHAT_MSG:
                    {
     
                    }break;
                }
            }
     
     
            //주기적인 동기화
            for (KNetworkUser* user : m_UserList)
            {
                if (user->m_lPacketPool.size() > 0)
                {
                    Broadcast(user);
                }
            }
            //커넥트가 false면 나가는 처리까지
            std::list<KNetworkUser*>::iterator user_iter;
            for (user_iter = m_UserList.begin();
                user_iter != m_UserList.end();)
            {
                if ((*user_iter)->m_bConnect == false)
                {
                    (*user_iter)->Disconnect();
                    delete (*user_iter);
                    user_iter = m_UserList.erase(user_iter);
                    std::cout <<"\nCurrent : " << m_UserList.size() <<" 명 접속중.."<<std::endl;
                }
                else
                {
                    user_iter++;
                }
            }
            LeaveCriticalSection(&m_cs);
        }
        return true;
    }
     
    bool KLobbyServer::Release()
    {
        CloseHandle(m_hIOCP);
        KServer::Release();
        return true;
    }
     
    cs

     

    COMMENT
     
    01
    17

    IMGUI 한글 깨짐

    Directx 11에 툴링용으로 Imgui를 적용하였다.

    하지만 Imgui는 기본적으로 멀티 바이트용으로 한글이 출력이 되지 않는다.

    ???로 출력이 된다.  깃헙에 제시된 여러 해결방법이 있지만,

    나에게 제일 간단한 방법을 찾았다.

     

    1. ImGuiIO 객체의 유니코드 한글 폰트를 추가한다.

     

     
    ImGuiManager::ImGuiManager()
    {
        IMGUI_CHECKVERSION();
        ImGui::CreateContext();
        ImGui::StyleColorsDark();
        ImGuiIO& io = ImGui::GetIO();
        io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\malgun.ttf"18.0f, NULL, io.Fonts->GetGlyphRangesKorean());
    }
     
    ImGuiManager::~ImGuiManager()
    {
        ImGui::DestroyContext();
    }
    cs

     

    ImGuiIO& io = ImGui::GetIO();
    io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\malgun.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesKorean());

     

     

    굳이 ImGui 매니져를 사용하지 않더라도, 본인이 ImGui를 초기화하는 부분에서

    호출해주면 된다. 나는 폰트는 제일 흔한 마이크로소프트 맑은 고딕을 사용했다.

    io.Fonts->GetGlyphRangesKorean()로 한국어의 필요한 특정 그리프 범위를 지정해준다. 

     

    2. (u8"한글 출력")

     

     
        #pragma region IMGUI INTERFACE
        if (ImGui::Begin("Chatting Box"))
        {
            
            ImGui::TextColored(color,isConnect.c_str());
            //연결이 안되어 있으면 연결 재 시도
            if (!m_Net.m_bConnect)
            {
                if (ImGui::Button(u8"연결 재시도"))
                {
                    if (m_Net.Connect(g_hWnd, SOCK_STREAM, 10000, IP_DD))
                    {
                        m_bConnect = true;
                    }
                }
            }
            else
            {
                ImGui::BeginChild(u8"채팅창", ImVec2(0-ImGui::GetItemsLineHeightWithSpacing() - 15));
                ImGui::Text(chatItems);
                ImGui::EndChild();
                ImGui::Dummy(ImVec2(0.0f, 5));
                ImGui::InputText("", buffer, sizeof(buffer));
                ImGui::SameLine();
                if (ImGui::Button("Send"))
                {
                    char clear[MAX_PATH] = { 0, };
                    KPacket kPacket(PACKET_CHAT_MSG);
                    kPacket << 123 << "Test" << (short)12 << buffer;
     
                    //리턴 값이 0보다 작으면 전송되지 않았음
                    if (m_Net.SendMsg(m_Net.m_Sock, kPacket.m_uPacket) < 0)
                    {
                        ZeroMemory(&chatItems, sizeof(char* 2048);
                        strcat(chatItems, "Error\n");
                        m_Net.m_bConnect = false;
                    }
     
                    strcpy(buffer, clear);
                }
            }
        }
        ImGui::End();
    #pragma endregion
    cs

     

    한글 부분에 u8를 붙여서 UTF-8 (유니코드) 글자임을 명시한다.

    아마도 내부적으로 MultiBytetoWideChar() <멀티 바이트-> UTF-8>을 쓰고 있지 않을까 싶다.

    이렇게 간단하게 imgui에서 한글 출력을 할 수가 있다. 

     

    참고로, Win32 API로 

    유니코드 -> 멀티바이트 : WideCharToMultiByte()

    멀티 바이트 -> 유니코드 :  MultiBytetoWideChar()이다.

    https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte

     

    WideCharToMultiByte function (stringapiset.h) - Win32 apps

    Maps a UTF-16 (wide character) string to a new character string.

    docs.microsoft.com

     

    3. 결과

     

     

     

     

    COMMENT
     
    01
    14

    C언어에서는 처리할 파일을 File 구조체와 

    파일을 여는 fopen(경로 및 파일명, 모드)

    파일을 읽고 쓰는 fread(), fwrite() 

    파일을 닫는 fclose()등 파일 입출력을 했었다.

     

    WinApi에서도 파일 입출력 함수가 있는데, 

    CreateFile(), ReadFile(), WriteFile(), CloseHandle()

    위와 같은 역활을 한다. 다만, 윈도우 운영체제와 밀접하고

    비동기 파일 입출력을 지원한다는 큰 강점이 있다.

    소켓을 붙이면 소켓 입출력이된다.

     

    비동기 파일 입출력의 필요성

     

    유튜브 영상을 볼때, 영상을 모두 로드하고 영상을 시청할려하면,

    영상을 로드하는 동안은, 영상을 볼 수가 없다. 사용자는 버근가 싶을 것이다.

    그래서 비동기 I/O 사용한다. 데이터 수신과 영상 출력이 동시에 일어난다.

    WINAPI의 파일입출력은 이러한 비동기 파일 입출력을 지원한다. 

     

    CreateFile() 함수 원형

    1
    2
    3
    4
    5
    6
    7
    8
    9
    HANDLE CreateFile(
     [in]           LPCWSTR               lpFileName, //만들거나 열 파일 또는 파일의 경로 및 이름
      [in]           DWORD                 dwDesiredAccess, //원하는 접근 모드,  GENERIC_READ , GENERIC_WRITE
      [in]           DWORD                 dwShareMode, //다른 프로세스가 파일 또는 장치를 연 상태에서 공유 가능하게 할 것인지
      [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, //파일의 보안속성 지정함, 자식한테 상속 가능, 옵션값임
      [in]           DWORD                 dwCreationDisposition, //생성 관련 속성, 덮어쓰기나 새로만들기 등
      [in]           DWORD                 dwFlagsAndAttributes, //장치 속성 플래그, 읽기전용, 숨김파일, 비동기 I/O등 플래그 세움
      [in, optional] HANDLE                hTemplateFile //템플릿으로 동일한 특성 새파일 만들고 싶으면 쓰임, 일반적으로 NULL
    );
    cs

     

    WriteFile() 함수 원형

    1
    2
    3
    4
    5
    6
    7
    8
    BOOL WriteFile(
      [in]                HANDLE       hFile, //CreateFile에서 할당 해 줬던 핸들을 입력 해 준다.
      [in]                LPCVOID      lpBuffer, // write할 데이터가 들어 있는 버퍼 포인터 
      [in]                DWORD        nNumberOfBytesToWrite, // write할 데이터의 사이즈
      [out, optional]     LPDWORD      lpNumberOfBytesWritten, // 작성된 바이트 수를 수신하는 변수,
                                                               // 비동기에서 잠재적으로 잘못될수 있어 Null로 설정
      [in, out, optional] LPOVERLAPPED lpOverlapped // 비동기 Overlapped 구조 포인터 자리
    );
    cs

     

    ReadFile() 함수 원형

    1
    2
    3
    4
    5
    6
    7
    BOOL ReadFile(
      [in]                HANDLE       hFile, // 읽고자 하는 파일의 핸들 
      [out]               LPVOID       lpBuffer, // 읽어올 저장 버퍼의 포인터
      [in]                DWORD        nNumberOfBytesToRead, // 실제 읽어오는 바이트 수
      [out, optional]     LPDWORD      lpNumberOfBytesRead, // 얼마나 읽고 쓰여졌는가 결과값이 저장되는 주소값
      [in, out, optional] LPOVERLAPPED lpOverlapped // 비동기 Overlapped 구조 포인터 자리
    );
    cs

     

    파일에 읽고 쓸 파일 HANDLE을 만들어서 함수처리하면 된다.  

    주요 함수에 대해서 알았으니 파일 입출력을 해보자. 

     

    동기 파일 입출력 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    int main()
    {
        setlocale(LC_ALL, ""); // Korean해도 되지만 공백은 시스템 지역설정으로 따르게 한다.
        HANDLE hFile = CreateFile(L"test.txt", GENERIC_WRITE
            , 0NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        //CREATE_ALWAYS : 파일 덮어씀
        if (hFile != NULL)
        {
            CHAR buffer[] = "유니코드든 싱글바이트든 해석할때가 중요하다.";
            DWORD dLength = sizeof(buffer);
            DWORD dWritten;
            bool bRet = WriteFile(hFile, buffer, dLength, &dWritten, NULL);
     
            if (bRet == true)
            {
                std::wcout << L"출력성공" << std::endl;
            }
        }
        CloseHandle(hFile);
     
     
        HANDLE hFile_Read = CreateFile(L"test.txt", GENERIC_READ
            , 0NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile_Read != NULL)
        {
            WCHAR buffer[512= { 0, };
            DWORD dLength = sizeof(buffer);
            DWORD dWritten;
            bool bRet = ReadFile(hFile_Read, buffer, dLength, &dWritten, NULL);
     
            if (bRet == true)
            {
                std::wcout << L"읽어오기 성공" << std::endl;
            }
        }
        CloseHandle(hFile_Read);
    }
    cs

     

    1. 쓰기용 핸들을 만든다. CreateFile()

     

    2. 쓰기용 핸들에 파일에 쓴다. 버퍼를 넣는다. WriteFile()

     

    3. 핸들을 닫는다. 읽기 ReadFile()도 유사하다.

     

    4. 결과 

     

    이러한 ReadFile(), WriteFile() 입출력 함수는 일반적으로

    입출력이 끝날때까지 Blocked 상태를 유지한다. 

     

    만약 만화책을 보고 그림을 따라 그린다고 상상을 해보자.

    만화책 한번보고, 기억하고, 도화지에 그림을 그리고

    다시 만화책 한번보고(Blocked) 기억하고(CPU 사용 코드) 도화지에 그림을 그리고(Blocked)....

    반복하는 작업이 있다고 생각해보자.

     

     

    도화지에 만화책을 중첩시켜 그림을 그리는 작업은 어떨까!

    그게 바로 Overlapped(중첩) 입출력이다. 훨씬 속도가 빠르다.

    스레드를 쓰지 않고도 스레드를 쓰는 효과가 난다.

     

    비동기 파일 입출력 Overlapped 입출력 예시

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    #define BlockSizes 2048
    #include <windows.h>
    #include <iostream>
     
    void main()
    {
        setlocale(LC_ALL, "");
        WCHAR* pwbuffer = NULL;
        LARGE_INTEGER fileSize;
        OVERLAPPED ol_Read = { 0, };
        OVERLAPPED ol_Write = { 0, };
     
        //핸들 반환 FILE_FLAG_OVERLAPPED으로 비동기 파일 입출력 
        //기존에 있는 파일을 읽어옴
        HANDLE hFile = CreateFile(L"test.JPG", GENERIC_READ | GENERIC_WRITE, 0,
            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
        //실패하면 초기값이 잡힘
        if (hFile == INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFile);
            return;
        }
        // 핸들 반환 FILE_FLAG_OVERLAPPED으로 비동기 파일 입출력
        // 복사하는 핸들
        HANDLE hFileCopy = CreateFile(L"test_copy.JPG", GENERIC_WRITE, 0
            NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
        //실패하면 초기값이 잡힘
        if (hFileCopy == INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFileCopy);
            return;
        }
     
        //읽어온 파일 사이즈를 체크
        GetFileSizeEx(hFile, &fileSize);
     
        DWORD dwRead;
        DWORD dwWritten;
        DWORD dwTotalWrite = 0;
     
        //sector의 크기 배수 일반적으로 512
        pwbuffer = new WCHAR[BlockSizes];
        int i = 0;
        while(1)
        {
            //Temp 버퍼
            ZeroMemory(pwbuffer, BlockSizes);
            
            //비동기 로드
            //Overlapped 구조체 오프셋
            ol_Read.Offset = i * BlockSizes;
            ol_Read.OffsetHigh = 0;
     
            BOOL ret = ReadFile(hFile, pwbuffer, BlockSizes, &dwRead, &ol_Read);
            BOOL bPending = FALSE;
            if (ret == FALSE)
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_IO_PENDING)
                {
                    std::wcout << L"읽기 중" << std::endl;
                }
                else
                {
     
                }
            }
            BOOL bReturn = GetOverlappedResult(hFile, &ol_Read, &dwRead, FALSE);
            if (bReturn == TRUE)
            {
                std::wcout << L"읽기 완료" << std::endl;
                bPending = FALSE;
            }
            else
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_IO_INCOMPLETE)
                {
                    std::wcout << L".." << std::endl;
                }
                else
                {
                    std::wcout << L"읽기 완료2" << std::endl;
                    bPending = FALSE;
                }
            }
            //비동기 출력
            //Overlapped 구조체 오프셋
            ol_Write.Offset = i * BlockSizes;
            ol_Write.OffsetHigh = 0;
     
            ret = WriteFile(hFileCopy, pwbuffer, dwRead, &dwWritten, &ol_Write);
            if (ret == FALSE)
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_IO_PENDING)
                {
                    std::wcout << L"쓰기 중" << std::endl;
                }
                else
                {
     
                }
            }
            bReturn = GetOverlappedResult(hFileCopy, &ol_Write, &dwWritten, FALSE);
            if (bReturn == TRUE)
            {
                std::wcout << L"쓰기 완료" << std::endl;
                bPending = FALSE;
            }
            else
            {
                DWORD dwError = GetLastError();
                if (dwError == ERROR_IO_INCOMPLETE)
                {
                    std::wcout << L"..";
                }
                else
                {
                    std::wcout << L"쓰기 완료2" << std::endl;
                    bPending = FALSE;
                }
            }
            dwTotalWrite += dwWritten;
     
            if (fileSize.LowPart == dwTotalWrite)
            {
                break;
            }
            std::wcout << (fileSize.LowPart- dwTotalWrite) << std::endl;
            i++;
        }
        CloseHandle(hFile);
        CloseHandle(hFileCopy);
    }
     
    cs

     

    위 코드는 블록단위로 파일을 복사하는 작업을 비동기로 처리한다. 

    블록단위로 읽고 복사(쓰고)를 반복한다.. 다 복사가 될때까지. 

    Offset을 이용해, 블록단위로 끊어 읽고, dwRead 읽은 만큼만 Write하는 구조이다.

     

     

    Overlapped 구조체를 이용하여 비동기적으로 하나 이상의 I/O 작업을 수행

    할 수 있다. Overlapped 구조체 내용은 파일 포인터의 Offset과 이벤트가 있다.

     

     

    오버랩 플래그를 달아준다. FILE_FLAG_OVERLAPPED

     

     

    WriteFIle, ReadFile의 마지막 옵션값, 오버랩 구조체 자리에 구조체를 넣고

    GetOverlappedResult의 결과를 받는다. 

     

    1
    2
    3
    4
    5
    6
    BOOL GetOverlappedResult(
      [in]  HANDLE       hFile, // 파일 핸들
      [in]  LPOVERLAPPED lpOverlapped, // Overlapped 구조체 
      [out] LPDWORD      lpNumberOfBytesTransferred, // 전송 양
      [in]  BOOL         bWait // 기다릴지의 유무
    );
    cs

     

    GetOverlappedResult의 마지막 인자의 이름은 bWait로 

    True이면 동기작업을 하게 되고 FALSE경우 비동기 작업이된다.

    비동기처리니까 당연히 예외처리를 해줘야한다. 

     

    결과

     

    COMMENT
     
    01
    11

    https://dlemrcnd.tistory.com/58?category=528341 

     

    Network Programming - 쓰레드(Thread)와 프로세스(Process) , 클라이언트/서버 멀티쓰레드 C++ 채팅 프로그

    프로세스(Process) 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램이다. Code, Data, Stack, Heap의 구조로 되어 있는 독립된 메모리 영역이다. Code 텍스트, 코드영역 Data 전역변수, 정적변수, 배

    dlemrcnd.tistory.com

     

    동기화 문제

    스레드 A와 스레드 B로 왔다 갔다(스위칭)하면서 경쟁하는데

    둘의 조건이 안맞아 스레드끼리 서로 대기하는 교착상태(DeadLock)이 발생한다.

     

    교착상태 : 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에

    아무것도 완료되지 못하는 상태를 의미함,

     

    위키피디아에서는 예시로 사다리에서 한 사람은 올라가려고 하고

    한 사람은 내려갈라고 하는데, 서로 비켜줄 때까지 기다리는 걸로

    예시를 들었다.

     

    실행 순서를 제어해야 하는데, 이런 기술들을 동기화(Synchronization)라고 한다. 

     

    동기화 기법의 분류

     

    유저 모드는 사용자, 즉 프로그래머의 처리이다. 외부에서 관여하지 못한다.

    장점으로는 프로그래밍하기 쉽고, 속도가 빠르다. 단점으로는 제한된 기능을 갖는다.

     

    커널 모드는 프로세스 관리하는 운영체제가 관여해 외부 프로그램이 접근 가능하다. 

    장점으로는 Deadlock 문제를 좀 더 명확히 막고, 둘 이상의 프로세스 간의 존재하는

    스레드 간의 동기화도 가능하다. 운영체제가 관여하기 때문이다. 

    단점으로는 실행 속도의 저하가 발생한다

     

    분류 종류
    유저 모드 크리티컬 섹션, 인터락 함수
    커널 모드 뮤텍스(Mutex), 세마포어(Semaphore), 이벤트 (Event)

    유저 모드 - 크리티컬 섹션

    내부적으로 인터락 함수를 갖고 있다고 한다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void InitializeCriticalSection(
      [out] LPCRITICAL_SECTION lpCriticalSection
    );
     
    // lpCriticalSection : 초기화할 크리티컬 섹션 변수의 포인터를 전달
    // 크리티컬 섹션 초기화
     
    void EnterCriticalSection(
      [out] LPCRITICAL_SECTION lpCriticalSection
    );
    // 임계영역에 들어간다. 다른 스레드는 동일 리소스 접근 못함
     
    void LeaveCriticalSection(
      [out] LPCRITICAL_SECTION lpCriticalSection
    );
    // 임계영역에서 떠남 . leave하지 않으면 영원히 데드락임
    // 대기중인 스레드가 작업이 안끝났구나 해서 무한 대기함
     
    void DeleteCriticalSection(
      [out] LPCRITICAL_SECTION lpCriticalSection
    );
     
    // 크리티컬 섹션 해제, 
    cs

    Enter~ Leave로 감싸면 크리티컬 한 보호 상태가 된다. 

     

    유저 모드 - 인터락(Interlocked)

    멀티스레드에서 안전하게 변숫값을 조작 하는 함수를 인터락이라한다.

    예를 들어 ++-- 증감 연산자는 개발자 입장에서 한 줄이지만,

    기계가 해석하는 어셈블리어에서는 한 줄이 아니다. 그 사이에서 스위칭이 발생함.

    그래서 별도의 증가함수가 필요하다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    LONG InterlockedIncrement(
      LONG volatile *Addend
    );
     
    LONG InterlockedDecrement(
      LONG volatile *Addend
    );
     
    LONG InterlockedAdd(
      LONG volatile *Addend,
      LONG          Value
    );
     
    LONG InterlockedExchange(
      LONG volatile *Target,
      LONG          Value
    );
     
    LONG InterlockedCompareExchange(
      LONG volatile *Destination,
      LONG          ExChange,
      LONG          Comperand
    );
    cs

     


     

    커널 모드 - 뮤텍스 (Mutex)

    뮤 텍스는 Mutual Exclusion의 줄임말. 상호 배제라는 뜻이다.

    상호 배제는 두 스레드가 동시에 소유할 수 없다는 것을 의미한다.

     

    뮤텍스는 신호상태(signaled)와 비신호상태(non-signaled)를 띤다.

    신호상태는 스레드의 실행을 허가하고 비신호는 허가하지 않는다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //뮤텍스 생성함수 
    HANDLE CreateMutex(
      [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes, // 보안 관련 특성 전달 (NULL)
      [in]           BOOL                  bInitialOwner, // 초기 소유된 스레드 (바로 Signaled)
      [in, optional] LPCSTR                lpName        // 객체 이름
    );
     
    //뮤텍스 반환 함수 Mutex 상태를 non-signaled -> signaled 로 바꿈
    BOOL ReleaseMutex(
      [in] HANDLE hMutex
    );
     
    //뮤텍스 소멸 함수
    BOOL CloseHandle(
      [in] HANDLE hObject
    );
     
    cs

     

    대기 함수

    스레드의 실행을 블록 하여 대기시킨다. 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 하나의 동기화 객체가 신호상태가 되기를 기다린다.
    DWORD WaitForSingleObject(
      [in] HANDLE hHandle,
      [in] DWORD  dwMilliseconds // INFINITE로 하면 무한정 기다림
    );
     
    //복수의 동기화 객체가 신호상태가 되길 기다림
    DWORD WaitForMultipleObjects(
      [in] DWORD        nCount,
      [in] const HANDLE *lpHandles,
      [in] BOOL         bWaitAll,
      [in] DWORD        dwMilliseconds
    );
    cs

     

    뮤텍스 예시

    서버에서 유저 리스트를 업데이트할 때, 다른 스레드에서 대기 함수로 대기하는 경우이다.

     

    핸들 선언

     

    뮤텍스 핸들을 선언한다. 

     

    뮤텍스 생성

     

    두개의 스레드 생성

     

    두 개의 스레드에서 전역 변수를 안전하게 접근하기 위해 뮤텍스를 사용한다.

    고로 , 스레드를 생성한다.

     

    아래는 스레드 시작 함수들의 내용이다. 

     

    리시브 스레드
    전송 스레드

     

    대기 함수로 뮤텍스 핸들을 넣어 ReleaseMutex가 일어날 때까지

    다른 스레드는 대기한다. 그러면, 각 스레드는 컨텍스트 스위칭을

    정신없이 하는 와중에도 유저 리스트(전역 변수)에 접근할 때는

    unsignaled이 돼서, 같은 변수를 접근하려는 다른 스레드는 대기를 한다. 

     

    Sample.cpp

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    #include "Sample.h"
     
    //서버
    CRITICAL_SECTION        g_CS;
    HANDLE                    g_hMutex;
    std::list<KNetworkUser> g_UserList;
    KNetwork                g_Net;
     
    int BroadCast(KNetworkUser& user)
    {
        if (user.m_lPacketPool.size() > 0)
        {
            std::list<KPacket>::iterator iter;
            for (iter = user.m_lPacketPool.begin();
                iter != user.m_lPacketPool.end();)
            {
                for (KNetworkUser& senduser : g_UserList)
                {
                    int iRet = g_Net.SendMsg(senduser.m_Sock, (*iter).m_uPacket);
                    if (iRet <= 0)
                    {
                        senduser.m_bConnect = false;
                    }
                }
                iter = user.m_lPacketPool.erase(iter);
            }
        }
        return 1;
    }
    DWORD WINAPI RecvThread(LPVOID param)
    {
        SOCKET sock = (SOCKET)param;
        while (1)
        {
            //스레드가 특정 시그널이 발생할때까지 기다림
            WaitForSingleObject(g_hMutex, INFINITE);
     
            std::list<KNetworkUser>::iterator iter;
            for (iter = g_UserList.begin();
                iter != g_UserList.end();)
            {
                int iRet = g_Net.RecvUser(*iter);
                //0보다 작거나 같음, 받아온 데이터가 없거나, 에러일 경우 
                if (iRet <= 0)
                {
                    iter = g_UserList.erase(iter);
                }
                else
                {
                    iter++;
                }
            }
            //운영체제한테 제어권 넘겨줌
            ReleaseMutex(g_hMutex);
            //의도적으로 Context Switching
            Sleep(1);
        }
    }
     
    DWORD WINAPI SendThread(LPVOID param)
    {
        SOCKET sock = (SOCKET)param;
        while (1)
        {
            //스레드가 특정 시그널이 발생할때까지 기다림
            WaitForSingleObject(g_hMutex, INFINITE);
     
            std::list<KNetworkUser>::iterator iter;
            for (iter = g_UserList.begin();
                iter != g_UserList.end();)
            {
                //전체에게 보내줌
                int iRet = BroadCast(*iter);
                //0보다 작거나 같음, 받아온 데이터가 없거나, 에러일 경우 
                if (iRet <= 0)
                {
                    iter = g_UserList.erase(iter);
                }
                else
                {
                    iter++;
                }
            }
            //운영체제한테 제어권 넘겨줌
            ReleaseMutex(g_hMutex);
            //의도적으로 Context Switching
            Sleep(1);
        }
    }
     
    void main()
    {
        //유저모드 동기화 모드 크리티컬섹션
        //InitializeCriticalSection(&g_CS);
        g_hMutex = CreateMutex(NULL, FALSE, NULL);
        g_Net.InitNetwork();
        g_Net.InitServer(SOCK_STREAM,10000, nullptr);
     
        SOCKADDR_IN clientAddr;
        int iLen = sizeof(clientAddr);
     
        std::cout<< "Server Start." << std::endl;
     
        //non blocking socket 0이면 블락킹 소켓
        u_long on = 1;
        ioctlsocket(g_Net.m_Sock, FIONBIO, &on);
     
        DWORD ThreadID_Recv;
        //데이터 받는 스레드
        HANDLE hThreadRecv = ::CreateThread(
            0,
            0,
            RecvThread, // 시작함수를 지정
            (LPVOID)g_Net.m_Sock, // 시작함수 인자값
            0// 바로 시작할것인지 플래그
            &ThreadID_Recv // 스레드 아이디 반환
        );
        CloseHandle(hThreadRecv);
        
        DWORD ThreadID_Send;
        HANDLE hThreadSend = ::CreateThread(
            0,
            0,
            SendThread,
            (LPVOID)g_Net.m_Sock,
            0,
            &ThreadID_Send
        );
        CloseHandle(hThreadSend);
     
        //메인 스레드
        while (1)
        {
            SOCKET clientSock = accept(g_Net.m_Sock,
                (sockaddr*)&clientAddr, &iLen);
     
            if (clientSock == SOCKET_ERROR)
            {
                int iError = WSAGetLastError();
                if (iError != WSAEWOULDBLOCK)
                {
                    std::cout << "ErrorCode=" << iError << std::endl;
                    break;
                }
            }
            //클라이언트가 접속 시 시작함
            else
            {
                KNetworkUser user;
                user.set(clientSock, clientAddr);
                //메인 스레드가 특정 시그널이 발생할때까지 기다림
                WaitForSingleObject(g_hMutex, INFINITE);
                //들어온 유저 전역 리스트에 추가
                g_UserList.push_back(user);
                ReleaseMutex(g_hMutex);
                std::cout
                    << "ip =" << inet_ntoa(clientAddr.sin_addr)
                    << "port =" << ntohs(clientAddr.sin_port)
                    << "  " << std::endl;
                u_long on = 1;
                ioctlsocket(clientSock, FIONBIO, &on);
                std::cout << std::to_string(g_UserList.size())<< " 명 접속중." << std::endl;
            }
            Sleep(1);
        }
        closesocket(g_Net.m_Sock);
        WSACleanup();
     
        CloseHandle(g_hMutex);
    }
    cs

     

     

     

    결과

     

    세마포어(Semaphore)

    세마포어는 뮤텍스와 유사하지만 카운팅이 가능한 함수로

    세마포어가 0이 되어야 non-signaled이 된다. 

    예를 들어 크롬에서 다운로드를 할 때 3개를 동시에 다운로드 중이라면

    4번째부터는 대기 상태에 들어가는 메커니즘과 유사하다.

     

    이벤트 (Event)

    어떤 사건이 일어났음을 알리는 동기화 객체이다.

    윈도우의 메시지와 유사한 기능으로 자동리셋 이벤트와 수동 리셋 이벤트가 있다.

    자동은 대기하던 스레드가 해제되면 자동으로 리셋이 되고, 수동은 자동으로 리셋이 안된다.

    프로그래밍 적으로 해줘야 함. 

    COMMENT
     
    01
    10
    COMMENT
     
    01
    10

    WSAAsyncSelect

     

    윈도우(창)에서 사용하는 기본 모델이다.

    Async은 비동기적 이라는 뜻이 있다.

    동기적 서로 다른 주체가 하는 작업이 작업 시작, 종료시간이 연관있을때 작업 지시가 주체
    비동기적 서로 다른 주체가 하는 작업이 작업 시작, 종료시간과는 관계가 없을 때
    블록킹 하나의 작업이 흐름을 막는것 작업 처리가 주체
    논블록킹 하나의 작업이 흐름을 막지 않는것

     

    윈속의 소켓 이벤트를 윈도우 메시지를 통해서 비동기적으로 받는다.

    윈도우 메세지를 통해 성공 여부를 판단해야한다. 메세지 큐에 의존하여

    소켓의 처리가 이루워져 많은 수의 소켓 이벤트를 처리해야하는 경우

    큐가 고갈될 수 있다. 그래서 클라이언트의 모델에 적합하다.

     

    1
    2
    3
    4
    5
    int WSAAsyncSelect(
                SOCKET socket,// 입출력 상황을 체크할 소켓
                HWND   hWnd, // 메시지 통지를 받을 윈도우 핸들
                unsigned int wMsg, // 이벤트가 발생하면 보낼 메시지
                long IEvent); // 통지받을 네트워크 이벤트 비트마스크 
    cs

     

    3번째의 인자가 이벤트가 발생하면 보낼 메세지라 되어있는데,

    메세지는 윈도우에서 사용하는 메시지뿐만 아니라 사용자가 직접 등록할 수 있다.

    [WM_USER + 숫자] 형식으로 윈도우 메시지와 중복되는 경우를 피할 수 있다.

     

    위는 예시로 사용자 정의한 메시지와 원하는 이벤트를 기입한 코드이다.

     

    네트워크 이벤트 

     

    FD_WRITE

    WSAConnect 함수 호출하고 소켓이 처음 연결했을떄,

    WSAAccept 함수 호출하고 소켓의 연결이 수락되었을때,

    Send, WSASend, send to, WSASendTO 함수 호출후 WSAEWOULDBLOCK이 리턴되서

    전송버퍼가 비워졌을때 이벤트가 발생한다.

    즉, 보낼수 있는 상태가 되면 이벤트가 발생한다고 생각할 수 있다. 

     

    FD_ACCEPT

    클라이언트가 접속했을떄

     

    FD_READ

    수신 가능할때

     

    FD_CLOSE

    접속 종료할때

     

    FD_CONNECT

    접속 완료될때

     

    FD_OBB

    데이터가 도착할때

     

    재정의한 윈도우 메시지 프로시저

     

    이런 식으로 구성하였다. 연결 상태를 체크하고

    수신 가능하다면 연결된 socket으로부터 데이터를 수신한다. recv()함수

     

    코드

    KAsyncSelect.cpp

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include "KAsyncSelect.h"
    //재정의 윈도우 메세지 프로시저
    LRESULT  KAsyncSelect::Select_MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case NETWORK_MSG:
        {
            WORD wRet = WSAGETSELECTEVENT(lParam);
            switch (wRet)
            {
            case FD_CONNECT:
            {
                m_bConnect = true;
            }break;
            case FD_CLOSE:
            {
                m_bConnect = false;
            }break;
            case FD_READ:
            {
                RecvUser(m_User);
            }break;
            case FD_WRITE:
            {
                m_bConnect = false;
            }break;
            }
        }break;
        }
        return 0;
    }
    bool KAsyncSelect::Connect(HWND hWnd, int type, int iport, const char* ip)
    {
        //소켓 구조체 채우기
        m_Sock = socket(AF_INET, type, 0);
        SOCKADDR_IN sa;
        ZeroMemory(&sa, sizeof(sa));
        sa.sin_family = AF_INET;
        sa.sin_port = htons(iport);
        sa.sin_addr.s_addr = inet_addr(ip);
        m_User.m_Sock = m_Sock;
     
        //윈도우 메세지를 통해서 비동기적으로 소켓 활용 가능
        if (WSAAsyncSelect(m_Sock, hWnd, NETWORK_MSG,
            FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
        {
            return false;
        }
        // 블록킹 연결시도 오류가 발생하지 않으면 0을 반환,  다른 소켓 어플리케이션에 연결
        int iRet = WSAConnect(m_Sock, (sockaddr*)&sa, sizeof(sa), NULLNULLNULLNULL);
        if (iRet == SOCKET_ERROR)
        {
            return false;
        }
        return true;
    }
    cs

    KNetwork.cpp

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    #include "KNetwork.h"
     
    bool KNetwork::InitNetwork()
    {
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(22), &wsa) != 0)
        {
            return false;
        }
        return true;
    }
     
    bool KNetwork::InitServer(int protocol, int iport, const char* ip)
    {
        m_Sock = socket(AF_INET, SOCK_STREAM, 0);
        SOCKADDR_IN sa;
        ZeroMemory(&sa, sizeof(sa));
        sa.sin_family = AF_INET;
        sa.sin_port = htons(iport);
        if (ip == nullptr)
        {
            sa.sin_addr.s_addr = htonl(INADDR_ANY);
        }
        else
        {
            sa.sin_addr.s_addr = inet_addr(ip);
        }
     
        //소켓에 주소 할당
        int iRet = bind(m_Sock, (sockaddr*)&sa, sizeof(sa));
        if (iRet == SOCKET_ERROR)  return false;
     
        //클라이언트로 부터 연결 요청을 처리할수있는 상태를 만듬 
        iRet = listen(m_Sock, SOMAXCONN);
        if (iRet == SOCKET_ERROR)  return false;
        return true;
    }
    int KNetwork::SendMsg(SOCKET sock, char* msg, WORD type)
    {
        //패킷 생성
        UPACKET    packet;
        ZeroMemory(&packet, sizeof(packet));
        packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE; //ph가 헤더에는 크기와 타입을 알려줌
        packet.ph.type = type;
        memcpy(packet.msg, msg, strlen(msg));
        //운영체제 sendbuffer에 패킷 전송
        char* pMsg = (char*)&packet;
        int iSize = 0;
        do {
            //send함수
            int iSendByte = 0;
                iSendByte= send(sock, &pMsg[iSize],
                packet.ph.len - iSendByte, 0);
     
            //논블럭킹이 아니여서 이거 해줄 필요 없음
            if (iSendByte == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    return -1;
                }
            }
            iSize += iSendByte;
        } while (iSize < packet.ph.len);
        return iSize;
    }
    int KNetwork::SendMsg(SOCKET sock, UPACKET& packet)
    {
        char* pMsg = (char*)&packet;
        int iSize = 0;
        do {
            int iSendByte = send(sock, &pMsg[iSize],
                packet.ph.len - iSize, 0);
            if (iSendByte == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    return -1;
                }
            }
            iSize += iSendByte;
        } while (iSize < packet.ph.len);
        //크기만큼 보내지게 반복문
        return iSize;
    }
    //int KNetwork::AddUser(SOCKET sock)
    //{
    //    SOCKADDR_IN clientAddr;
    //    int iLen = sizeof(clientAddr);
    //    SOCKET clientSock = accept(sock,
    //        (sockaddr*)&clientAddr, &iLen);
    //    if (clientSock == SOCKET_ERROR)
    //    {
    //        return -1;
    //    }
    //    else
    //    {
    //        KNetworkUser user;
    //        user.set(clientSock, clientAddr);
    //        userlist.push_back(user);
    //        std::cout
    //            << "ip =" << inet_ntoa(clientAddr.sin_addr)
    //            << "port =" << ntohs(clientAddr.sin_port)
    //            << "  " << std::endl;
    //        std::cout << userlist.size() << " 명 접속중.." << std::endl;
    //    }
    //    return 1;
    //}
    int KNetwork::RecvUser(KNetworkUser& user)
    {
        char szRecvBuffer[1024= { 0, };
        //recv 받기 연결된 socket으로부터 데이터를 수신합니다.
        int iRecvByte = recv(user.m_Sock, szRecvBuffer, 10240);
        if (iRecvByte == 0)
        {
            return 0;
        }
        if (iRecvByte == SOCKET_ERROR)
        {
            return -1;
        }
        user.DispatchRead(szRecvBuffer, iRecvByte);
        return 1;
    }
    bool KNetwork::CloseNetwork()
    {
        closesocket(m_Sock);
        WSACleanup();
        return true;
    }
     
    cs

    KNetworkUser.cpp

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #include "KNetworkUser.h"
     
    int KNetworkUser::DispatchRead(char* sRecvBuffer, int iRecvByte)
    {
        //m_szRecvBuffer 2048을 넘으면 메모리 위치 바꿈, 초기화
        if (m_iWritePos + iRecvByte >= 2048)
        {
            if (m_iReadPos > 0)
            {
                memmove(&m_szRecvBuffer[0], &m_szRecvBuffer[m_iPacketPos], m_iReadPos);
            }
            m_iPacketPos = 0;
            m_iWritePos = m_iReadPos;
        }
     
        memcpy(&m_szRecvBuffer[m_iWritePos], sRecvBuffer, iRecvByte);
        m_iWritePos += iRecvByte;// 버퍼에 이전에 저장된 위치
        m_iReadPos += iRecvByte; // 패킷시작 위치로부터 받은 바이트
        
        if (m_iReadPos >= PACKET_HEADER_SIZE)
        {
            // 패킷 해석 가능
            UPACKET* pPacket = (UPACKET*)&m_szRecvBuffer[m_iPacketPos];
            // 적어도 1개의 패킷이 도착했다면
            if (pPacket->ph.len <= m_iReadPos)
            {
                do {
                    KPacket kPacket(pPacket->ph.type);
                    memcpy(&kPacket.m_uPacket,
                        &m_szRecvBuffer[m_iPacketPos],
                        pPacket->ph.len);
                    m_lPacketPool.push_back(kPacket);
     
                    // 다음패킷 처리
                    m_iPacketPos += pPacket->ph.len;
                    m_iReadPos -= pPacket->ph.len;
                    if (m_iReadPos < PACKET_HEADER_SIZE)
                    {
                        break;
                    }
                    pPacket = (UPACKET*)&m_szRecvBuffer[m_iPacketPos];
                } while (pPacket->ph.len <= m_iReadPos);
            }
        }
        return 1;
    }
     
    void KNetworkUser::set(SOCKET sock, SOCKADDR_IN addr)
    {
        m_bConnect = true;
        ZeroMemory(m_szRecvBuffer, sizeof(char* 2048);
        m_iPacketPos = 0;
        m_iWritePos = 0;
        m_iReadPos = 0;
     
        m_Sock = sock;
        m_Addr = addr;
        //네트워크 주소 변환 함수 빅엔디안(32bit) ->  
        m_csName = inet_ntoa(addr.sin_addr);
        //엔디안은 메모리 연속된 대상을 배열하는 방법으로 
        // Network Byte 순서를 To Host의 Byte 순서로 바꿈
        m_iPort = ntohs(addr.sin_port);
    }
     
    cs

     

    결과

    COMMENT
     
    01
    07

    읽을 파일이 없는데, Read()함수를 호출하면 읽을 내용이 생길때까지

    대기하는 블록킹 소켓과 블록킹이 되지 않아, 조건을 만족하지 않을때

    오류를 내서 일일히 관리해주는 논블로킹소켓이 있다.

     

    이러한 소켓모드와 관계없이 여러 소켓을 한 스레드로 처리할 수

    있는게 Select 함수이다.

     

    Select() 함수

    작업할 준비가 된 파일에 대해서만 작업을 하는 함수이다.

     

    1
    2
    3
    4
    5
    6
    7
    int select(
        _In_ int nfds,
        _Inout_opt_ fd_set FAR * readfds,
        _Inout_opt_ fd_set FAR * writefds,
        _Inout_opt_ fd_set FAR * exceptfds,
        _In_opt_ const struct timeval FAR * timeout
        );
    cs

     

    nfds 유닉스와 호환성을 위해 존재함, 윈도우에서 사용 안함, 관리할 파일의 개수를 지정하는 것이였나봄
    readfds 읽기 set, 블록되지 않고 읽기가 가능한지 감시
    writefds 쓰기 set, 블록되지 않고 쓰기가 가능한지 감시
    exceptfds 예외 set, 예외 발생 감시
    timeout 타임 아웃 값, 정해진 시간이 경과할때까지 블록

     

    작업 순서

     

    1, Socket Set을 비운다. (readfds, writefds)

     

     

    2. 소켓셋에 소켓을 넣는다.

     

     

    3. Select() 함수를 호출한다. 

     

     

    4. Select 함수 리턴 후 소켓 셋에 남아 있는 모든 소켓에 대해 

    맞는 소켓함수를 호출하여 처리한다.

     

     

    5. 1~4 번 반복하면 된다.


    timeOut 값에 5초를 넣으니까 Select() 함수에서

    진짜 5초 블럭킹이 된다.

     

    FD_SET으로 소켓을 넣고나서 FD_ISSET에서 해당 소켓을

    넣으면 비트가 일치하니 바로 호출되는 줄 알았는데

    그건 또 아닌 것 같다. 입출력이 발생해야 if문에 걸린다.

     

    굉장히 편하지만, 대규모 서버에서는 적합하지 않다고 한다.

     

    전체 코드

    더보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    #include "KNetwork.h"
     
    //서버 Select 모델
    std::list<KNetworkUser> userlist;
    int SendMsg(SOCKET sock, char* msg, WORD type)
    {
        //1. 패킷을 객체 생성
        UPACKET packet;
        ZeroMemory(&packet, sizeof(packet));
        //패킷의 길이는 헤더 사이즈 + 메세지 사이즈
        packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
        packet.ph.type = type;
        //메세지 받은걸로 memcpy로 패킷 만듬
        memcpy(packet.msg, msg, strlen(msg));
     
        // 2번 패킷 전송 : 운영체제 sendbuffer(short바이트)
        char* pMsg = (char*)&packet;
        int iSize = 0;
     
        do {
            //TCP는 연속적이지만 바이트 단위로 나눠져 보내질도 있음
            int iSendByte = send(sock, &pMsg[iSize],
                packet.ph.len - iSize, 0);
     
            if (iSendByte == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    return -1;
                }
            }
     
            iSize += iSendByte;
        } while (iSize < packet.ph.len);
        return iSize;
    }
    //패킷을 받았을때.
    int SendMsg(SOCKET sock, UPACKET& packet)
    {
        char* pMsg = (char*)&packet;
        int iSize = 0;
        do {
            int iSendByte = send(sock, &pMsg[iSize],
                packet.ph.len - iSize, 0);
     
            if (iSendByte == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    return -1;
                }
            }
     
            iSize += iSendByte;
        } while (iSize < packet.ph.len);
        return iSize;
    }
    int AddUser(SOCKET sock)
    {
        SOCKADDR_IN clientAddr;
        int iLen = sizeof(clientAddr);
        SOCKET clientSock = accept(sock,
            (sockaddr*)&clientAddr, &iLen);
        if (clientSock == SOCKET_ERROR)
        {
            return -1;
        }
        else
        {
            KNetworkUser user;
            user.set(clientSock, clientAddr);
            userlist.push_back(user);
            std::cout
                << "ip =" << inet_ntoa(clientAddr.sin_addr)
                << "port =" << ntohs(clientAddr.sin_port)
                << "  " << std::endl;
            std::cout << userlist.size() << " 명 접속중.." << std::endl;
        }
        return 1;
    }
    int RecvUser(KNetworkUser& user)
    {
        char szRecvBuffer[1024= { 0, };
        int iRecvByte = recv(user.m_Sock, szRecvBuffer, 10240);
        if (iRecvByte == 0)
        {
            return 0;
        }
        if (iRecvByte == SOCKET_ERROR)
        {
            return -1;
        }
        user.DispatchRead(szRecvBuffer, iRecvByte);
        return 1;
    }
    void main()
    {
        KNetwork net;
        net.InitNetwork();
        net.InitServer(SOCK_STREAM, 10000);
     
        SOCKADDR_IN clientAddr;
        int iLen = sizeof(clientAddr);
     
        std::cout<< "Server Start." << std::endl;
     
        FD_SET    rSet;
        FD_SET    wSet;
        timeval timeOut;
     
        timeOut.tv_sec = 1;
        timeOut.tv_usec = 0;
     
        while (1)
        {
            //매번 초기화 구조체의 주요 변수는 배열 
            FD_ZERO(&rSet);
            FD_ZERO(&wSet);
            //리슨 소켓에 rset를 저장함
            FD_SET(net.m_ListenSocket, &rSet);
     
            std::list<KNetworkUser>::iterator useriter;
            for (useriter = userlist.begin();
                useriter != userlist.end();)
            {
                //유저 중에 연결 끊긴 사람이 있다면 리스트에서 삭제함
                if ((*useriter).m_bConnect == false)
                {
                    std::cout << (*useriter).m_csName << " 접속종료됨." << std::endl;
                    useriter = userlist.erase(useriter);
                    continue;
                }
                //유저의 소켓을 rset에 저장함
                FD_SET((*useriter).m_Sock, &rSet);
                // 만약 user에서 보낸 패킷이 있으면 wSet 구조체에 소켓 저장
                if ((*useriter).m_lPacketPool.size() > 0)
                {
                    FD_SET((*useriter).m_Sock, &wSet);
                }
                useriter++;
            }
            //select 모델을 사용
            //select 함수는 fd_set 구조체에 할당한
            // FD의 이벤트가 발생하면 이를 감지하고
            // 어떤 FD 이벤트가 발생했는지 알려준다.
            int iRet = select(0,
                &rSet,
                &wSet,
                nullptr,
                &timeOut);
     
            //아무 활동이 없으면 0을 반환한다.
            if (iRet == 0)
            {
                continue;
            }std::cout << "..";
            //fdset중 소켓 fd에 해당하는 비트가 세트되어 있으면 양수값인 fd를 리턴한다.
            if (FD_ISSET(net.m_ListenSocket, &rSet))
            {
                std::cout << "test"<< std::endl;
                if (AddUser(net.m_ListenSocket) <= 0)
                {
                    break;
                }
            }
            
            //유저 리스트를 돌면서 해당 유저 소켓이 저장되어 있다면
            for (KNetworkUser& user : userlist)
            {
                //연결 끊겼는지 확인 여기서는 불값 체크만
                if (FD_ISSET(user.m_Sock, &rSet))
                {
                    int iRet = RecvUser(user);
                    if (iRet <= 0)
                    {
                        user.m_bConnect = false;
                    }
                }
            }
            for (KNetworkUser& user : userlist)
            {
                if (FD_ISSET(user.m_Sock, &wSet))
                {
                    if (user.m_lPacketPool.size() > 0)
                    {
                        std::list<KPacket>::iterator iter;
                        for (iter = user.m_lPacketPool.begin();
                            iter != user.m_lPacketPool.end(); )
                        {
                            for (KNetworkUser& senduser : userlist)
                            {
                                int iRet = SendMsg(senduser.m_Sock, (*iter).m_uPacket);
                                if (iRet <= 0)
                                {
                                    senduser.m_bConnect = false;
                                }
                            }
                            iter = user.m_lPacketPool.erase(iter);
                        }
                    }
                }
            }
        }
        net.CloseNetwork();
    }
    cs

     

    COMMENT
     
    01
    06

    소켓모드는 블로킹(blocking)과 넌블로킹 (non-blocking)으로 구분됨

     

    블로킹 소켓 : 

    소켓함수 호출 시 조건에 만족하지 않으면 함수 리턴하지

    않고 해당 스레드는 대기 상태가 됨.

    네트워크에서 블로킹 소켓을 사용하려면 멀티스레드를 

    사용해서 해결해야함.

     

    1
    2
    //브로킹 소켓 생성
    SOCKET listen_Sock = socket(AF_INET, SOCK_STREAM, 0)
    cs

     

    넌블로킹 소켓 : 

    소켓함수 호출 시 조건이 만족하지 않아도 함수가 리턴해서 스레드는 계속 진행됨, 다른 작업 가능

    멀티스레드를 사용하지 않아도 여래개의 소켓 입출력 처리할 수 있다. 

    단점으로는 소켓 함수를 호출할때마다 WSAEWOULDBLOCK 등 오류 코드 확인해야함

     

    1
    2
    3
    u_long on =1;
    ret = ioctlsocket(listen_Sock, FIONBIO, &on);
    if(ret==SOCKET_ERROR) err_quit(ioctlsocket());
    cs

     

    넌블로킹 함수 호출했을때 조건이 만족하지 않아 작업을 완료하지 못하면

    소켓 함수는 오류를 리턴하는데, 이때 WSAGetLastError()함수를 호출해서

    오류코드 WSAEWOULDBLOCK이되면 정상작동하게 한다.

     

    이는 조건이 만족되지 않음을 나타내므로 나중에 다시 소켓 함수를 호출하면 된다.

    만약 이 오류가 아닐 경우에는 예상하지 못한 오류이기 떄문에 return을 해준다.

    Send()함수의 경우 전송할 버퍼공간이 없다면 이 WSAEWOULDBLOCK이 뜬다.

    다시 send를 호출해서 보내지게 하면 된다. 

    1
    2
    3
    4
    5
    6
    7
        if (iSendByte == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    return -1;
                }
            }
    cs

     

    COMMENT
     
    01
    06

    프로세스(Process)

     

    컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램이다.

    Code, Data, Stack, Heap의 구조로 되어 있는 독립된 메모리 영역이다.

     

    Code 텍스트, 코드영역
    Data 전역변수, 정적변수, 배열, 구조체
    Heap 동적 데이터 (new delete)
    Stack 지역변수, 매개변수, 임시메모리

     

    쓰레드(Thread)

     

    CPU 코어에서 돌아가는 프로그램 단위를 쓰레드(Thread)라고 한다.

    한 개의 프로세스에는 최소 한개의 쓰레드로 돌아가며, 프로세스에서

    여러개의 쓰레드로 구성하는걸 멀티 쓰레드라고 한다.

     

     

    하나의 프로세스에서 여러 쓰레드에서 나누면

    자원을 효율적으로 관리 할 수 있고, 응답시간이 단축된다는 장점이 있지만

    (쓰레드를 사용하면 일의 분업화가 된다. )

    단점으로는 동기화 문제가 있다.

     

    동기화 문제

    쓰레드A와 쓰레드B로 왔다 갔다(스위칭)하면서 경쟁하는데

    둘의 조건이 안맞아 쓰레드끼리 서로 대기하는 교착상태(DeadLock)이 발생한다.

    실행 순서를 제어해야하는데, 이런 기술들을 동기화(Synchronization)라고 한다. 

     

    쓰레드 스케줄링 -> 쓰레드를 얼마 만큼 간격으로 실행할걱인가 결정하는 정책

    스위칭 -> 스레드 A는 일정시간(0.02초) 퀀텀시간동안 실행하고 잠시 중지하고 스레드 B로 스위칭한다.

    스레드 컨텍스트 -> 쓰레드 스위칭간에 작업상태 정보를 저장해 놓는 것


    클라이언트/서버 멀티쓰레드 C++ 채팅 프로그램

    c++에서 쓰레드를 생성하는 방법은 두가지가 있다.

    1. Window API 쓰레드

    2. C++11부터 지원하는 쓰레드. 

     

    C++11 부터 지원하는 쓰레드 방식으로 생성한다.

    //C++
    HANDLE CreateThread(
      [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
      [in]            SIZE_T                  dwStackSize,
      [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
      [in, optional]  __drv_aliasesMem LPVOID lpParameter,
      [in]            DWORD                   dwCreationFlags,
      [out, optional] LPDWORD                 lpThreadId
    );

     

     lpThreadAttributes, // 보안 속성 지정
     dwStackSize,         // 스택 크기를 지정
     lpStartAddress,      // 스레드의 시작함수를 지정
     lpParameter,         //시작 함수의 인자값
     dwCreationFlags,   // 스레드의 특성 지정 (바로 시작 OR 대기상태)
     lpThreadId           // 스레드의 ID를 반환함

    반환값으로는 스레드 핸들값을 반환한다.

    예시

    패킷 전송하는 스레드와 패킷을 받아오는 스레드

     

    스레드를 종료하는 방법은 함수안에서, 밖에서 하는 함수가 있지만

    제일 좋은 방법은 시작함수에서 반환하는 것이라고 한다.

     

    스래드 중지 및 재실행

    // 해당 스레드의 동작을 일시 중지 시킨다.
    DWORD SuspendThread(HANDLE hThread);
    // 일시 정지된 스레드의 수행을 재개한다.
    DWORD ResumeThread(HANDLE hThread);

     

     

    더보기

    클라이언트

    #define ServerIP "192.xxx.0.xx"
    #define ServerIP2 "192.xxx.0.xx"
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <conio.h>
    #include "KPacket.h"
    #pragma comment	(lib, "ws2_32.lib")
    int SendMsg(SOCKET sock, char* msg, WORD type);
    int SendPacket(SOCKET sock, char* msg, WORD type);
    int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket);
    int RecvPacketData(SOCKET sock, UPACKET& recvPacket);
    
    DWORD WINAPI SendThread(LPVOID IpThreadParameter);
    DWORD WINAPI RecvThread(LPVOID IpThreadParameter);
    //SendThread가 리턴이 되면 쓰레드가 종료가 된다.
    //클라이언트
    void main()
    {
    	/*WSACleanup 함수와 쌍을 이뤄 소켓 프로그램의 시작과 끝을 나타냄*/
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	//TCP - SOCK_STREAM , UDP - SOCK_DGRAM
    
    	SOCKET sock = socket(AF_INET,
    		SOCK_STREAM, 0);
    
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = inet_addr(ServerIP);
    	int iRet = connect(
    		sock,
    		(sockaddr*)&sa,
    		sizeof(sa));
    	if (iRet == SOCKET_ERROR)
    	{
    		return;
    	}
    	std::cout << "접속성공!" << std::endl;
    
    	//논블로킹 소켓 0이 아니라면 논블로킹 모드로 설정
    	u_long on = 1;
    	ioctlsocket(sock, FIONBIO, &on);
    
    	//쓰레드 생성에는 두가지 방법이 있음
    	// Window API 방법, C++ 11 방법, :: 범위해결자
    	//c++
    	DWORD ThreadIndex;
    	HANDLE hThread_Send = ::CreateThread(0, 0, SendThread,
    		(LPVOID)sock, 0, &ThreadIndex);
    
    	HANDLE hThread_Recv = ::CreateThread(0, 0, RecvThread,
    		(LPVOID)sock, 0, &ThreadIndex);
    
    	//메인 쓰레드 작업
    	while (1)
    	{
    		Sleep(100);
    	}
    	std::cout << "접속종료" << std::endl;
    
    	CloseHandle(hThread_Send); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
    	CloseHandle(hThread_Recv); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
    	closesocket(sock);
    	WSACleanup();
    }
    
    DWORD __stdcall RecvThread(LPVOID IpThreadParameter)
    {
    	SOCKET sock = (SOCKET)IpThreadParameter;
    	while (1)
    	{
    		UPACKET packet;
    		int iRet = RecvPacketHeader(sock, packet);
    		if (iRet < 0) break;
    		if (iRet == 1)
    		{
    			int iRet = RecvPacketData(sock, packet);
    			if (iRet < 0) break;
    			if (iRet == 0) continue;
    			// 메세지 처리
    			KPacket data;
    			data.m_uPacket = packet;
    			KChatting recvdata;
    			ZeroMemory(&recvdata, sizeof(recvdata));
    			data >> recvdata.index >> recvdata.name
    				>> recvdata.damage >> recvdata.message;
    			std::cout << "\n" <<
    				"[" << recvdata.name << "]"
    				<< recvdata.message;
    		}
    	}
    	return 0;
    }
    
    DWORD __stdcall SendThread(LPVOID IpThreadParameter)
    {
    	SOCKET sock = (SOCKET)IpThreadParameter;
    	char szBuffer[256] = { 0, };
    	while (1)
    	{
    		ZeroMemory(szBuffer, sizeof(char) * 256);
    		fgets(szBuffer, 256, stdin);
    		if (strlen(szBuffer) == 0)
    		{
    			std::cout << "정상 종료됨.." << std::endl;
    			break;
    		}
    
    		// 방법 1
    		//int iSendByte = SendMsg(sock, szBuffer, PACKET_CHAT_MSG);
    		// 방법 2
    		int iSendByte = SendPacket(sock, szBuffer, PACKET_CHAT_MSG);
    		if (iSendByte < 0)
    		{
    			std::cout << "비정상 종료됨.." << std::endl;
    			break;
    		}
    	}
    	return 0;
    }
    
    
    
    int SendMsg(SOCKET sock, char* msg, WORD type)
    {
    	// 1번 패킷 생성
    	UPACKET packet;
    	ZeroMemory(&packet, sizeof(packet));
    	//메세지 내용에서 헤더 사이즈 추가
    	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
    	packet.ph.type = type;
    	memcpy(packet.msg, msg, strlen(msg));
    	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트), recvbuffer
    	char* pMsg = (char*)&packet;
    	int iSendSize = 0;
    	do {
    		int iSendByte = send(sock, &pMsg[iSendSize],
    			packet.ph.len - iSendSize, 0);
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    		iSendSize += iSendByte;
    	} while (iSendSize < packet.ph.len);
    	return iSendSize;
    }
    //패킷 전송
    int SendPacket(SOCKET sock, char* msg, WORD type)
    {
    
    	// 1번 패킷 생성
    	KPacket tPacket(type);
    
    	//index, name[20], damage, message[256]
    	tPacket << 999 << "최지원" << (short)50 << msg;
    	
    	//생성자로 패킷 생성자로 패킷을 받아 패킷을 만듬
    	KPacket tPacketTest(tPacket);
    
    	KChatting recvdata;
    	ZeroMemory(&recvdata, sizeof(recvdata));
    	
    	//패킷에서 정보 파싱
    	tPacketTest >> recvdata.index >> recvdata.name
    		>> recvdata.damage >> recvdata.message;
    
    	char* pData = (char*)&tPacket.m_uPacket;
    
    	int iSendSize = 0;
    	do {
    		int iSendByte = send(sock, &pData[iSendSize],
    			tPacket.m_uPacket.ph.len - iSendSize, 0);
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    		iSendSize += iSendByte;
    	} while (iSendSize < tPacket.m_uPacket.ph.len);
    	return iSendSize;
    }
    //패킷 헤더만 받아오기
    int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket)
    {
    	char szRecvBuffer[256] = { 0, };
    	//패킷헤더 받기
    	ZeroMemory(&recvPacket, sizeof(recvPacket));
    	bool bRun = true;
    	int iRecvSize = 0;
    	//페킷 헤더 (4바이트)가 될때까지 do while문 
    	do {
    		int iRecvByte = recv(sock, szRecvBuffer,
    			PACKET_HEADER_SIZE, 0);
    		iRecvSize += iRecvByte;
    		if (iRecvByte == 0)
    		{
    			closesocket(sock);
    			std::cout << " 접속종료됨." << std::endl;
    			return -1;
    		}
    		if (iRecvByte == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << " 비정상 접속종료됨." << std::endl;
    				return -1;
    			}
    			else
    			{
    				return 0;
    			}
    		}
    	} while (iRecvSize < PACKET_HEADER_SIZE);
    	memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
    	return 1;
    }
    //패킷 헤더 제외한 패킷 받기
    int RecvPacketData(SOCKET sock, UPACKET& recvPacket)
    {
    	// 데이터 받기
    	int iRecvSize = 0;
    	do {
    		int iRecvByte = recv(sock, recvPacket.msg,
    			recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
    		iRecvSize += iRecvByte;
    		if (iRecvByte == 0)
    		{
    			closesocket(sock);
    			std::cout << " 접속종료됨." << std::endl;
    			return -1;
    		}
    		if (iRecvByte == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << " 비정상 접속종료됨." << std::endl;
    				return -1;
    			}
    			else
    			{
    				return 0;
    			}
    		}
    	} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);
    	return 1;
    }

    서버

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <list>
    #include <string>
    #include "KPacket.h"
    #pragma comment	(lib, "ws2_32.lib")
    
    //서버
    struct User
    {
    	SOCKET		m_Sock;
    	SOCKADDR_IN m_Addr;
    	std::string m_csName;
    	short       m_iPort;
    	void set(SOCKET sock, SOCKADDR_IN addr)
    	{
    		m_Sock = sock;
    		m_Addr = addr;
    		//네트워크 주소 변환 함수 빅엔디안(32bit) ->  
    		m_csName = inet_ntoa(addr.sin_addr);
    		//엔디안은 메모리 연속된 대상을 배열하는 방법으로 
    		// Network Byte 순서를 To Host의 Byte 순서로 바꿈
    		m_iPort = ntohs(addr.sin_port);
    	}
    };
    
    int SendMsg(SOCKET sock, char* msg, WORD type)
    {
    	//1. 패킷을 객체 생성
    	UPACKET packet;
    	ZeroMemory(&packet, sizeof(packet));
    	//패킷의 길이는 헤더 사이즈 + 메세지 사이즈
    	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
    	packet.ph.type = type;
    	//메세지 받은걸로 memcpy로 패킷 만듬
    	memcpy(packet.msg, msg, strlen(msg));
    
    	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트)
    	char* pMsg = (char*)&packet;
    	int iSize = 0;
    
    	do {
    		//TCP는 연속적이지만 바이트 단위로 나눠져 보내질도 있음
    		int iSendByte = send(sock, &pMsg[iSize],
    			packet.ph.len - iSize, 0);
    
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    
    		iSize += iSendByte;
    	} while (iSize < packet.ph.len);
    	return iSize;
    }
    //패킷을 받았을때.
    int SendMsg(SOCKET sock, UPACKET& packet)
    {
    	char* pMsg = (char*)&packet;
    	int iSize = 0;
    	do {
    		int iSendByte = send(sock, &pMsg[iSize],
    			packet.ph.len - iSize, 0);
    
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    
    		iSize += iSendByte;
    	} while (iSize < packet.ph.len);
    	return iSize;
    }
    
    void main()
    {
    	WSADATA wsa;
    	//2.2 버젼 WSA 
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, 0);
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = htonl(INADDR_ANY);
    	//소켓에 주소 할당
    	int iRet = bind(ListenSock, (sockaddr*)&sa, sizeof(sa));
    	if (iRet == SOCKET_ERROR)  return;
    	//클라이언트로 부터 연결 요청을 처리할수있는 상태를 만듬 
    	iRet = listen(ListenSock, SOMAXCONN);
    	if (iRet == SOCKET_ERROR)  return;
    
    	SOCKADDR_IN clientAddr;
    	int iLen = sizeof(clientAddr);
    
    	std::cout<< "Server Start." << std::endl;
    
    	//non blocking socket 0이면 블락킹 소켓
    	u_long on = 1;
    	ioctlsocket(ListenSock, FIONBIO, &on);
    
    	std::list<User> userlist;
    
    	while (1)
    	{
    		SOCKET clientSock = accept(ListenSock,
    			(sockaddr*)&clientAddr, &iLen);
    
    		if (clientSock == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << "ErrorCode=" << iError << std::endl;
    				break;
    			}
    		}
    		//클라이언트가 접속 시 시작함
    		else
    		{
    			User user;
    			user.set(clientSock, clientAddr);
    			userlist.push_back(user);
    			std::cout
    				<< "ip =" << inet_ntoa(clientAddr.sin_addr)
    				<< "port =" << ntohs(clientAddr.sin_port)
    				<< "  " << std::endl;
    			u_long on = 1;
    			ioctlsocket(clientSock, FIONBIO, &on);
    			std::cout << userlist.size() << " 명 접속중." << std::endl;
    		}
    
    		if (userlist.size() > 0)
    		{
    			std::list<User>::iterator iter;
    			for (iter = userlist.begin(); iter != userlist.end(); )
    			{
    				User user = *iter;
    				char szRecvBuffer[256] = { 0, };
    				//패킷헤더 받기
    				UPACKET recvPacket;
    				ZeroMemory(&recvPacket, sizeof(recvPacket));
    				int iRecvSize = 0;
    				do {
    					int iRecvByte = recv(user.m_Sock, szRecvBuffer,
    						PACKET_HEADER_SIZE, 0);
    					iRecvSize += iRecvByte;
    					if (iRecvByte == 0)
    					{
    						closesocket(user.m_Sock);
    						iter = userlist.erase(iter);
    						std::cout << user.m_csName << " 접속종료됨." << std::endl;
    						break;
    					}
    					if (iRecvByte == SOCKET_ERROR)
    					{
    						int iError = WSAGetLastError();
    						if (iError != WSAEWOULDBLOCK)
    						{
    							iter = userlist.erase(iter);
    							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    							break;
    						}
    						else
    						{
    							break;
    						}
    					}
    				} while (iRecvSize < PACKET_HEADER_SIZE);
    
    				if (iRecvSize == SOCKET_ERROR)
    				{
    					if (iter != userlist.end())
    					{
    						iter++;
    					}
    					continue;
    				}
    
    				memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
    				// 데이터 받기
    				iRecvSize = 0;
    				do {
    					int iRecvByte = recv(user.m_Sock, recvPacket.msg,
    						recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
    					iRecvSize += iRecvByte;
    					if (iRecvByte == 0)
    					{
    						closesocket(user.m_Sock);
    						iter = userlist.erase(iter);
    						std::cout << user.m_csName << " 접속종료됨." << std::endl;
    						continue;
    					}
    					if (iRecvByte == SOCKET_ERROR)
    					{
    						int iError = WSAGetLastError();
    						if (iError != WSAEWOULDBLOCK)
    						{
    							iter = userlist.erase(iter);
    							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    						}
    						else
    						{
    							iter++;
    						}
    					}
    				} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);
    
    				KPacket data;
    				data.m_uPacket = recvPacket;
    				KChatting recvdata;
    				ZeroMemory(&recvdata, sizeof(recvdata));
    				data >> recvdata.index >> recvdata.name
    					>> recvdata.damage >> recvdata.message;
    
    				std::cout << "\n" <<
    					"[" << recvdata.name << "]"
    					<< recvdata.message;
    
    				// 패킷 완성		
    				std::list<User>::iterator iterSend;
    				for (iterSend = userlist.begin();
    					iterSend != userlist.end(); )
    				{
    					User user = *iterSend;
    					int iSendMsgSize = SendMsg(user.m_Sock, recvPacket);
    					if (iSendMsgSize < 0)
    					{
    						closesocket(user.m_Sock);
    						iterSend = userlist.erase(iterSend);
    						std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    					}
    					else
    					{
    						iterSend++;
    					}
    				}
    				if (iter != userlist.end())
    				{
    					iter++;
    				}
    			}
    		}
    	}
    	closesocket(ListenSock);
    	WSACleanup();
    }
    COMMENT
     
    1 2 3 4 5 6 7 ··· 9