client - server 구현 (1)
알아야할 것들
- ioctlsocket() : 비동기처리
- getsockopt() : 소켓 확인
- select() : 이벤트 처리 부분
- 동기적 처리가 일어나는 부분
ioctlsocket() 함수
- 소켓핸들에 명령을 주어 설정을 하는 함수
- 명령어 목록
- FIONBIO : 소켓의 동기화/비동기화를 설정
- FIONREAD : 소켓의 읽을 수 있는 데이터 양 검색
- SIOCATMARK : 소켓의 urgent 데이터가 있는지 여부 확인
- SIO_KEEPALIVE_VALS : TCP keep-alive 시간 설정을 변경
- SIO_GET_EXTENSION_FUNCTION_POINTER : 확장 함수 포인터를 가져옴 (*사용자 명령 사용 가능)
getsockopt() 함수
- 소켓의 상태 확인 (연결 중/ 연결 종료/ 연결 에러 ) 등등
- 소켓의 옵션 값 확인
- SO_REUSEADDR : 이미 사용중인 주소를 다른 소켓이 재사용할 수 있도록 허용
- SO_KEEPALIVE : 소켓이 유휴 상태일때 네트워크 연결 상태 확인
- SO_LINGER : 소켓을 닫을 때 해당 소켓에 대한 데이터 전송 대기
- SO_SNDBUF : 송신 버퍼 크기 설정
- SO_RCVBUF : 수신 버퍼 크기 설정
- SO_ERROR : 소켓의 오류 상태 조회
- 등등..
SELECT 간단 이론
- 소켓이 비동기로 처리가 되고 있는 부분이 있다면, 이벤트로써 사용하여 가능한 상태인지 판단 후 처리를 해줘야할 때 사용
- select는 I/O가 가능한지 판단하는 여부 탐색
- 0/1로서 이벤트 처리가 일어난다.
- timeout()값을 설정하여 지정한 시간동안 탐색 가능
- 소켓의 연결 여부를 탐색하여 연결시도
동기적 처리가 일어나는 부분
CLIENT 구현
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <stdexcept>
#include <string>
#pragma comment(lib, "Ws2_32.lib")
class Winsock {
public:
Winsock() {
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
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;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_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;
}
u_long mode = 1;
// ioctlsocket 함수 성공 -> 0 반환
// FIONBIO 비동기처리 mode 설정
// NO_ERROR == 0
if (ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR)
{
closesocket(sock);
sock = INVALID_SOCKET;
continue;
}
if (connect(sock, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR)
{
int error = WSAGetLastError();
if (error != WSAEWOULDBLOCK)
{
closesocket(sock);
sock = INVALID_SOCKET;
continue;
}
}
else
{
break;
}
fd_set writeSet; // write 사용가능여부 확인
FD_ZERO(&writeSet); // 초기화
FD_SET(sock, &writeSet); // writeSet 확인 대기
timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
// select를 이용하여 writeSet부분 이벤트 확인
int selectResult = select(0, nullptr, &writeSet, nullptr, &timeout);
// wirteSet 이벤트 변경이 있을경우
if (selectResult > 0 && FD_ISSET(sock, &writeSet))
{
int error;
int len = sizeof(error);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) == 0)
{
if (error == 0)
{
break;
}
else
{
closesocket(sock);
sock = INVALID_SOCKET;
}
}
}
else if (selectResult == 0)
{
throw std::runtime_error("connect timeout");
}
else
{
throw std::runtime_error("select failed with error : " + std::to_string(WSAGetLastError()));
}
}
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)
{
fd_set writeSet;
FD_ZERO(&writeSet);
FD_SET(sock, &writeSet);
timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int selectResult = select(0, nullptr, &writeSet, nullptr, &timeout);
if (selectResult > 0 && FD_ISSET(sock, &writeSet))
{
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()));
}
}
else if (selectResult == 0)
{
throw std::runtime_error("send timeout");
}
else
{
throw std::runtime_error("select failed with error : " + std::to_string(WSAGetLastError()));
}
}
std::string receive(size_t size)
{
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int selectResult = select(0, &readSet, nullptr, nullptr, &timeout);
if (selectResult > 0 && FD_ISSET(sock, &readSet))
{
std::string buffer(size, '\0');
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()));
}
}
else if (selectResult == 0)
{
return {};
}
else
{
throw std::runtime_error("select 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
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);
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;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
closesocket(serverSocket);
throw std::runtime_error("Socket bind failed : " + GetLastErrorAsString());
}
if (listen(serverSocket, 100) == SOCKET_ERROR)
{
closesocket(serverSocket);
throw std::runtime_error("Socket listen failed : " + GetLastErrorAsString());
}
u_long mode = 1;
// server accept() 비동기처리를 위한 serverSocket 설정
if (ioctlsocket(serverSocket, FIONBIO, &mode) != NO_ERROR)
{
closesocket(serverSocket);
throw std::runtime_error("ioctlsocket failed : " + GetLastErrorAsString());
}
std::cout << "Waiting for connection..." << std::endl;
}
void accept_connection()
{
while (true)
{
// select를 사용하여 accept처리 이벤트 확인
fd_set readSet; // read 이벤트 선언
FD_ZERO(&readSet); // 초기화
FD_SET(serverSocket, &readSet); // readSet 설정
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int selectResult = select(0, &readSet, nullptr, nullptr, &timeout);
if (selectResult > 0 && FD_ISSET(serverSocket, &readSet))
{
//accept이벤트 확인시 accept 진행
acceptSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize);
if (acceptSocket != INVALID_SOCKET)
{
std::cout << "Connection accepted " << std::endl;
u_long mode = 1;
if (ioctlsocket(acceptSocket, FIONBIO, &mode) != NO_ERROR)
{
closesocket(acceptSocket);
throw std::runtime_error("ioctlsocket failed : " + GetLastErrorAsString());
}
break;
}
else
{
int error = WSAGetLastError();
if (error != WSAEWOULDBLOCK)
{
throw std::runtime_error("Socket accept failed : " + GetLastErrorAsString());
}
}
}
else if (selectResult == 0)
{
continue;
}
else
{
throw std::runtime_error("select failed with error : " + std::to_string(WSAGetLastError()));
}
}
}
void socket_recv(const char* sendMessage)
{
char buffer[BUFFER_SIZE] = { 0, };
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(acceptSocket, &readSet);
timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
int selectResult = select(0, &readSet, nullptr, nullptr, &timeout);
if (selectResult > 0 && FD_ISSET(acceptSocket, &readSet))
{
int readValue = recv(acceptSocket, buffer, BUFFER_SIZE, 0);
if(readValue > 0)
{
std::cout << "Message from client : " << buffer << std::endl;
send(acceptSocket, sendMessage, strlen(sendMessage), 0);
std::cout << "(server-> client) send message" << std::endl;
}
else if (readValue == 0)
{
std::cout << "Connection closed" << std::endl;
}
else
{
std::cerr << "recv failed with error : " << GetLastErrorAsString() << std::endl;
}
}
else if (selectResult == 0)
{
throw std::runtime_error("recv timeout");
}
else
{
throw std::runtime_error("select failed with error : " + GetLastErrorAsString());
}
}
~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.accept_connection();
serverSocket.socket_recv(message);
}
catch (const std::exception& e)
{
std::cerr << "Exception : " << e.what() << std::endl;
}
return 0;
}