client - server 구현 (1)
알아가기전 알아야할 것들
- 동기적 처리가 일어나는 부분
- socket library ( ex _ winsock2 )
- 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;
}