티스토리 뷰

드디어 로비 부분을 거의 다 만들었습니다. 너무 공부를 안해서 엄청 오래 걸렸는데 그래도 조금씩 꾸준히 해서 거의 완성이 되었네요. 이제 게임 시작하는 부분이랑 게임 내부만 만들면 완성입니다. 이제는 개강도 다가와서 얼른 끝내고 패킷 직렬화나 유니티 같은 나머지 게임 공부들을 해야죠. 

 

일단 게임 서버 부분은 인프런 루카스님의 게임서버 강의를 많이 참고했습니다만, 책에서 배운 내용도 많이 썼습니다. 다만 처음 써보는 거라 통신을 하는 부분들이 조금 어려움이 많았네요.

 

원래는 IOCP를 사용해 보려고 했지만, 처음인 만큼 만들기 쉬운 WSAEventSelect 서버를 사용했습니다.

 

먼저 서버를 간단하게 설명드리자면 네트워크 부분을 거의 static 형식으로 다 묶어서 사용할 수 있게 만들었습니다.

 

서버 네트워크 헤더 부분

더보기
#pragma once
#include "pch.h"
#include "Datas.h"
#include <NetAddress.h>

class NetWorking
{
public:
	static void Init();
	static void Clear();

	static SOCKET CreateSocket();

	static bool SetLinger(SOCKET socket, uint16 onoff, uint16 linger);
	static bool SetReuseAddress(SOCKET socket, bool flag);
	static bool SetRecvBufferSize(SOCKET socket, int32 size);
	static bool SetSendBufferSize(SOCKET socket, int32 size);
	static bool SetTcpNoDelay(SOCKET socket, bool flag);
	static bool SetUpdateAcceptSocket(SOCKET socket, SOCKET listenSocket);

	static bool Bind(SOCKET socket, NetAddress netAddr);
	static bool BindAnyAddress(SOCKET socket, uint16 port);
	static bool Listen(SOCKET socket, int32 backlog = SOMAXCONN);
	static void Close(SOCKET& socket);

	static bool AddScoketInfo(SOCKET sock, int Index);
	static void RemoveSocketInfo(int nIndex);
		
	static bool SetCheck(int index);
	static int WaitForMultipleEvent();
	static int NetWorkEvent(int i, WSANETWORKEVENTS& Event);

	static WSAEVENT* GetEventArray() { return _EventArray; }
	static Datas* GetSocketInfo(int index) { return _SocketInfo[index]; }
	static int GetTotalSockets() { return _totalSockets; }
	static int GetHostSockets() { return _HostSockets; }

	static void err_quit(const char* msg);
	static void err_display(int errcode);
private:
	static Datas* _SocketInfo[WSA_MAXIMUM_WAIT_EVENTS];
	static WSAEVENT _EventArray[WSA_MAXIMUM_WAIT_EVENTS];

	static int _HostSockets;
	static int _totalSockets;
};

template<typename T>
static inline bool SetSockOpt(SOCKET socket, int32 level, int32 optName, T optVal)
{
	return SOCKET_ERROR != ::setsockopt(socket, level, optName, reinterpret_cast<char*>(&optVal), sizeof(T));
}

 

그리고 서버부분에서 사용하는 형식으로 만들었는데, 사실 어거지로 만든 부분들이 조금 많아서 저도 많이 보기 어려운 부분들이 많았습니다. 다음 서버를 만들때는 이번 경험을 참고해서 정리를 잘 하려고 합니다.

 

서버코드

더보기
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include "NetWorking.h"
#include "Room.h"

int main()
{
    //윈속 초기화
    NetWorking::Init();
    Room::Init();

    //소켓 생성
    SOCKET listen_s = NetWorking::CreateSocket();
    if (listen_s == INVALID_SOCKET)
        NetWorking::err_quit("socket");

    if(!NetWorking::BindAnyAddress(listen_s, PORT)) NetWorking::err_quit("Bind");
    if(!NetWorking::Listen(listen_s, SOMAXCONN)) NetWorking::err_quit("listen");
    
    NetWorking::AddScoketInfo(listen_s, 0);

    //데이터 통신에 사용할 변수
    WSANETWORKEVENTS NetworkEvents;
    SOCKET clientsock;
    SOCKADDR_IN clientaddr;
    int addrlen, i, retVal;

    while (1) {
        //이벤트 객체 관찰
        i = NetWorking::WaitForMultipleEvent();
        if (i == WSA_WAIT_FAILED) continue;

        i -= WSA_WAIT_EVENT_0;

        retVal = NetWorking::NetWorkEvent(i, NetworkEvents);
        if (retVal == SOCKET_ERROR) continue;

        //FD_ACCEPT 이벤트 처리
        if (NetworkEvents.lNetworkEvents & FD_ACCEPT) {
            if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
                NetWorking::err_quit("NetworkEvents.iErrorCode[FD_ACCEPT_BIT]");
                continue;
            }
            addrlen = sizeof(clientaddr);
            clientsock = accept(NetWorking::GetSocketInfo(i)->GetSocket(), (SOCKADDR*)&clientaddr, &addrlen);
            if (clientsock == INVALID_SOCKET) {
                NetWorking::err_quit("Accept()");
                continue;
            }
            cout << "[TCP서버] 클라이언트 접속 IP : " << inet_ntoa(clientaddr.sin_addr) << " " << ntohs(clientaddr.sin_port) << endl;
            if (NetWorking::GetTotalSockets() >= WSA_MAXIMUM_WAIT_EVENTS) {
                cout << "[오류] 더이상 접속을 받아들일 수 없습니다." << endl;
                closesocket(clientsock);
                continue;
            }
            if(!NetWorking::AddScoketInfo(clientsock, -1))
                continue;
        }

        //FD_READ 이벤트 처리
        if (NetworkEvents.lNetworkEvents & FD_READ) {
            if ((NetworkEvents.lNetworkEvents & FD_READ) && (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)) {
                NetWorking::err_display(NetworkEvents.iErrorCode[FD_READ_BIT]);
                continue;
            }
            Datas* socket = NetWorking::GetSocketInfo(i);
            retVal = recv(socket->GetSocket(), reinterpret_cast<char*>(socket->GetLoby_Data()), 512, 0);
            if (retVal == SOCKET_ERROR) {
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    NetWorking::err_quit("recv()");
                }
                continue;
            }
            cout << "Recv : " << retVal << endl;
            if (socket->GetLoby_Data()->Type == 1) {
                socket->GetLoby_Data()->RoomNumber = Room::GetHostSize();
                Room::PushHost(i);
            }
            
            //Clinet 패킷 처리
            else if (socket->GetLoby_Data()->Type == 2) {

                //Loby 입장 시처리 
                if (socket->GetLoby_Data()->RoomNumber == -1) {
                    //호스트 갯수
                    int size = Room::GetHostSize();
                    socket->GetLoby_Data()->RoomNumber = size;

                    //보낼 길이 측정
                    int len = 0;
                    Datas* data;
                    for (int j = 0; j < size; j++) {
                        data = NetWorking::GetSocketInfo(Room::GetHostIndex(j));
                        len = sizeof(*(data->GetLoby_Data()));

                        retVal = send(socket->GetSocket(), reinterpret_cast<const char*>(&len), sizeof(int), 0);
                        retVal = send(socket->GetSocket(), reinterpret_cast<const char*>(data->GetLoby_Data()), len, 0);
                        cout << "데이터 전송 : " << retVal << endl;
                    }
                    len = 0;
                    send(socket->GetSocket(), reinterpret_cast<char*>(&len), sizeof(int), 0);
                }
                //Room 입장시 Host와 Client에 서로의 정보 전달
                else {
                    Room::PushClient(socket->GetLoby_Data()->RoomNumber, i);

                    Datas* data;
                    data = NetWorking::GetSocketInfo(Room::GetHostIndex(socket->GetLoby_Data()->RoomNumber));
                    send(data->GetSocket(), reinterpret_cast<char*>(socket->GetLoby_Data()), sizeof(*(socket->GetLoby_Data())), 0);
                    send(socket->GetSocket(), reinterpret_cast<char*>(data->GetLoby_Data()), sizeof(*(data->GetLoby_Data())), 0);
                }
            }
            //Client 측에서 방 나올때 전달
            else if (socket->GetLoby_Data()->Type == -1) {
                Datas* data = data = NetWorking::GetSocketInfo(Room::GetHostIndex(socket->GetLoby_Data()->RoomNumber));
                send(socket->GetSocket(), reinterpret_cast<char*>(socket->GetLoby_Data()), sizeof(*(socket->GetLoby_Data())), 0);
                send(data->GetSocket(), reinterpret_cast<char*>(socket->GetLoby_Data()), sizeof(*(socket->GetLoby_Data())), 0);

                Room::DeleteClient(NetWorking::GetSocketInfo(i)->GetLoby_Data()->RoomNumber);
            }
        }

       /* if (NetworkEvents.lNetworkEvents & FD_WRITE) {
            if ((NetworkEvents.lNetworkEvents & FD_WRITE) && (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)) {
                NetWorking::err_display(NetworkEvents.iErrorCode[FD_WRITE_BIT]);
                continue;
            }
        }*/

        //FD_CLOSE 이벤트 처리
        if (NetworkEvents.lNetworkEvents & FD_CLOSE) {
            if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                NetWorking::err_quit("NetworkEvents.iErrorCode[FD_CLOSE_BIT]");
            if (NetWorking::GetSocketInfo(i)->GetLoby_Data()->Type == 1) {
                Room::DeleteHost(NetWorking::GetSocketInfo(i)->GetLoby_Data()->RoomNumber);
            }
            else {
                Room::DeleteClient(NetWorking::GetSocketInfo(i)->GetLoby_Data()->RoomNumber);
            }
            NetWorking::RemoveSocketInfo(i);
        }
    }
    //윈속 종료
    WSACleanup();
    return 0;
}

 


클라이언트는 예전에 QT로 만들었던 미로찾기 게임을 토대로 만들었습니다. 다만, 아직 게임부분은 건들지 못하고 로비 부분만 작성했습니다. 로비부분도 서버와 마찬가지고 네트워크는 static 형식으로 만들고 Menu 부분에서 사용할 수 있게 만들었습니다.

 

Menu.h

더보기
#pragma once

#include "Player.h"
#include "Board.h"
#include "Consts.h"
#include "Networking.h"
#include "pch.h"
#include <qbuttongroup.h>

class Menu : public QGraphicsScene{
	Q_OBJECT
private:
	int _size;
	Player* _player;
	QGraphicsView* _view;

	//게임 판
	Board* board;

	//화면들 
	QGraphicsScene Loby;
	QGraphicsScene Menus;
	QGraphicsScene Room;
private:
	static User_Data user;
	struct Loby_Data tmp;

	bool isLoop = false;
	QMetaObject::Connection Connect;
public:
	Menu(QGraphicsView* view);
	virtual ~Menu();

private:
	//초기화 부분
	void Socket_init();
	void Set_Loby();
	void Set_Room();
	void Set_Menu();
	void Windows_setting(int widht, int height);
	void Menu_Init();
	void Loby_Init();

	//스레드 생성하여 주기적으로 방 리셋
	void Create_Thread();
	//정보 저장
	void SetData(int _type, int _RoomNumber, int _level, std::string _name);
	
private slots:
	//게임 시작
	void btn_click();

	//로비 
	void Show_Menu();

	//로비에서 방 입장
	void InSide_Room(int i);

	//방 생성
	void Create_Room();

	void Start_Game();

	//로비 정보 받기
	void Recv_Data();

	//로비 입장
	void Loby_Show();
private:

	//게임 시작할때 정보
	QLineEdit _Level_INPUT;
	QLineEdit _NAME_INPUT;
	QLabel _Level_LABEL;
	QLabel _NAME_LABEL;

	//방 생성 후 버튼
	QPushButton _button;
	QPushButton _button2;

	//처음 화면 버튼
	QPushButton _Create_Button;
	QPushButton _In_Button;

	//로비 정보 및 입장
	QPushButton _Reset_Button;
	QLabel* _Loby_Label[10];
	QPushButton* _Input_Button[10];
	QButtonGroup* _Loby_Button;

	//방 입장한 사람들 정보
	QLabel _Host_Information;
	QLabel _Client_Information;
};

이렇게 글 쓰면서 코드 보니까 정말 더럽게 코드를 짯네요.. ㅜㅜ 좀 더 실력 향상을 위해 노력해야겠네요.

 

CPP 코드

더보기
#include "Menu.h"
#include <QtCore/qdebug.h>
#include "pch.h"
#include <string>

User_Data Menu::user;

Menu::Menu(QGraphicsView* view)
	: _view(view)
{
	Networking::Init();
	Set_Menu();

	Loby_Init();
	Menu_Init();
	Show_Menu();

	Socket_init();
	_view->show();
}

Menu::~Menu()
{
	isLoop = false;
	Networking::Close(user.socket);
	Networking::Clear();
}

void Menu::Set_Loby()
{
	Windows_setting(300, 450);
	_view->setScene(&Loby);
}
void Menu::Set_Room()
{
	Windows_setting(250, 250);
	_view->setScene(&Room);
}
void Menu::Set_Menu()
{
	Windows_setting(250, 250);
	_view->setScene(&Menus);
}

void Menu::Windows_setting(int width, int height)
{
	_view->setMinimumHeight(height);
	_view->setMinimumWidth(width);

	_view->setMaximumHeight(height);
	_view->setMaximumWidth(width);
}
void Menu::Menu_Init()
{
	Menus.addWidget(&_Create_Button);
	Menus.addWidget(&_In_Button);

	Menus.addWidget(&_Level_INPUT);
	Menus.addWidget(&_NAME_INPUT);

	Menus.addWidget(&_Level_LABEL);
	Menus.addWidget(&_NAME_LABEL);

	Room.addWidget(&_button);
	Room.addWidget(&_button2);

	Room.addWidget(&_Host_Information);
	Room.addWidget(&_Client_Information);

	_Level_INPUT.setGeometry(_view->minimumWidth() / 2 - 120, _view->minimumHeight() / 2 - 100, 100, 20);
	_NAME_INPUT.setGeometry(_view->minimumWidth() / 2 + 20, _view->minimumHeight() / 2 - 100, 100, 20);

	_Level_LABEL.setText("Level (2~5)");
	_NAME_LABEL.setText("Room Name Input");

	_Level_LABEL.setAlignment(Qt::AlignCenter);
	_NAME_LABEL.setAlignment(Qt::AlignCenter);

	_Level_LABEL.setGeometry(_view->minimumWidth() / 2 - 120, _view->minimumHeight() / 2 - 120, 100, 20);
	_NAME_LABEL.setGeometry(_view->minimumWidth() / 2 + 20, _view->minimumHeight() / 2 - 120, 100, 20);

	_Host_Information.setAlignment(Qt::AlignCenter);
	_Client_Information.setAlignment(Qt::AlignCenter);

	_Host_Information.setGeometry(_view->minimumWidth() / 2 - 120, _view->minimumHeight() / 2 - 120, 100, 20);
	_Client_Information.setGeometry(_view->minimumWidth() / 2 + 20, _view->minimumHeight() / 2 - 120, 100, 20);
	
	_Create_Button.setText("Create Room");
	_In_Button.setText("Inside Room");

	_Create_Button.setGeometry(_view->minimumWidth() / 2 - 120, _view->minimumHeight() / 2 - 50, 100, 20);
	_In_Button.setGeometry(_view->minimumWidth() / 2 + 20, _view->minimumHeight() / 2 - 50, 100, 20);

	_button.setText("Start Game");
	_button2.setText("Exit");

	_button.setGeometry(_view->minimumWidth() / 2 - 120, _view->minimumHeight() / 2 - 50, 100, 20);
	_button2.setGeometry(_view->minimumWidth() / 2 + 20, _view->minimumHeight() / 2 - 50, 100, 20);
	
	Connect = QGraphicsScene::connect(&_button2, SIGNAL(clicked()), this, SLOT(Show_Menu()));
	QGraphicsScene::connect(&_Create_Button, SIGNAL(clicked()), this, SLOT(Create_Room()));

	QGraphicsScene::connect(&_In_Button, SIGNAL(clicked()), this, SLOT(Loby_Show()));
	QGraphicsScene::connect(&_button, SIGNAL(clicked()), this, SLOT(Start_Game()));
}

void Menu::Loby_Init()
{
	_Loby_Button = new QButtonGroup(this);

	for (int i = 0; i < 10; i++) {
		_Loby_Label[i] = new QLabel;
		_Loby_Label[i]->setGeometry(10, (i * 35), 150, 25);

		_Input_Button[i] = new QPushButton;
		_Input_Button[i]->setGeometry(210, (i * 35), 40, 25);
		_Input_Button[i]->setText(QString::fromLocal8Bit("입장"));
		_Loby_Button->addButton(_Input_Button[i], i);

		Loby.addWidget(_Loby_Label[i]);
		Loby.addWidget(_Input_Button[i]);
	}
	Loby.addWidget(&_Reset_Button);

	_Reset_Button.setText("Reset");
	_Reset_Button.setGeometry(10, 350, 50, 25);
	QGraphicsScene::connect(&_Reset_Button, SIGNAL(clicked()), this, SLOT(Recv_Data()));
	QGraphicsScene::connect(_Loby_Button, SIGNAL(buttonClicked(int)), this, SLOT(InSide_Room(int)));
}

void Menu::SetData(int _type, int _RoomNumber, int _level, std::string _name)
{
	user.loby.Type = _type;
	user.loby.RoomNumber = _RoomNumber;
	user.loby.Level = _level;
	user.loby.name = _name;
}

void Menu::Start_Game()
{
	
}

void Menu::Recv_Data()
{
	for (int i = 0; i < 10; i++) {
		_Loby_Label[i]->setText("");
	}

	int retVal = Networking::Send_Loby(user.socket, user.loby);

	if (retVal == SOCKET_ERROR)
		Networking::err_quit("Send");
	int i = 0, size = 0;

	while (1) {
		::recv(user.socket, reinterpret_cast<char*>(&size), sizeof(int), 0);
		if (size == 0)
			break;
		retVal = ::recv(user.socket, reinterpret_cast<char*>(&tmp), size, 0);
		if (retVal == SOCKET_ERROR)
			Networking::err_quit("Recv");

		char str[40];
		sprintf_s(str, "%d, %d, %s", tmp.RoomNumber, tmp.Level, tmp.name.c_str());

		qDebug() << str << endl;
		_Loby_Label[i++]->setText(str);
	}
}

void Menu::InSide_Room(int i)
{
	disconnect(Connect);
	Connect = QGraphicsScene::connect(&_button2, SIGNAL(clicked()), this, SLOT(Loby_Show()));

	SetData(2, i, 2, user.loby.name);
	int retVal = send(user.socket, reinterpret_cast<char*>(&user.loby), sizeof(user.loby), 0);
	if (retVal == SOCKET_ERROR)
		Networking::err_quit("Loby Send");
	_Host_Information.setText(user.loby.name.c_str());
	Create_Thread();
	Set_Room();
}

//방 로비 입장
void Menu::Loby_Show()
{	
	if (isLoop == true) {
		tmp.Type = -1;
		::send(user.socket, reinterpret_cast<char*>(&tmp), sizeof(tmp), 0);

		isLoop = false;
	}
	else {
		Socket_init();

		if (::connect(user.socket, reinterpret_cast<SOCKADDR*>(&user.sockaddr), sizeof(SOCKADDR_IN))) {
			Networking::err_quit("Connect");
		}
	}
	string name = _NAME_INPUT.text().toStdString();
	int Level = _Level_INPUT.text().toInt();
	SetData(2, -1, Level, name);

	Recv_Data();
	Set_Loby();
}

//방 생성
void Menu::Create_Room() 
{
	Socket_init();

	if (::connect(user.socket, (SOCKADDR*)(&user.sockaddr), sizeof(SOCKADDR_IN))) {
		Networking::err_quit("Connect");
	}

	string name = _NAME_INPUT.text().toStdString();
	int Level = _Level_INPUT.text().toInt();
	SetData(1, 1, Level, name);

	int retVal = ::send(user.socket, reinterpret_cast<char*>(&user.loby), sizeof(user.loby), 0);
	if (retVal == SOCKET_ERROR)
		Networking::err_quit("Send");

	_Host_Information.setText(user.loby.name.c_str());

	Create_Thread();
	Set_Room();
}
void Menu::Show_Menu()
{
	isLoop = false;
	Networking::Close(user.socket);

	Set_Menu();
}

void Menu::Socket_init()
{
	user.socket = socket(AF_INET, SOCK_STREAM, 0);
	if (user.socket == INVALID_SOCKET) {
		Networking::err_quit("CreateSocket()");
	}

	ZeroMemory(&user.sockaddr, sizeof(sockaddr));

	user.sockaddr.sin_family = AF_INET;
	user.sockaddr.sin_port = ::htons(PORT);
	user.sockaddr.sin_addr.s_addr = ::inet_addr(SERVERIP);
}

void Menu::btn_click() {
	_size = _Level_INPUT.text().toInt();
	_player = new Player(1, 1, _size, _NAME_INPUT.text());
	if (_size < 6 && _size > 1) {
		int trap = _size * 5;
		_size *= 10;
		_size += 1;
		board = new Board(_size, _player, trap);
		Windows_setting(_size * (Consts::BOARD_IMAGE_SIZE + 1), _size * (Consts::BOARD_IMAGE_SIZE + 1) + 30);
		_view->setScene(board);
		_view->setAlignment(Qt::AlignTop);
		_view->update();
	}
	else {
		return;
	}
}

void Menu::Create_Thread()
{
	isLoop = true;
	std::thread t1([=]() {
		while (true) {			
			int retVal = recv(user.socket, reinterpret_cast<char*>(&tmp), BUFSIZE, 0);			

			if (isLoop == false)
				break;

			if (retVal == SOCKET_ERROR) {
				Networking::err_quit("Recv()");
				continue;
			}

			if (tmp.Type == -1 && user.loby.Type == 2)
				break;
			else if (tmp.Type == -1)
				_Client_Information.setText("");
			else
				_Client_Information.setText(tmp.name.c_str());
		}
		_Client_Information.setText("");
		return;
		});
	t1.detach();
}

 

일단은 이정도인데, 농땡이를 심하게 피웠다는게 보이네요. 이런 짧은 코드를 며칠째 짠건지.. 얼른 끝내도록 노력해보겠습니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함