해당 문서에서는 C++에서 winsock2 라이브러리를 통해 Server/Client 간 소켓 통신을 구현하는 방법에 대해 설명한다.
해당 문서는 Client에 대한 문서이며 Server에 대한 내용은 이전 문서를 참조하자.
#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() 함수를 통해 설정된 리소스를 해제할 수 있도록 해주었다.
#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;
}