I'm FanJae.

[C++ Server] Day 1. Echo Server 구현. 본문

Toy Project/Multi Room Cheating Server

[C++ Server] Day 1. Echo Server 구현.

FanJae 2024. 9. 25. 16:23

1. 윈도우 소켓 프로그래밍 서버 설정

- 윈도우 소켓(윈속)도 기본적으로 BSD 계열 유닉스 소켓과 유사한 부분을 많이 띄고 있다.

 

1-1. 윈도우 소켓 프로그래밍 서버의 설정 순서

① 윈속(Winsock) 초기화 -> WSAStartup() 호출.

② 소켓 생성 -> socket() 호출

③ 소켓 주소 구조체 설정 -> sockaddr_in 구조체

④ 소켓에 ip주소 및 포트 정보 할당 -> bind() 호출

⑤ 연결 대기 상태 -> listen() 호출

⑥ 연결 요청 수락 -> accept() 호출

⑦ 소켓 종료 및 윈속 해제-> closesocket(), WSACleanup()

- 위 순서대로 서버를 구성한다. 

 

※ sockaddr_in 구조체 관련 정보

typedef struct sockaddr_in {
#if ...
  short          sin_family;
#else
  ADDRESS_FAMILY sin_family;
#endif
  USHORT         sin_port;
  IN_ADDR        sin_addr;
  CHAR           sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;

- 링크 : https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in

 

sin_family : 주소 체계를 의미. 이 멤버는 AF_INET으로 설정함.

sin_port : 전송 프로토콜의 포트 번호

sin_addr : IPv4 전송 주소를 포함하는 IN_ADDR 구조체

sin_zero[8] : 시스템 사용을 위해 예약.

 

1-2. 기본적인 서버 구현

① 윈속 초기화  -> WSAStartup() 호출.

#include <iostream>
#include <WinSock2.h>

int main(int argc, char *argv[])
{

    char message[] = "Message";
	WSADATA wsaData;
	SOCKET serverSocket; 
	SOCKADDR_IN serverAddr;
    
	if (argc != 2) // Port 번호를 직접 작성하는 방식으로 사용하기 위함으로 필수는 아님.
	{
		std::cerr << "Usage : <port> : " << argv[0];
		return 1;
	}
    
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		std::cerr << "WSAStartup() Error" << std::endl;
		return 1;
	}
}

 

② 소켓 생성  -> socket() 호출

serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET)
{
    std::cerr << "Socket() Error" << std::endl;
    return 1;
}

 

③ 서버 주소 설정  -> sockaddr_in 구조체 설정

serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = atoi(argv[1]);

 

④ 소켓에 ip주소 및 포트 정보 할당 -> bind() 호출

if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
    std::cerr << "bind() Error" << std::endl;
    return 1;
}

 

⑤ 연결 대기 상태 -> listen() 호출

if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR)
{
    std::cerr << "listen() Error" << std::endl;
    return 1;
}

- 서버 소켓을 수신 상태로 만들어준다. 문제는 여기서 2번째 인자인 backlog에 들어갈 값이 애매해서 찾아보았다.

- Backlog가 무엇일까 확인해 볼 때 다음과 같이 적혀있다.

The maximum length of the queue of pending connections. If set to SOMAXCONN, the underlying service provider responsible for socket s will set the backlog to a maximum reasonable value.

 

- 즉, 연결을 대기중인 클라이언트의 요청을 큐에 보관할 수 있는 최대 개수를 지정하는 것이다.

- SOMAXCONN으로 소켓 s에 대해서 서비스 공급자가 합리적인 값으로 설정한다고 한다.

 

⑦ 연결 요청 수락 - > accept() 호출

SOCKET clientSocket;
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
		
clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET)
{
	std::cerr << "accept() Error" << std::endl;
	return 1;
}
send(clientSocket, message, sizeof(message), 0);

- 실질적으로 Multi Thread 환경을 전제하여 만들고 있기 때문에 이곳에서 Thread 처리를 진행해야 한다.

 

⑧ 소켓 종료 및 윈속 해제-> closesocket(), WSACleanup()

closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();

- 서버 소켓을 종료 해주고, 소켓 라이브러리를 해제하는 것으로 마무리 된다.

 

 


2. 테스트용 클라이언트 작성 (C++ CUI)

- 기본적인 서버기 때문에 테스트 클라이언트로 해당 서버가 정상 작동하는지만 확인한다.

- 이를 먼저 하는 이유는 소켓 통신 자체에 이상이 없는지 먼저 확인하기 위함이다.

 

2-1. 윈도우 소켓 프로그래밍 서버의 설정 순서

① 윈속(Winsock) 초기화 -> WSAStartup() 호출.

② 소켓 생성 -> socket() 호출

③ 소켓 주소 구조체 설정 -> sockaddr_in 구조체

④ 연결 요청 -> connect() 호출

⑤ 소켓 종료 및 윈속 해제-> closesocket(), WSACleanup()

 

※ 서버에 비하면, 클라이언트 쪽은 연결 과정이 훨씬 더 간단하다.

 

2-2. 기본적인 서버 구현

① 윈속 초기화  -> WSAStartup() 호출.

#include <iostream>
#include <Winsock2.h>

int main(int argc, char* argv[])
{
	char buffer[1024];
	WSAData wsaData;
	SOCKET clientSocket;
	sockaddr_in serverAddr;

	if (argc != 3)
	{
		std::cerr << "Usage : " << argv[0] << " <IP> <port>";
		return 1;
	}
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		std::cerr << "WSAStartup() Error";
		return 1;
	}
}

 

② 소켓 생성 -> socket() 호출

clientSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET)
{
	std::cerr << "socket() Error";
	return 1;
}

 

③ 소켓 주소 구조체 설정 -> sockaddr_in 구조체

serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
serverAddr.sin_port = atoi(argv[2]);

 

④ 연결 요청 -> connect() 호출

if (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
	std::cerr << "connect() Error";
	return 1;
}

 

⑤ 소켓 종료 및 윈속 해제-> closesocket(), WSACleanup()

closesocket(clientSocket);
WSACleanup();

 

 


3. 테스트

 

 

- Server와 Client간 메시지를 정상적으로 주고 받는 것을 확인할 수 있다.

- 이제 Thread를 이용해서 이를 어떻게 Multi-Room Chatting Server를 구현할지 구성할 것이다.


4. TODO

1) Server

- Multi Thread를 이용한 Multi-Room 구현

2) Client

- 기능 테스트를 위한 CUI 환경 필요

- C# Windows Forms을 이용한 GUI 구현 필요

Comments