요즘에 왠지 모르겠는데.. 메시지 큐랑 IOCP에 꽂혔다. 너무 재밌어보여요!
그래서 C++을 활용해 소켓 통신으로 메세지 큐를 구현해보기로 했음.
그걸 하기 앞서 나는 C++도 싫어하고, 소켓 프로그래밍도 처음이라 소켓으로 간단하게 서버랑 클라이언트가 통신하는 것부터 구현해보겠습니다.
1. Windows 소켓 구조체 생성하기
// 1. Windows 소켓 구조체 생성
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "WSADATA create failed" << endl;
return 1;
}
WSADATA : 소켓 구조체를 생성하는 변수
WSAStartup : 생성한 객체를 초기화 해주는 역할
MAKEWORD(2,2) 매개 변수 : Winsock 2.2 버전을 호출하라는 뜻
WSAStartup이 성공하면 0, 실패하면 1을 반환하므로 이를 활용하여 if문으로 에러 검사까지 처리해줌
2. 리스닝 소켓 생성
// 2. 리스닝 소켓 생성
SOCKET listeningSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSock == INVALID_SOCKET)
{
cout << "listening socket create failed" << endl;
return 1;
}
네트워크 레벨 통신을 위해 필요한 정보들을 socket() 함수에 매개 변수로 전달해야한다.
순서대로 네트워크 주소체계(IPv4, IPv6), 소켓 타입(STREAM, DATAGRAM), 프로토콜(TCP, UDP)을 매개변수로 전달해야한다.
- AF_INET : IPv4
- SOCK_STREAM : TCP 전송방식
- IPPROTO_TCP : TCP 프로토콜
TCP 방식으로 통신하는 소켓을 만들었다!
소켓은 커널 오브젝트이기 때문에, 성공시 HANDLE 값이 나온다. 유효하지 않을 경우에는 INVALID_SOCKET을 반환함.
유효하지 않을 경우에 대비해 에러 처리해줌.
잠깐! 커널 오브젝트가 뭐에요?
커널 오브젝트란 운영체제(OS)가 직접 관리하는 리소스임
유저 프로그램이 마음대로 만질 수 없음 → 커널 오브젝트 형태로 핸들(handle)을 제공해 주는 방식으로 접근
핸들(handle) : 실제 커널 오브젝트를 가리키는 간접참조 키
커널 오브젝트의 예시 :
- 스레드 생성
- 메모리 관리
- 파일 접근
- 동기화
- 프로세스 관리
- I/O 처리
3. IP 주소, PORT 번호 바인딩
// 3. IP 주소, PORT 번호 바인딩
sockaddr_in addrServer{};
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(4882);
addrServer.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listeningSock, (SOCKADDR *)&addrServer, sizeof(addrServer));
먼저 서버가 사용할 네트워크 주소를 OS에 등록하는 과정이 필요하다.
이를 위해 sockaddr로 소켓 서버의 주소를 가지는 객체를 생성해준다.
- AF_INET : IPv4
- htons(4882) : 포트 번호
- htonl(INADDR_ANY) : 랜카드의 IP주소 중 현재 사용 가능한 IP주소를 아무거나 선택하겠다
bind()함수로 위에서 생성했던 listening socket에 구조체를 바인딩해준다.
4. 클라이언트 요청 리스닝
listen(listeningSock, SOMAXCONN); // 4. 클라이언트 요청 리스닝
listen()함수로 바인딩한 서버의 주소로 클라이언트로부터 요청이 들어오는 것을 대기함
5. 데이터 수신
// 5. 데이터 수신
char buffer[1024] = {0};
int recievedMessage = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
if (recievedMessage > 0)
{
buffer[recievedMessage] = '\0'; // 문자열 끝 처리
cout << "recieved message from client: " << buffer << endl;
}
buffer를 통해 recv()함수로 클라이언트 메시지를 받아서 출력함
6. 소켓 종료
// 7. 소켓 종료
closesocket(client_socket);
closesocket(listeningSock);
WSACleanup();
return 0;
서버 전체 코드
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
// 1. Windows 소켓 구조체 생성
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "WSADATA create failed" << endl;
return 1;
}
// 2. 리스닝 소켓 생성
SOCKET listeningSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSock == INVALID_SOCKET)
{
cout << "listening socket create failed" << endl;
return 1;
}
// 3. IP 주소, PORT 번호 바인딩
sockaddr_in addrServer{};
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(4882); // port 번호
addrServer.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listeningSock, (SOCKADDR *)&addrServer, sizeof(addrServer));
listen(listeningSock, SOMAXCONN); // 4. 클라이언트 요청 리스닝
// 5. 클라이언트 연결 수락
sockaddr_in client = {};
int client_size = sizeof(client);
SOCKET client_socket;
client_socket = accept(listeningSock, (SOCKADDR *)&client, &client_size);
if (client_socket == INVALID_SOCKET)
{
cout << "client connection failed" << endl;
}
// 6. 데이터 수신
char buffer[1024] = {0};
int recievedMessage = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
if (recievedMessage > 0)
{
buffer[recievedMessage] = '\0'; // 문자열 끝 처리
cout << "recieved message from client: " << buffer << endl;
}
// 7. 소켓 종료
closesocket(client_socket);
closesocket(listeningSock);
WSACleanup();
return 0;
}