소켓 프로그래밍 소개
소켓 프로그래밍은 네트워크를 통해 통신하는 소프트웨어를 작성하는 기술입니다. C++에서는 Berkeley 소켓 API를 사용하여 소켓 프로그래밍을 구현할 수 있습니다. 오늘은 TCP/IP 소켓을 사용하여 간단한 클라이언트-서버 애플리케이션을 구현해보겠습니다.
TCP 서버 구현
1. 서버 소켓 설정
TCP 서버는 클라이언트 연결을 수락하고 데이터를 주고받습니다. 서버는 다음 단계를 통해 구현됩니다.
- 소켓 생성
- 소켓 바인딩
- 소켓 리스닝
- 클라이언트 연결 수락
- 데이터 송수신
tcp_server.cpp
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
// 1. 소켓 생성
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 소켓 바인딩
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 3. 소켓 리스닝
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
std::cout << "Server listening on port " << PORT << std::endl;
// 4. 클라이언트 연결 수락
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 5. 데이터 송수신
int valread = read(new_socket, buffer, 1024);
std::cout << "Received: " << buffer << std::endl;
const char* hello = "Hello from server";
send(new_socket, hello, strlen(hello), 0);
std::cout << "Hello message sent" << std::endl;
close(new_socket);
close(server_fd);
return 0;
}
이 코드는 간단한 TCP 서버를 구현합니다. 클라이언트의 연결을 수락하고 메시지를 수신한 후 응답을 전송합니다.
TCP 클라이언트 구현
TCP 클라이언트는 서버에 연결하고 데이터를 주고받습니다.
tcp_client.cpp
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8080
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[1024] = {0};
// 1. 소켓 생성
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "Socket creation error" << std::endl;
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 2. 서버 주소 설정
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
std::cerr << "Invalid address/ Address not supported" << std::endl;
return -1;
}
// 3. 서버에 연결
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "Connection Failed" << std::endl;
return -1;
}
const char* hello = "Hello from client";
send(sock, hello, strlen(hello), 0);
std::cout << "Hello message sent" << std::endl;
int valread = read(sock, buffer, 1024);
std::cout << "Received: " << buffer << std::endl;
close(sock);
return 0;
}
이 코드는 간단한 TCP 클라이언트를 구현합니다. 서버에 연결하여 메시지를 전송하고 응답을 수신합니다.
실습 문제
문제 1: 멀티클라이언트 서버 구현
여러 클라이언트의 연결을 동시에 처리할 수 있는 멀티클라이언트 서버를 구현하세요.
해설:
멀티클라이언트 서버는 여러 클라이언트의 요청을 동시에 처리하기 위해 멀티스레딩을 사용할 수 있습니다. 다음 코드는 멀티스레딩을 사용하여 여러 클라이언트를 처리하는 서버를 구현합니다.
tcp_multiclient_server.cpp
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>
#define PORT 8080
void handle_client(int new_socket) {
char buffer[1024] = {0};
int valread = read(new_socket, buffer, 1024);
std::cout << "Received: " << buffer << std::endl;
const char* hello = "Hello from server";
send(new_socket, hello, strlen(hello), 0);
std::cout << "Hello message sent" << std::endl;
close(new_socket);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
std::vector<std::thread> threads;
// 1. 소켓 생성
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 소켓 바인딩
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 3. 소켓 리스닝
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
std::cout << "Server listening on port " << PORT << std::endl;
while (true) {
// 4. 클라이언트 연결 수락
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 5. 새로운 스레드에서 클라이언트 처리
threads.emplace_back(handle_client, new_socket);
}
// 모든 스레드 종료 대기
for (auto& th : threads) {
if (th.joinable()) {
th.join();
}
}
close(server_fd);
return 0;
}
이 코드는 클라이언트의 연결을 수락하고, 각각의 클라이언트를 처리하기 위해 새로운 스레드를 생성합니다. 여러 클라이언트를 동시에 처리할 수 있습니다.
TCP 서버와 클라이언트 빌드 및 실행
1. 서버 빌드 및 실행
g++ tcp_multiclient_server.cpp -o server -lpthread
./server
2. 클라이언트 빌드 및 실행
g++ tcp_client.cpp -o client
./client
여러 터미널에서 클라이언트를 실행하여 멀티클라이언트 서버의 동작을 확인할 수 있습니다.
이제 26일차의 학습을 마쳤습니다. C++로 소켓 프로그래밍을 사용하여 TCP 서버와 클라이언트를 구현하고, 멀티클라이언트를 처리하는 방법을 학습하였습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "C++에서의 데이터베이스 연동 (SQLite, MySQL)"에 대해 학습하겠습니다.
'-----ETC----- > C++ 고급 프로그래밍과 응용 프로젝트 시리즈' 카테고리의 다른 글
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 30: 오픈 소스 프로젝트 참여 방법 및 기여 (0) | 2024.08.01 |
---|---|
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 28: C++로 게임 개발 (SFML, SDL) (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 27: C++에서의 데이터베이스 연동 (SQLite, MySQL) (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 24: gRPC를 이용한 분산 시스템 (0) | 2024.08.01 |
[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 25: CMake를 이용한 빌드 시스템 관리 (0) | 2024.08.01 |