ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Socket 동기 처리
    책/게임서버 프로그래머 책 2024. 6. 2. 01:15
    client - server 구현 (1)
    알아가기전 알아야할 것들
    1. 동기적 처리가 일어나는 부분
    2.  socket library ( ex _ winsock2 )
    3. client - server 구현

     

    동기적 처리가 일어나는 부분

    • CLIENT
      • connect() 과정 : server와 통신연결을 위해 응답이 올때까지 기다린다.
      • send() 과정 : 서버에서 해당 메시지가 제대로 보내는지 확인 과정을 기다린다.
      • recv() 과정 : 서버에서 송신한 데이터가 오는 것을 기다리고, 데이터의 결과를 확인한다.
    • SERVER
      • accept() 과정 : client와의 연결을 위해 새로운 socket을 생성할 때까지를 기다린다.
        • Listen() 과정까지는 client연결을 기다리는 중이고, accept단계에서 연결작업이 진행된다
      • send() 과정 : 데이터 송신 버퍼가 찰 때까지 블로킹 될 가능성이 존재
      • recv() 과정 : 데이터 수신할 때까지 대기

     

    SOCKET LIBRARY

    • Winsock2.h
      • CLIENT + SERVER 공통
        • WSAStartup () : 소켓 라이브러리를 사용하기 전 초기화 목적과 버전 명시를 위해 사용
      • CLIENT
        • getaddrinfo() : 소켓 정보 (protocol, stream 방식 등)을 연결리스트로 저장
          • 동적으로 할당되기에 Free 과정 필요 ( freeaddrinfo() )
        • socket () : 소켓 생성
        • connect () : 소켓 연결 (*서버 접속)
        • send () : 데이터 송신
        • recv () : 데이터 수신
        • closesocket () : 소켓 종료
      • SERVER
        • socket () :  소켓 생성
        • bind () : 소켓 방식과 클라이언트 요청 IP , 열어둘 PORT를 소켓에 저장
        • listen() : client 연결 요청을 대기 (*바로 반환됨 - queue로 수신 저장)
        • accept () : listen queue의 저장된 내용을 확인 후 client 연결 요청의 새로운 소켓 생성
        • send() : 데이터 송신
        • recv() : 데이터 수신
        • closesocket() : 소켓 종료

     

    CLIENT 구현

    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <iostream>
    #include <chrono>
    #include <stdexcept>
    #include <string>
    
    #pragma comment(lib, "Ws2_32.lib")
    
    class Winsock {
    public:
    	Winsock() {
    		WSADATA wsaData;
    		int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // winsock2.2 버전명시
    		if (result != 0)
    		{
    			throw std::runtime_error("WSAStartup failed with error : " + std::to_string(result));
    		}
    	}
    
    	~Winsock()
    	{
    		WSACleanup();
    	}
    };
    
    class ClientSocket {
    public:
    	// 소켓 생성자
    	ClientSocket(const std::string& host, const std::string& port)
    	{
    		addrinfo hints = {};
    		hints.ai_family = AF_INET; // IPv4를 명시
    		hints.ai_socktype = SOCK_STREAM; // STREAM 지정 ( 연결지향형 )
    		hints.ai_protocol = IPPROTO_TCP; // TCP 지정 (신뢰성이 있는 데이터 전송)
    
    		addrinfo* result = nullptr;
    		int addrResult = getaddrinfo(host.c_str(), port.c_str(), &hints, &result);
    
    		if (addrResult != 0)
    		{
    			throw std::runtime_error("getaddrinfo failed with errro: " + std::to_string(addrResult));
    		}
    
    		for (addrinfo* ptr = result; ptr != nullptr; ptr = ptr->ai_next)
    		{
    			sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); // 소켓 생성
    			if (sock == INVALID_SOCKET)
    			{
    				continue;
    			}
    
    			if (connect(sock, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR) // 소켓 연결 (서버)
    			{
    				closesocket(sock);
    				sock = INVALID_SOCKET;
    				continue;
    			}
    			break;
    		}
    
    		freeaddrinfo(result);
    
    		if (sock == INVALID_SOCKET)
    		{
    			throw std::runtime_error("Unable to connect to server!");
    		}
    	}
    
    	~ClientSocket()
    	{
    		if (sock != INVALID_SOCKET)
    		{
    			closesocket(sock);
    		}
    	}
    
    	void send(const std::string& message)
    	{
        	// ::send() 를 이용한 이유는 전역으로 명시된 라이브러리 함수를 명시적으로 사용하기 위함 (std::send())
    		int sendResult = ::send(sock, message.c_str(), (int)message.size(), 0);
    		if (sendResult == SOCKET_ERROR)
    		{
    			throw std::runtime_error("send failed with error : " + std::to_string(WSAGetLastError()));
    		}
    	}
    
    	std::string receive(size_t size)
    	{
    		std::string buffer(size, '\0');
            //server 송신 내용 받기
    		int recvResult = recv(sock, &buffer[0], (int)size, 0);
    		if (recvResult > 0)
    		{
    			buffer.resize(recvResult);
    			return buffer;
    		}
    		else if (recvResult == 0)
    		{
    			return {};
    		}
    		else
    		{
    			throw std::runtime_error("recv failed with error : " + std::to_string(WSAGetLastError()));
    		}
    	}
    private:
    	SOCKET sock = INVALID_SOCKET;
    };
    
    
    int main()
    {
    	try
    	{
    		Winsock winsock;
    		ClientSocket socket("localhost", "27015");
    
    		std::string message = "Hello from client";
    		
    		socket.send(message);
    		std::string response = socket.receive(512);
    
    		std::cout << "Response : " << response << std::endl;
    	}
    	catch (const std::exception& e) {
    		std::cerr << "Exception : " << e.what() << std::endl;
    	}
    
    	return 0;
    }

     

    SERVER 구현

    #include <iostream>
    #include <winsock2.h>
    #include <string>
    
    #pragma comment(lib, "Ws2_32.lib")
    
    #define PORT 27015
    #define BUFFER_SIZE 1024
    
    
    // error 값을 문자열로 나타내기위해 사용
    std::string GetLastErrorAsString() {
        DWORD errorMessageID = ::WSAGetLastError();
        if (errorMessageID == 0) {
            return std::string();
        }
    
        LPSTR messageBuffer = nullptr;
        size_t size = FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                          NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL
        );
    
        std::string message(messageBuffer, size);
    
        LocalFree(messageBuffer);
    
        return message;
    }
    
    
    
    class Winsock
    {
    public:
        Winsock()
        {
            WSADATA wsaData;
            int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // winsock 2.2 명시
            if (result != 0)
            {
                throw std::runtime_error("WSAStartup failed with error : " + std::to_string(result));
            }
        }
    
        ~Winsock()
        {
            WSACleanup();
        }
    };
    
    class ServerSocket
    {
    public:
        ServerSocket()
        {
            serverSocket = socket(AF_INET, SOCK_STREAM, 0); //소켓 생성
            if (serverSocket == INVALID_SOCKET)
            {
                WSACleanup();
                throw std::runtime_error("Socket creation failed : " + GetLastErrorAsString());
            }
    
            serverAddr.sin_family = AF_INET; // IPv4 명시
            serverAddr.sin_addr.s_addr = INADDR_ANY; //  모든 네트워크 ip 사용 명시
            serverAddr.sin_port = htons(PORT); // port 지정
    		
            //socket 정보 매핑
            if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
            {
                closesocket(serverSocket);
                throw std::runtime_error("Socket bind failed : " + GetLastErrorAsString());
            }
            //socket 연결 요청 queue 저장
            if (listen(serverSocket, 100) == SOCKET_ERROR)
            {
                closesocket(serverSocket);
                throw std::runtime_error("Socket listen failed : " + GetLastErrorAsString());
            }
    		//listen() queue의 socket 연결 요청 수락 (새로운 socket 생성)
            acceptSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize);
            if (acceptSocket == INVALID_SOCKET)
            {
                closesocket(serverSocket);
                throw std::runtime_error("Socket accept failed : " + GetLastErrorAsString());
            }
            std::cout << "Connection accepted" << std::endl;
    
            
        }
    
        void socket_recv(const char* sendMessage)
        {
            char buffer[BUFFER_SIZE] = { 0, };
            //client send() 수신
            int readValue = recv(acceptSocket, buffer, BUFFER_SIZE, 0);
            if (readValue > 0)
            {
                std::cout << "Message from client : " << buffer << std::endl;
                //server 데이터 송신
                send(acceptSocket, sendMessage, strlen(sendMessage), 0);
                std::cout << "(server -> client) send message" << std::endl;
            }
        }
        ~ServerSocket()
        {
            if (acceptSocket != INVALID_SOCKET)
            {
                closesocket(serverSocket);
                serverSocket = INVALID_SOCKET;
            }
                
            if (acceptSocket != INVALID_SOCKET)
            {
                closesocket(acceptSocket);
                acceptSocket = INVALID_SOCKET;
            }
                
        }
    
    
    private:
        SOCKET serverSocket = INVALID_SOCKET;
        SOCKET acceptSocket = INVALID_SOCKET;
        struct sockaddr_in serverAddr;
        struct sockaddr_in clientAddr;
        int clientAddrSize = sizeof(clientAddr);
    };
    int main()
    {
        try
        {
            
            Winsock winsock;
            ServerSocket serverSocket;
            
            const char* message = "Hello from server";
    
            serverSocket.socket_recv(message);
        }
        catch (const std::exception& e)
        {
            std::cerr << "Exception : " << e.what() << std::endl;
        }
        return 0;
    }
Designed by Tistory.