C++ winsock2를 이용한 소켓 통신 구현 (Client)

Brie·2023년 11월 6일
0

C++

목록 보기
3/8

개요

해당 문서에서는 C++에서 winsock2 라이브러리를 통해 Server/Client 간 소켓 통신을 구현하는 방법에 대해 설명한다.
해당 문서는 Client에 대한 문서이며 Server에 대한 내용은 이전 문서를 참조하자.

ChatTest_client.cpp (Client)

#define _WINSOCK_DEPRECATED_NO_WARNINGS // winsock c4996 처리

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#pragma comment(lib,"ws2_32.lib") // ws2_32.lib 라이브러리를 링크
#define PACKET_SIZE 1024 // 송수신 버퍼 사이즈 1024로 설정
SOCKET server_socket;99999999999999

int main() {
	WSADATA wsa; // Windows 소켓 구현에 대한 구조체 생성

	// WSAStartup을 통해 wsadata 초기화
	// MAKEWORD(2,2) 매개 변수는 시스템에서 Winsock 버전 2.2를 요청
	// WSAStartup은 성공시 0, 실패시 SOCKET_ERROR를 리턴하므로
	// 리턴값이 0인지 검사하여 에러 여부 확인
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
		std::cout << "WSAStartup failed" << endl;
		return 1;
	}

	……

	WSACleanup();
	return 0;
}

include, define, namespace 설정은 Server의 코드와 동일하다.

데이터를 수신하거나 송신할 버퍼 사이즈 값을 1024로 define하고, 접속할 서버 소켓(server_socket)을 생성하였다.

main() 함수 구문에 있어서도 WSADATA 구조체를 생성하고 초기화하는 부분과 마지막에 리소스를 해제하는 부분은 동일하다.

	// 클라이언트에서 접속할 서버 소켓 생성
	server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	// 주소 패밀리, IP 주소 및 포트 번호에 대한 정보를 보유할 sockaddr 구조체 생성
	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	// 서버의 IP 설정. 서버가 클라이언트와 동일한 IP에서 실행되므로 여기서는 loopback 설정

서버 소켓을 생성하고 초기화하는 코드도 Server의 구문과 크게 다르지 않다.

다만 다른 점은 client의 코드에서는 inet_addr에 서버의 IP Address를 입력해주어야 한다는 점이다.

현재 개발 환경에서는 Server와 Client를 동일한 PC에서 실행하여 테스트할 예정이므로, Server의 IP를 127.0.0.1(Loopback Address)로 설정했다.

while (1) { // 서버 소켓에 대해 반복 연결 시도, 연결 성공 시 break
	if (!connect(server_socket, (SOCKADDR*)&addr, sizeof(addr))) break;
}

connect() 함수는 연결이 필요한 소켓, 연결을 설정해야하는 sockaddr 구조체, 해당 sockaddr 구조체의 길이(바이트)를 매개 변수로 전달하여 지정된 소켓에 대한 연결을 설정하는 함수이다.

클라이언트에서 생성된 Server 소켓 및 sockaddr 구조를 매개 변수로 connect() 함수에 전달하여 연결을 시도한다.

void proc_recv() {
	char buffer[PACKET_SIZE] = {}; //char 생성
	while (!WSAGetLastError()) {
		ZeroMemory(&buffer, PACKET_SIZE); //buffer 비우기
		recv(server_socket, buffer, PACKET_SIZE, 0); //데이터받아오기
		cout << "받은 메세지: " << buffer << endl;
	}
}

int main() {
	……

	// server로부터 메세지를 수신하는 함수를 thread에 등록
	thread proc1(proc_recv);

	char msg[PACKET_SIZE] = { 0 }; // 메세지 입력받을 버퍼 생성
	while (!WSAGetLastError()) {
		ZeroMemory(&msg, PACKET_SIZE); // 버퍼 초기화하고, 입력받은 메세지를 서버에게 send
		cin >> msg;
		send(server_socket, msg, strlen(msg), 0);
	}
	proc1.join(); // 실행중인 thread의 작업 완료될때까지 대기

	……
}

서버로부터 데이터를 수신할 수 있도록 proc_recvs()라는 함수를 작성하였다. 메세지가 담길 버퍼를 생성하고, recv()로 해당 버퍼에 데이터를 반환받아 출력해준다.

그리고 해당 함수를 thread를 통해 실행하여 main()의 서버에게 메세지를 전송하는 구문과 동기적으로 작업이 이루어 질 수 있도록 구성하였다.

int main() {
	……

	closesocket(server_socket);

	WSACleanup();
	return 0;
}

소스의 마지막 부분에는 closesocket() 함수를 통해 각각 서버의 소켓을 닫고, WSACleanup() 함수를 통해 설정된 리소스를 해제할 수 있도록 해주었다.

Full Code

#define _WINSOCK_DEPRECATED_NO_WARNINGS // winsock c4996 처리

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#pragma comment(lib,"ws2_32.lib") // ws2_32.lib 라이브러리를 링크
#define PACKET_SIZE 1024 // 송수신 버퍼 사이즈 1024로 설정
SOCKET server_socket;

void proc_recv() {
	char buffer[PACKET_SIZE] = {}; //char 생성
	while (!WSAGetLastError()) {
		ZeroMemory(&buffer, PACKET_SIZE); //buffer 비우기
		recv(server_socket, buffer, PACKET_SIZE, 0); //데이터받아오기
		cout << "받은 메세지: " << buffer << endl;
	}
}

int main() {
	WSADATA wsa; // Windows 소켓 구현에 대한 구조체 생성

	// WSAStartup을 통해 wsadata 초기화
	// MAKEWORD(2,2) 매개 변수는 시스템에서 Winsock 버전 2.2를 요청
	// WSAStartup은 성공시 0, 실패시 SOCKET_ERROR를 리턴하므로
	// 리턴값이 0인지 검사하여 에러 여부 확인
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
		std::cout << "WSAStartup failed" << endl;
		return 1;
	}

	// 클라이언트에서 접속할 서버 소켓 생성
	server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	// 주소 패밀리, IP 주소 및 포트 번호에 대한 정보를 보유할 sockaddr 구조체 생성
	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	// 서버의 IP 설정. 서버가 클라이언트와 동일한 IP에서 실행되므로 여기서는 loopback 설정

	while (1) { // 서버 소켓에 대해 반복 연결 시도, 연결 성공 시 break
		if (!connect(server_socket, (SOCKADDR*)&addr, sizeof(addr))) break;
	}

	// server로부터 메세지를 수신하는 함수를 thread에 등록
	thread proc1(proc_recv);

	char msg[PACKET_SIZE] = { 0 }; // 메세지 입력받을 버퍼 생성
	while (!WSAGetLastError()) {
		ZeroMemory(&msg, PACKET_SIZE); // 버퍼 초기화하고, 입력받은 메세지를 서버에게 send
		cin >> msg;
		send(server_socket, msg, strlen(msg), 0);
	}

	proc1.join(); // 실행중인 thread의 작업 완료될때까지 대기

	closesocket(server_socket); // client socket 닫기

	WSACleanup(); // 리소스 해제
	return 0;
}

0개의 댓글