C++
6 posts
클라이언트 콘솔 입력 스레드 분리하기

클라이언트 스레드 관리 요약 클라이언트에서 전투 시작 시 콘솔 입력 + 패킷 처리가 동시에 실행이 되지 않았던 이유는 콘솔 입력 작업을 중복으로 큐에 추가하여, 모든 스레드가 블로킹 됐기 때문이다. 문제 상황 boost asio를 이용해 소켓 프로그래밍을 구축했습니다. 클라이언트의 스레드 구조는 아래와 같습니다. 4개의 작업스레드를 생성하고, io_context에 post된 작업을 스레드가 가져가서 작업을 하는 형태입니다. post 되는 작업은 Send() : 콘솔 입력 + 패킷 전송 Receive() : 패킷 수신 + 콘솔 출력 2개의 작업뿐입니다. 이해도 부족 처음 문제에 대해 다뤘던 문서 (읽어보지 않아도 됩니다..!) 요약 : 콘솔 입력 작업 / 패킷 수신 작업 2개의 작업이 존재하는데, 패킷 수신 작업이 1초마다 반복될 때, 두 작업이 동시에 진행되지 않았습니다. 정리했던 문서의 결과는 다음과 같습니다. getline() 함수는 블로킹 함수이고, 이로 인해 다른 스레드에서…

May 01, 2024
C++
Boost
Boost Work란?

Boost asio::io_service::work 란? Boost asio를 사용해 소켓 서버를 생성하면, 항상 io_service(지금은 io_context로 대체)를 사용하게 된다. 링크에선 work를 사용해야 하는 이유를 다루고 있다. 두 코드를 보면, 1. thread 생성, 2. 작업 post, 두 개의 작업이 존재하고 1번 코드는 1 → 2 (work 객체와 함께) 2번 코드는 2 → 1 의 순서로 코드 흐름이 이어진다. post는 IO Execution Context(여기선 io_service)에서 실행하겠다는 의미이다. 모든 context는 run을 실행해줘야 하는데, 여기선 thread를 생성하고 작업을 실행한다. 즉, 1번 코드는 IO Context run 이후 post 2번 코드는 post 이후 IO Context를 run 한다. 즉, 남은 작업이 존재하지 않아도 io_service가 run을 반환하지 않도록 도와주는 것이 work의 역할이다. ps. De…

March 23, 2024
C++
Boost
메모리 사이클 관리

메모리 사이클 기존 코드를 보면 아래와 같습니다. ProtocolPtr은 패킷을 주고 받을 때 사용하는 encode, decode가 구현된 객체입니다. 여기서 temp 변수는 Send() 함수에서 선언한 지역 변수이고, Send() 함수의 종료와 함께 소멸됩니다. Send() 함수는 비동기 함수인 boost::asio::async_write_some()을 호출합니다. 이때 Send() 함수가 종료된 이후 비동기 함수가 실행되고, 실행 후 SendHandle() 함수가 호출됩니다. 문제는 여기서 발생할 수 있습니다. 비동기함수는 temp->encode()를 매개변수로 전달받는데, temp는 Send() 함수가 종료되면 소멸됩니다. Send()의 종료 이후, 비동기함수가 실행될 때 temp의 메모리 안정성은 보장되지 않습니다. 때문에 메모리 이슈가 발생할 수 있고, 우리는 이 메모리를 관리해줄 필요가 있습니다. 람다에 캡쳐 비동기 함수에서 temp를 사용하기 위해 람다에 캡쳐를 사용합…

March 19, 2024
C++
소켓 통신 오류 디버깅 (패킷 처리 오류)

오류 발생 과정 Server ↔ Client 간의 소켓 통신 과정은 아래와 같습니다. Client: 를 통해 ID 설정 Server: 해당 Client의 ID를 설정하고 “set $ID Success”를 해당 Client에게 전송 () “$ID 님이 입장하셨습니다”를 모든 Client에게 전송 () Client: 를 통해 수신 이 과정에서 아래 에러가 발생했습니다. libc++abi: terminating due to uncaught exception of type std::__1::system_error: mutex lock failed: Invalid argument (해당 오류는 10번 시도 중 9번 정도로 발생했습니다) 오류 디버깅 Client와 Server의 핵심 코드는 아래와 같습니다 Thread 생성 시에 lock을 사용한 부분은 문제가 없었고, Receive 함수의 lock 호출과는 실행 시점이 분리되어 있습니다. 때문에 lock 관련 에러는 Receive …

March 07, 2024
C++
Deque push_front는 O(1)일까?

deque의 push_front 시간복잡도 list는 Node로 구성되어, 삽입 삭제에 유리하지만 operator [] 를 사용할 수 없다. vector는 데이터가 선형으로 이어져야 하기 때문에 삽입/삭제 시 불리하지만, operator []가 가능하다. c++ 표준 라이브러리가 제공하는 Deque는 이 둘을 섞은 느낌이다. Deque는 block의 형태로 구현되어 있다. operator [], *를 보면 Block과 Map이 나온다. Deque는 내부적으로 block으로 이루어져 있다. 이 block이 하나의 vector(배열)이고, 이 block들이 여러개로 구성되어 있다. 때문에 원소의 삽입,삭제는 빠르게 이루어진다. vector처럼 원소를 재정렬할 필요가 없기 때문이다. 또 vector와 같이 []도 사용가능하다. 여기서 의문이 생겼는데, push_front를 실행할 경우, 새로운 block이 필요할 수 있다. 이 경우 새로운 block을 어떻게 관리하면 시간복잡도가 O(1…

January 30, 2024
C++
c++의 가상 함수란!

가상 함수 가상 함수 vs 순수 가상 함수 가상 함수 한글자료 가상 함수 런타임 가상 함수 가상 함수는 클래스 멤버 변수에 선언할 수 있고, 함수 호출에 사용된 타입(포인터)에 관계없이 실제 객체의 함수가 호출되도록 한다. 몇 가지 특징, 규칙은 아래와 같다. 런타임 다형성을 구현할 수 있다. 이를 위해 상위 클래스 타입을 사용해야한다. 가상 함수는 하위 클래스에서 꼭 override 해야하는 것은 아니다. 가상 함수의 시그니처는 동일해야한다. 가상 생성자는 불가능하다. 소멸자는 가능 어떻게 이런 동작을 할까? RTTI(RunTime Type Information)을 활용한다. 클래스와 객체를 생성할 때 각각 VTable, VPtr이 생성된다. 아래 예시를 보자. 각 에서 실제 derived 객체를 생성하고, 타입은 base 포인터이다. 이때 derived 객체는 멤버 변수로만 이뤄지지 않고, VPtr이라는 주소값이 할당된다. 이 VPtr은 실제 이 객체의 가상 함수 테이블(VT…

January 30, 2024
C++