안녕하세요 루키스님 이번 SendBuffer 강의를 들으면서 궁금한 것이 생겨 질문 드립니다. SendBuffer에 자기 자신에 대한 스마트 포인터를 갖도록 해주어 Reference Counting을 해주었는데, 이번 강의의 코드를 복습하면서 분석하다 보니 '왜 SendBuffer에 RefCounting이 필요한가?' 에 대한 의문이 생겼습니다. 일단 제가 이해한 Send의 흐름은 다음과 같습니다. GameSession에서 SendBuffer를 생성하여 원하는 데이터를 복사하여 Send를 호출하면 SendQueue에 일단 Push하여 데이터를 쌓아 놓습니다. 그 때 어떠한 쓰레드에서도 그 세션에 대한 RegisterSend를 수행하고 있지 않다면 해당 쓰레드가 RegisterSend를 수행하는데 이때까지 쌓인 데이터를 SendEvent의 버퍼에 담아 WSASend를 수행합니다. 그런 다음 IocpCore에서 완료 통지를 받으면 Worker 쓰레드가 Dispatch를 수행하여 ProcessSend를 수행하면서 이 때 SendQueue에 데이터가 더 있다면 그 쓰레드가 RegisterSend를 수행합니다. 위의 흐름이 제가 이해한 내용인데 결국 한번에 하나의 RegisterSend가 진행되도록 하는 것이 핵심이며 한 번에 하나만 실행하기 때문에 SendEvent도 재 사용 가능한 것이라고 생각합니다. 그렇다면 이 세션에 대한 SendEvent는 Send되고 있는 동안에는 멀티쓰레드 환경에서 안전하다고 생각 되는데 그렇다면 SendEvent에 담긴 SendBuffer도 안전하여 사라질 걱정을 하지 않아도 되는 것 아닐까 싶습니다. 즉, WSASend를 한 번에 하나의 쓰레드만 수행하기 때문에 SendEvent도 재 사용이 가능하고 그 속에 담긴 SendBuffer도 멀티쓰레드 환경으로부터 안전하기 때문에 SendBuffer의 RefCounting이 왜 필요한 것인지 궁금합니다.
안녕하세요 루키스님! 유튜브에 온라인 출판사 영상을 보고 궁금하게 생겨서 이렇게 질문드려요! 유튜브 영상에서는 WinAPI-D2D-D3D 커리큘럼, UE 커리큘럼, Unity 커리큘럼 이렇게 3개를 구상중이라고 하셨는데 C++ IOCP + UE5 연동 강의는 일정이 어떻게 되는지 알 수 있을까요? ㅠㅠ
안녕하세요. 지금 ReadLock WriteLock 강의듣고 혼자서 구현해보는 연습중에 궁금한점이 있어서 문의드립니다. Main부분에서 for (int i = 0; i < 2; i++) { threaManger->Luncher(ThreadWrite); } for (int i = 0; i < 5; i++) { threaManger->Luncher(ThreadRead); } 고유 스레드 아이디를 부여해서 스레드 2개는 Write("스레드 아이디" 1,2) 스레드 5개는 Read ("스레드 아이디" 3,4,5) 일을 시키는데 이렇게 되면 Read만 하는 스레드는 ReadLock만 잡고 Write만 하는 스레드는 WirteLock만 잡게 되어서 1번 아이디의 스레드(Write)가 Read Lock을 잡을 수 없지 않나요? 아래 코드에서 Readlock 함수를 보면 WRITE_THREAD_MASK를 씌어서 threadId를 비교하는데 이 부분은 실행 안될 거라고 생각되는데 맞나요? 실제로 디버그에서 ReadLock에 포인트를 잡고 봤는데 브레이크 포인트에 걸리지는 않았습니다. 제가 궁금한 걸 글로 쓰려니 잘 정리가 안돼서 죄송합니다. 질문을 요약하자면 1.ReadLock을 잡는 스레드의 id는 WriteLock을 잡는 스레드 id와 똑같을 수가 있나요? // ReadLock const int lockThreadId = (_lockFlag.load() & WRITE_THREAD_MASK) >> 16; if (lockThreadId == SharedThreadId) { _lockFlag.fetch_add(1); return; } // WriteLock const int lockThreadId = (_lockFlag & WRITE_THREAD_MASK) >> 16; if (lockThreadId == SharedThreadId) { _writeCount++; return; }
JobQueue::Push 에서 pushOnly를 추가해야 하는 이유를 잘 모르겠습니다. JobQueue::Push 함수가 실행되었다는 것은 이미 예약한 시간이 되어서 실행되어도 좋은 Job이 JobQueue에 Push된다는 뜻이 아닌가요? 지금 당장 실행되어도 좋은 Job을 Push 하려는데, 왜 굳이 JobQueue::Execute를 건너뛰고 GlobalQueue::Push를 호출해서 한 타이밍 늦게 Job을 실행시키는지 잘 모르겠습니다.
하나의 쓰레드가 여러 개의 JobQueue를 점유하는 현상이 안좋은 이유는 알겠습니다. 다른 쓰레드가 다른 JobQueue의 Job들을 처리하지 못하게 되니까요. 그런데 모든 쓰레드가 모든 일감을 처리할 수 있는 만능 일꾼이 된 상황에서, 하나의 쓰레드가 하나의 JobQueue를 오래동안 잡고있는 현상이 왜 안좋은지는 잘 이해가 안갑니다. 어차피 하나의 쓰레드가 붙잡고 해야할 일이 아닌가요? 동시에 여러 쓰레드가 처리 가능한 것도 아닌 것 같구요... 예를 들어, 쓰레드 A, B, C가 있다고 해봅시다. 쓰레드 A가 JobQueue 'ㄱ'를 점유하고 계속 작업을 하고 있습니다. 쓰레드 B와 C는 'ㄱ'과 관련된 일은 모두 A에게 몰아주고 나머지 작업들을 처리하고 있습니다. 그런데 A의 'ㄱ'점유가 너무 길어져서 'ㄱ'의 점유를 B에게 넘겨주었습니다. 그리고 A는 B와 C가 하던 것 처럼 나머지 작업들을 처리하게 되었습니다. 그럼 결국 'ㄱ'의 점유가 A에서 B로 넘어갔을 뿐 달라진 것이 없지 않나요? 오히려 쓸 데 없는 스위칭 비용만 발생한 것이 아닌가요?
안녕하세요. 정말 신입 서버의 교과서 같은 강의라서 잘 보고 있다가 문득 궁금한 점이 생겨 질문 드리게 되었습니다. 제가 이해하기로는 TCP 는 Stream 방식이라서 100 byte 의 데이터를 send 하는 경우, 한 번에 받는다는 보장이 없어 20, 30, 50 이렇게 끊어서 받는 경우가 충분히 있을 것 같은데.. 현재 강의해서 제작된 RecvBuffer 를 사용함과는 무관하게 해당 현상을 해소하는 부분이 아직 안 보이는 것 같은데 이 부분은 따로 처리를 해야되는 부분인가요? 추후에 OnRecevie 함수 내부에서 처리를 하게끔 만드는 것인지, 아니면 해당 현상에 관련된 작업은 굳이 안해도 되는 것인지 궁금하여 질문드려봅니다.
LockGuard Lock(_mutex); _threads.push_back(thread([=]() { InitTLS(); callback(); } )); 위가 원본 코드이고 LockGuard Lock(_mutex); _threads.push_back(thread([&]() { InitTLS(); callback(); } )); 수정본 입니다 데이터를 전부 복사 보다 참조로 들고 있는게 좋을 것 같아서 위와 같이 수정하자 abort() has been called 에러가 뜹니다 원본코드시는 문제가 없는데 혹시 쓰레드에서 참조를 들고 있으면 안되나요?
좋은 강의 감사드립니다 1.혹시 언리얼과 서버 강의 연동을 내년에 출시하실 예정이라고 하셨는데 계획에는 혹시 변경이 없나요? 언리얼엔진5와 서버연동을 많이 기대하고있습니다 2.예전에 제가 혼자 서버와 언리얼을 연동할 때는 언리얼에 Winsock을 추가하는 방식으로 제작해서 크래쉬가 좀 많이 일어났었는데 강의를 듣고보니 더 좋은 방법이 있을꺼 같은데 연동할 때는 어떤 방식으로 진행을 하나요? 마지막으로 혼자 서버 제작할때는 단순 노가다식 코드로 했었는데 강의듣고 난 후에는 서버코어 라이브러리 부터 받아오는 방식과 메모리 관리/자동화 코드를 배운 덕분에 코드가 많이 좋아지고 편해졌습니다 항상 좋은 강의 감사드립니다
강의 초반부에서 Session::Send()를 수정해주셨는데 { WRITE_LOCK; _sendQueue.push(sendBuffer); } if(_sendRegistered.exchange(true) == false) RegisterSend(); 이렇게 작성하면 안되는 것일까요? 별도로 스택 변수를 사용해야 되는 이유가 있는지 궁금합니다. 멀티스레드 개념이 잘 안잡혀서 번거롭게 사소한 것까지 여쭤봐서 죄송합니다..
template<typename T> BufferWriter& BufferWriter::operator<<(const T& src) { *reinterpret_cast<T*>(&_buffer[_pos]) = src; _pos += sizeof(T); return *this; } reinterpret_cast c++강의에서 아주 강력한 변환 포인터랑 정수사이도 변환이 가능하고.. 다 가능한 변환 느낌이었는데 앞에*를 붙여주는 이유가 궁금합니다. *reinterpret_cast<T*>(&_buffer[_pos]) = src; =========================== T*로 변환하는데 src가 참조라서 *를 한번 더 붙여주신건가요?
강의와 같이, 자신의 페이지를 넘어선 메모리에 접근하면 "쓰기 엑세스 위반"이라는 에러가 뜨는 상황인데. 이 때 만약, 오버플로우로 접근하는 바로 그 다음 페이지가 "쓰기 권한이 있는" 페이지 였다면 오버플로우를 잡아내지 못하는건가요? 아니면, 엑세스 권한과 상관없이 단순히 페이지 범위를 벗어났기 때문에 에러를 주는 원리인가요?
1. // 상속 관계 복사 template<typename U> TSharedPtr(const TSharedPtr<U>& rhs) { Set(sticta_cast<T*>(rhs._ptr)); } 강의에서 TSharedPtr클래스의 상속관계복사부분이 이해가 조금 부족한 것같습니다. 부모 자식관계에서 받아온 값들을 다시 static_cast<T*>로 하면 복사가 정상적으로 되는건가요? 2. 강의에서 wraight = nullptr 이 부분은 실제로 wraight = WraightRef(nullptr); 로 되어있어서 TSharedPtr(T* ptr) { Set(ptr); }이 호출된 후 복사 연산자가 호출된다고 말씀하셨는데 실제 디버깅을 해보니 복사가 아니라 이동쪽으로 브레이크 포인트가 타고있었는데 같은 맥락인건가요 ?
먼저 이런 좋은 강의 남겨주셔서 너무나 감사드립니다. 1. DirectX를 조금 더 깊게 공부하고 싶은데 11과 12중에 어떤것을 배우면 될까요?? 2. 일하는 시간대가 학원과 시간대가 안맞아서 그런데 DirectX 를 공부할 수 있는 책이나 온라인 강의 추천해주시면 정말정말 감사하겠습니다! 3. 혹시 DirectX에 대한 정보를 얻을 수 있는 커뮤니티가 있을까요?? 4. 그리고 Node.js를 배우는데 책이나 온라인강의 추천해주시면 감사하겠습니다!
현재 락프리까지 듣고 소켓프로그래밍이 궁금해서 바로 넘어 왔는데 현재 게임서버쪽이 ws2에러가 재정의 에러가 나서 질문드립니다. 현재 msdn으로가서 오타도 확인하고 안되서 루키스님의 소스도 복붙했는데 제 visual에서 에러가 발생하는데 혹시 threadmaanager 강의쪽에서 추가하신 것이 있어서 그런건가요? 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
AcceptEx가 실패가 나는 경우 즉 , AcceptEx호출 자체가 false가 나고, 이때 GetlastErrror가 WSA_IO_PENDING이 아닌경우나 , AcceptEx의 호출로 IOCP 에서 Pending중일때 모종의 이유로 GQCS에서 AcceptEx pending에 대한 실패가 리턴되는경우 listen socket이 close되었거나, 어찌되었든 더이상 유저를 받지못하는 상황일 수 있는데 이럴때 서버에 강제로 CRASH를 내도록 해도 될것같은데, 혹시 이렇게 하면 안될 이유가 있을까요?? (AcceptEx를 호출할때 close된 session 소켓을 전달하는 경우는 없고, AcceptEx의 호출결과로 IOCP 유저접속을 대기중인상태(Pending)일때 session socket이 close되거나 session이 삭제되는 일은 일체 없다고 가정합니다. 물론 listen socket은 닫힐 수 있지만, listensocket이 닫히면, 서버를 유지시켜줄 이유가 없다고 생각되긴 합니다.)