WinCNT

Socket Programming in C/C++ 본문

게임 프로그래밍(학습 내용 정리)/네트워크 프로그래밍

Socket Programming in C/C++

WinCNT_SSS 2022. 4. 18. 16:45

TCP/IP 4계층과 소켓

인터넷의 핵심인 TCP/IP는 데이터 전송 과정에서의 역할에 따라

응용 계층, 전송 계층, 인터넷 계층, 네트워크 인터페이스 계층이라는 4개의 계층으로 구성된다

 

응용 계층과 전송 계층을 소통하기 위해 사용하는 것이 Socket이다

이 Socket을 프로그래밍하므로써 응용 계층에서 송수신 데이터를 다룰 수 있게 된다

 

TCP/IP 4계층과 소켓

소켓이란?

네트워크에서 소켓이란 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다

오늘날 대부분의 통신은 인터넷 프로토콜을 기반이므로, 대부분의 네트워크 소켓은 인터넷 소켓이다.

네트워크 통신을 위한 프로그램들은 소켓을 생성하고, 이 소켓을 통해서 서로 데이터를 교환한다

 

전송 계층, 인터넷 계층, 네트워크 인터페이스 계층의 데이터들은 응용 계층에서 다룰 수 없다

메모리에 올라와 있는 있어야 응용 계층에서 다룰 수 있다(즉, 코딩할 수 있게 된다)

이 때 필요한 것이 소켓(!!)이다

 

Windows OS에서는 OS가 소켓을 커널 오브젝트로 관리한다

(다시 말해 네트워크를 담당하는 커널 오브젝트을 소켓이라 함)

프로세스에서 소켓을 생성하면 윈도우 OS가 소켓 커널 오브젝트를 생성하고 그 핸들을 돌려준다

 

또한 OS가 소켓 커널 오브젝트를 만들 때 디폴트로 버퍼(소켓 버퍼)가 생성된다

(옵션으로 조정 가능)

소켓을 많이 만들게 되면 소켓마다 소켓 버퍼가 생성될 것이고
결국 커널 영역의 메모리가 넘처서 블루 스크린이 뜰 것이다

이 소켓(핸들)을 이용해서 커널에 전송 계층의 데이터에 대한 읽고 쓰기를 부탁하면

커널이 우선 소켓 커널 오브젝트의 버퍼에 데이터를 담고,

그 다음 OS가 소켓 버퍼를 이용해서 전송 계층의 데이터를 다룰 것이다

소켓을 다루는 방법

Windows OS의 경우 Winsock의 사용법을 알면 된다

하지만 그 다음에 만드는 것은 스스로의 머리를 짜내어서 만들어야 한다

소켓 프로그래밍이란

TCP 연결의 디폴트 모델

 

Listen은 TCP로 연결한다고만 알려준다

 

Bind는 서버에 필요한 것

Client에서는 포트 번호가 자동으로 할당되지만

서버에는 특정한 포트 번호를 직접 지정해야 한다

(그래야 클라이언트가 어디로 보내야 하는지 알 수 있으므로)

 

Listen(TCP 소켓) 상태에서 대기 중에 어떤 클라이언트가 Connet를 하면

Accept를 하면서 새로운 소켓(클라이언트 소켓)을 만든다

(반대로 Accept를 하지 새로운 소켓을 만들지 않는다)

이 때부터 서버와 클라이언트가 데이터를 주고 받을 준비가 됐다는 뜻이다

데이터의 주고 받음은 소켓 버퍼의 Read/Write로 이루어진다

 

Blocking(위의 그림) 방식이면 Read/Write할 데이터가 없으면 계속 기다린다

이를 피하기 위하는 방법으로는 비동기를 적용하는 방식이 있다

WSAAsyncSelect

비동기 방식으로 소켓의 상태를 통지받기 위해 사용하는 API 함수

WSAAsyncSelect(Listen, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);

위의 코드는 Listen이라는 소켓이 FD_ACCEPT나 FD_CLOSE의 상태가 되면

윈도우 메시지로 통지(비동기)해달라고 설정하는 함수

 

case FD_ACCEPT:
    clientSocket = accept(wParam, (SOCKADDR*)&clientAddr, &sockaddrLen);
    WSAAsyncSelect(clientSocket, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);

 

FD_READ가 발생하면 Recieve로 소켓의 수신 버퍼의 데이터를 가져오고,

다시 읽을 데이터가 있으면 통지해달라는 요청을 하게 된다

case FD_READ:
    nRecv = recv((SOCKET)wParam, recvBuffer, DATA_BUFSIZE, 0);

 

만약 Receive를 했는데 수신 버퍼에 데이터가 남아있으면 OS가 다시 FD_READ를 발생시킨다

FD_READ와 Recieve의 코딩 실수의 예
1. FD_READ가 발생했는데 Receive를 하지 않을 경우
  클라이언트 소켓에 대해서 요청을 하지 않으므로 FD_READ가 다시 발생하지 않음
  이를 방지하기 위해서 0바이트라도 읽어와야 한다
2. 수신 버퍼를 비우지 않는 경우
  수신의 버퍼가 꽉 차게 되면 클라이언트 쪽에 버퍼가 꽉 찼다고 알려준다
  그러면 클라이언트는 송신 버퍼에 데이터를 계속 채우고 송신은 하지 않게 되고
  결국 송신 버퍼도 꽉 차게 되면 would block이 발생한다

TCP 소켓 연결 후에...

TCP는 소켓이 2개 필요하다

Listen용 소켓

open, bind, listen의 3단계

open은 소켓을 만드는 것

bind는 지정된 주소와 포트 번호에 바인딩하는 것

listen은 서버 소켓을 수동 모드로 설정하여 클라이언트가 연결을 설정하기 위해 서버에 접근할 때까지 기다리는 것

 

디폴트가 오픈, 바인드, 웨이트이지만 문제는 블로킹이 디폴트이다

 

이벤트 발생

비동기로 바꾸고 싶으면?

데이터가 도착한 건 모르지만

비동기는 OS으로부터 통지(notify)를 받는 모델

 

비동기를 만드는 방법은 2가지

1. 윈도우 메시지

2. Async

어떤 소켓에 사건이 발생하면 커널의 이벤트 객체로 앱에게 통지해주세요

그럼 WinProc에서 처리하겠습니다 라는 모델

 

AsyncSelect든 EventSelect든 비동기 소켓이다

FD_RECV은 수신 버퍼에 읽을 것이 있다고 OS가 통지하는 것

(실제 도착 시간은 더 전일 수 있다)

 

수신 버퍼를 다 안 읽었다면 OS가 다시 통지해준다

비동기 소켓은 읽을 것이 없어도 would block(10035)으로 다시 돌아온다

 

Client가 Send()로 Server에 보내는 상황(TCP)에서

서버의 수신 버퍼에 데이터가 꽉 찼으면

클라이언트의 데이터도 송신 버퍼에 계속 쌓이게 되고

송신 버퍼도 다 차면 would block이 발생하고 FD_WRITE이 뜨게 된다

따라서 FD_WRITE는 정상적인 상황에서는 잘 발생하지 않는다

Remote 관리는 어떻게 할 것인가

Winsock 버퍼에 있는 것을 읽어 오는 것

Listen 소켓 하나랑 클라이언트 소켓 여러 개

송신 버퍼도 별도로 필요하다

FirstWinServer

솔루션

주기적으로 Update 등으로 OnRecv 등을 하는 것이 좋다

업데이트 할 때 타임 아웃도 관리하면서 병목 현상을 제어하는 것이 좋음

 

참고 사이트)

https://www.geeksforgeeks.org/socket-programming-cc/

 

Socket Programming in C/C++ - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

SSS