해결된 질문
24.05.25 21:53 작성
·
177
·
수정됨
0
안녕하세요, 루키스님.
좋은 강의를 제공해주신 것에 대해 항상 감사드립니다.
기존에는 서버의 코어 부분은 건드리지 않고 컨텐츠 부분만 직접 수정 및 추가하며 클라이언트와 연동해보는 사이드 프로젝트만 진행해보았고,
서버 코어 부분에 대하여 좀 더 깊게 이해를 해보기 위해 다시 정주행을 하는 중 입니다.
이유는 모르겠지만, 같은 내용의 강의를 계속해서 볼 때마다 점점 이해되는 범위가 달라지는 것이 느껴지는 것 같긴 하네요..
이번에는 최대한 운영체제 같은 윗 분들의 사정(?)을 고려해보며 이해를 해보며 접근을 해보려고 하는데요.
그러다 보니 몇 가지 의문점이 생겨 질문 드립니다.
1. 세션코드에서 Send부분에 lock안에서 enqueue를 하는 이유는 TCP 통신의 순서를 보장하기 위해서 인가요?
2. Recv부분은 왜 lock이 안걸려있는지 이해가 되지 않습니다.
검색해보거나 다른 분들 질문을 참고해보면, ReceiveAsync 부분이 1개의 스레드만 접근하는 것을 보장하기 때문이라고 하시는 것 같은데요.
이 부분은 소켓API 부분에서 보장해주는 건가요?
그렇다면 다른 SendAsync와 같은 부분과 다르게 버퍼를 읽던 중 다른 수신을 받게 되어도, 수신을 처리하는 스레드가 스레드풀에서 제공되는 것이 아닌, 현재 수신을 처리 중인 스레드가 완료 될 때 까지 어딘가에서 대기하고 있다가 한 번에 처리되는 건가요?
3. PacketSession의 OnRecv 부분에서 BitConverter.ToUInt16 메서드가 메서드 자체적으로 바이트 배열의 시작하는 인덱스에서 '2바이트'만큼을 부호없는 정수로 바꾸는 메서드라고 알아보았는데요.
메서드 자체에서 '2바이트'라고 지정해둔 것은 보편적으로 패킷의 크기를 나타내는 헤더를 2바이트로 정하기 때문인가요?
4. 위처럼 OnRecv에서 패킷이 온전하게 도착하지 않았을 때, 단순히 writePos만 옮겨진 상태로 다음 패킷을 받을 때까지 기다리기만 해도 되는 것은, TCP 통신 자체가 '순서를 보장하기 때문에 앞에서 온전하게 받지 못한 패킷에 대해서 다른 패킷보다 먼저 다시 보내주고', '온전하게 도착하지 않은 부분에 대해서만 보내주기 때문'일까요?
그리고 그렇다면 위처럼 순서를 보장해주고, 상대가 어디까지 받았는지, 어디를 못 받았는지 판단하는 것은 운영체제에서 알아서 처리되는 건가요?
만약 상대가 못 받았을 경우 다시 보내줘야 한다면, 커널에서 패킷을 송신한 뒤에도 상대가 온전하게 받았다고 신호하기 전까지는 커널 상의 버퍼에 해당 패킷을 계속 저장해두고 있으려나요?
5. ArraySegment를 사용할 때, Array를 통해 배열의 시작 위치를 넘기고 어떤 위치부터 조회할 지(?) 조회할 위치를 넘기는 부분이 Offset인 것 같은데요.
Offset을 0으로 두고 readPos및 writePos나 DataSize, FreeSize 등을 통해 패킷 시작 위치나 범위를 지정해주는 것은 Offset 값을 변경하며 이를 컨트롤 하는 것이 복잡하기 때문인가요?
처음 강의를 수강할 때에 비하면 강의 내용이 점점 머릿속에 그려지고 구조와 흐름이 얼추 잡혀가는 느낌이지만,
누군가가 "너 한 번 혼자서 서버 만들어봐!!"라고 한다면 강의를 참고하거나 구글링 없이 혼자 서버 코어 단을 만들 수 있을 거라는 엄두가 나질 않는데요.
6. 4년제 학부 졸업생의 수준에서 서버 코드를 보고 이 부분은 왜 이렇게 작성된 것인지, 어떤 흐름으로 코드가 실행되는지 정도만 이해하면 게임 서버 프로그래머로 취업하기엔 충분할까요...?
아니면 정말 혼자 서버 코어 부분을 작성할 수 있을 정도가 되어야 하는건가요?
강의 들으며 궁금했던 부분을 싹 모아서 정리해보니 장문의 질문이 되어버렸네요 죄송합니다 ㅠㅠ
다시 한 번 좋은 강의 제공해주셔서 정말 감사합니다.
답변 2
1
2024. 05. 25. 22:39
1. 세션코드에서 Send부분에 lock안에서 enqueue를 하는 이유는 TCP 통신의 순서를 보장하기 위해서 인가요?
아닙니다. Send는 여러 쓰레드에서 호출을 할 수가 있어요. 한 유저한테 귓말이 올 수도 있찌만 몬스터가 주변에서 공격도 할 수 있고 그런거라 컨텐츠를 만들어봐야 알기 쉽습니다.
2. Recv부분은 왜 lock이 안걸려있는지 이해가 되지 않습니다.
검색해보거나 다른 분들 질문을 참고해보면, ReceiveAsync 부분이 1개의 스레드만 접근하는 것을 보장하기 때문이라고 하시는 것 같은데요.
이 부분은 소켓API 부분에서 보장해주는 건가요?
아닙니다. 이 부분을 늘 낚시대에 비유를 하는데
낚시대를 하나 던지고, 그것을 회수하고, 다시 던지기 때문에
애당초 낚시대가 하나여서 멀티쓰레드로 동시에 두개의 낚시대를 끌어올릴 수 없는 것과 유사합니다.
3. PacketSession의 OnRecv 부분에서 BitConverter.ToUInt16 메서드가 메서드 자체적으로 바이트 배열의 시작하는 인덱스에서 '2바이트'만큼을 부호없는 정수로 바꾸는 메서드라고 알아보았는데요.
메서드 자체에서 '2바이트'라고 지정해둔 것은 보편적으로 패킷의 크기를 나타내는 헤더를 2바이트로 정하기 때문인가요?
우리가 2바이트로 정한 것이고, 64KB면 하나의 패킷으로 적당해서 그렇지만
경우에 따라 헤더를 4바이트-4바이트로 하는 경우도 있긴 합니다.
4. 위처럼 OnRecv에서 패킷이 온전하게 도착하지 않았을 때, 단순히 writePos만 옮겨진 상태로 다음 패킷을 받을 때까지 기다리기만 해도 되는 것은, TCP 통신 자체가 '순서를 보장하기 때문에 앞에서 온전하게 받지 못한 패킷에 대해서 다른 패킷보다 먼저 다시 보내주고', '온전하게 도착하지 않은 부분에 대해서만 보내주기 때문'일까요?
전자입니다. 다른 패킷보다 먼저 다시 보내주는건 TCP상 불가능합니다.
그리고 그렇다면 위처럼 순서를 보장해주고, 상대가 어디까지 받았는지, 어디를 못 받았는지 판단하는 것은 운영체제에서 알아서 처리되는 건가요?
네 정확히는 TCP 프로토콜에 의해 그렇습니다.
만약 상대가 못 받았을 경우 다시 보내줘야 한다면, 커널에서 패킷을 송신한 뒤에도 상대가 온전하게 받았다고 신호하기 전까지는 커널 상의 버퍼에 해당 패킷을 계속 저장해두고 있으려나요?
그건 TCP 프로토콜에 의해 네트워크 라이브러리 및 OS에서 알아서 해줍니다.
5. ArraySegment를 사용할 때, Array를 통해 배열의 시작 위치를 넘기고 어떤 위치부터 조회할 지(?) 조회할 위치를 넘기는 부분이 Offset인 것 같은데요.
Offset을 0으로 두고 readPos및 writePos나 DataSize, FreeSize 등을 통해 패킷 시작 위치나 범위를 지정해주는 것은 Offset 값을 변경하며 이를 컨트롤 하는 것이 복잡하기 때문인가요?
질문이 이해 안 갑니다. 이미 처리한 부분과 처리하지 않은 부분을 read, write 커서로 관리하는 것입니다.
6. 4년제 학부 졸업생의 수준에서 서버 코드를 보고 이 부분은 왜 이렇게 작성된 것인지, 어떤 흐름으로 코드가 실행되는지 정도만 이해하면 게임 서버 프로그래머로 취업하기엔 충분할까요...?
아니면 정말 혼자 서버 코어 부분을 작성할 수 있을 정도가 되어야 하는건가요?
취업은 정답이 없습니다. 알면 확률이 높아지는 것일 뿐이죠.
정말 깊이 있게 이해했다면, 그리고 응용이 가능하다면
거기서 클라/서버 연동 포폴을 이쁘게 만들면 좋기는 하지만
"= 서버코어를 작성할 수 있다"는 아닙니다.
서버 프로그래머라고 서버코어를 할 줄 아는건 아니고 경험상 5% 정도만 할 줄 압니다.
0
2024. 05. 25. 23:32
헉 빠른 답변 감사합니다.
5번 질문이 너무 난잡했네요
5번 질문에서는 Offset 값을 변경하는 것으로 커서를 대체할 수 있지 않을까 라는 생각이 들어 질문했던 것인데,
다시 찾아보니 Offset 프로퍼티에는 set이 정의되어 있지 않군요..ㅎㅎ
여전히 2번 질문에 대해서는 의문점이 하나 남는데요.
낚시대를 회수하고, 다시 던지기 전에 누군가 저에게 패킷을 보내게 되면 해당 패킷은 어떻게 처리가 되는지 궁금합니다.
커널 내부에 있는 수신 버퍼에 저장이 되어있다가 다음 ReceiveAsync가 호출될 때 꺼내오는 건가요?
2024. 05. 25. 23:33
질문이 너무 많은건 좋지 않습니다. 스스로 답을 찾아보세요 화이팅