서론
https://dongyeop00.tistory.com/132
C++에서 지원하는 소켓 함수와 server와 client간의 통신을 알아보았으니 데이터를 주고 받는 프로그램을 만들어 보자.
먼저 서버부터 구현해보자
WSADATA
WSADATA는 Windows Sockets API(WinSock)에서 사용하는 데이터 구조체로, Windows 소켓 프로그래밍을 시작하기 전에 Winsock 라이브러리를 초기화하는 데 사용된다.
WSAStartup 함수가 성공적으로 호출되면 WSADATA 구조체가 초기화 되고, 이 구조체는 Winsock의 버전과 관련된 정보를 담고 있다.
즉, WSADATA는 소켓 프로그래밍을 시작하기 위한 초기 설정을 저장하는 역할을 한다.
#pragma comment(lib, "ws2_32.lib")
- ws2_32.lib은 Widows Sockets 2.0 라이브러리이다.
- TCP/IP 네트워크 통신을 가능하게 해주는 API를 포함하고 있으며, 소켓을 사용한 네트워크 프로그래밍할 때 필수적으로 포함되어 있어야 한다.
Server
#include <iostream>
#include <cstring>
#include <WinSock2.h>
using namespace std;
#define serverPort 8080
#define charMax 256
#pragma comment(lib, "ws2_32.lib")
void errorMessage(string message) {
cout << "[오류발생]: " << message << "\n";
system("pause");
exit(1);
}
void currentMessage(string message) {
cout << "[현재상태]: " << message << "\n";
}
int main() {
WSADATA wsaData;
SOCKET serverSocket;
SOCKET clientSocket;
SOCKADDR_IN serverAddress;
SOCKADDR_IN clientAddress;
char received[charMax];
//Winsock 초기화
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
errorMessage("WSAStartup");
}
//TCP 소켓 생성
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
errorMessage("socket()");
}
currentMessage("socket()");
memset(&serverAddress, 0, sizeof(serverAddress));
//서버 주소 구조체 설정
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(serverPort); // 2바이트 정수 네트워크 바이트 형식으로
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); //4바이트 정수 네트워크 바이트 형식으로
//bind 함수, 소켓에 로컬 주소와 포트를 할당
if (bind(serverSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) {
errorMessage("bind()");
}
currentMessage("bind()");
//listen 함수, 클라이언트로부터 연결 요청을 기다린다.
if (listen(serverSocket, 5) == SOCKET_ERROR) {
errorMessage("listen()");
}
currentMessage("listen()");
while (1) {
int sizeClientAddress = sizeof(clientAddress);
// accept 함수, 수신 대기 중인 연결 요청 수락
clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddress, &sizeClientAddress);
if (clientSocket == INVALID_SOCKET) {
errorMessage("accept()");
}
currentMessage("accept()");
while (1) {
int length = recv(clientSocket, received, sizeof(received) - 1, 0);
if (length <= 0) {
currentMessage("클라이언트 연결 종료");
break;
}
received[length] = '\0';
cout << "[Client Message]: " << received << '\n';
if (strcmp(received, "exit") == 0) {
send(clientSocket, received, sizeof(received), 0);
cout << "[----------------서버 종료----------------]\n";
break;
}
send(clientSocket, received, sizeof(received), 0);
}
closesocket(clientSocket);
}
//closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
system("pause");
return 0;
}
1. Winsock 초기화
- 사용 함수: WSAStartup
- 초기화가 성공적으로 완료되면 WSADATA 구조체가 채워지며, 소켓 통신을 시작할 준비가 된다.
WSADATA wsadata;
//Winsock 초기화
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
errorMessage("WSAStartup");
}
2. TCP 소켓 생성
- 사용함수: SOCKET
- 서버 소켓은 AF_INET(IPv4) 주소 체계와 SOCK_STREAM(TCP) 소켓 타입을 지정
SOCKET serverSocket;
//TCP 소켓 생성
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
errorMessage("socket()");
}
currentMessage("socket()");
3. 서버 주소 설정
- 서버는 SOCKADDR_IN 구조체를 사용해 자신의 IP 주소와 포트를 설정한다.
- 이 정보를 바탕으로 클라이언트가 서버에 연결할 수 있다.
SOCKADDR_IN serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
//서버 주소 구조체 설정
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(serverPort); // 2바이트 정수 네트워크 바이트 형식으로
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); //4바이트 정수 네트워크 바이트 형식으로
4. 소켓 바인딩
- 사용함수: bind
- 서버는 생성된 소켓을 특정 IP 주소와 포트에 바인딩한다.
- 이를 통해 클라이언트가 해당 IP와 포트를 통해 서버에 접속할 수 있게 된다.
//bind 함수, 소켓에 로컬 주소와 포트를 할당
if (bind(serverSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) {
errorMessage("bind()");
}
currentMessage("bind()");
5. 연결 요청 대기
- 사용함수: listen
- 대기열의 크기를 설정하며, 이 크기만큼의 클라이언트 요청을 동시에 대기시킬 수 있다.
//listen 함수, 클라이언트로부터 연결 요청을 기다린다.
if (listen(serverSocket, 5) == SOCKET_ERROR) {
errorMessage("listen()");
}
currentMessage("listen()");
6. 연결 수락
- 사용함수: accept
- 클라이언트가 서버에 요청을 보내면 서버는 accept 함수를 통해 이 요청을 수락한다.
- accept 함수가 호출되면 새로운 소켓이 생성되어 클라이언트와 연결이 이루어진다.
//accept 함수, 수신 대기 중인 연결 요청을 수락한다.
clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddress, &sizeClientAddress);
if (clientSocket == INVALID_SOCKET) {
errorMessage("accept()");
}
currentMessage("accept()");
7. 데이터 송수신
- 사용함수: send, recv
- 서버와 클라이언트는 send와 recv 함수를 사용해 데이터를 주고 받는다.
while (1) {
int length = recv(clientSocket, received, sizeof(received) - 1, 0);
if (length <= 0) {
currentMessage("클라이언트 연결 종료");
break;
}
received[length] = '\0';
cout << "[Client Message]: " << received << '\n';
if (strcmp(received, "exit") == 0) {
send(clientSocket, received, sizeof(received), 0);
cout << "[----------------서버 종료----------------]\n";
break;
}
send(clientSocket, received, sizeof(received), 0);
}
결과
'🔊 Language > C++' 카테고리의 다른 글
C++ - 소켓 프로그래밍 함수 (0) | 2024.08.14 |
---|---|
C++ - TCP 소켓 통신을 사용해보자 (0) | 2024.08.14 |
C++ - Map (0) | 2024.08.01 |
C++ - STL 컨테이너 (0) | 2024.07.31 |
C++ - Stack (0) | 2024.07.29 |