๐๐ป c++์ Asio(๋๋ Bootst.asio) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ดํดํ๊ธฐ ์ํ ์ฝ๋์
๋๋ค.
This is code to understand the C++ Asio (or Bootst.asio) library.
๐๐ป ์๋์ ์ฝ๋๋ ์ด์ ์ ์
๋ก๋ํ mini_mediaserver_1 ์ ์ผ๋ถ๋ก์ง์ ์ฌ์ฉํฉ๋๋ค.
The code below uses some of the logic from the previously uploaded mini_mediaserver_1.
๐๐ป ํ
์คํธ ํ๊ฒฝ์ MacOS์
๋๋ค.
The test environment is MacOS.
๐๐ป mini_mediaserver_1์ standalone๋ฐฉ์์ asio๋ฅผ ์ฌ์ฉํฉ๋๋ค.
mini_mediaserver_1 uses standalone asio.
๐๐ป ์ฌ๊ธฐ์๋ Boosta.asio๋ฅผ์ฌ์ฉํฉ๋๋ค.
Here, we use Boosta.asio.
๐๐ป ์ค์น ๋ฐ ์ฌ์ฉ / Install and Usage
โ๏ธ Boost.Asio ์ ์ฒด ์ค์น / full installation
— ์ค์น / Installatioon
# macOS brew - boost.asio
brew install boost
— ์ฌ์ฉ / Usage
#include "boost/asio"
โ๏ธ Asio Standalone
— ์ค์น / Installation
# macOS brew - asio standalone
brew install asio
— ์ฌ์ฉ / Usage
#include "asio.hpp"
๐๐ป ์ฝ๋ / Code
โ๏ธ์ฝ๋ ์ค๋ช / Code Description
— ํจ์ ์คํ ํ๋ฆ / Function execution flow
main() -> Server Class- > do_accept() -> Session Class -> start { do_read {do_write()}}
— ์์ผ,์ ์์ ๋ณด ๊ณต์ / Socket, connection information sharing
1.์์ผ์ ๋ณต์ฌ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์์ ๊ถ ์ด๋์ ํฉ๋๋ค.
Since sockets cannot be copied, ownership is transferred.
std::make_shared<Session>(std::move(socket))->start();
2.์ ์ ์ ๋ณด๋ฅผ ๊ณต์ ํ๊ธฐ ์ํด ๋ค์ ์ฝ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค.
Use the following code to share connection information.
- make_shared๋ ํ๋๋ง ๊ณต์ ํ ์ ์๋ ํฌ์ธํฐ๋ฅผ ๋ง๋ญ๋๋ค.
make_shared creates a pointer that can be shared by only one person.
std::make_shared<Session>(std::move(socket))->start();
- enable_shared_from_this๋ฅผ ์์๋ฐ์ผ๋ฉด shared_from_this(); ํจ์๋ฅผ ์ฌ์ฉ ํ ์ ์์ต๋๋ค.
If you inherit from enable_shared_from_this, you can use the shared_from_this(); function.
public std::enable_shared_from_this<Session>
- this(Session class) ์ ๋ณด๊ฐ ์ฌ๋ผ์ง ์ ์์ด์ ์์ ํ ์ฐ๊ฒฐ์ ์ํด self์ Session Class ์ ๋ณด๋ฅผ ๋ณตํํฉ๋๋ค.
Since the information in this (Session class) can be lost, the Session Class information is copied to self for a secure connection.
auto self = shared_from_this();
— echo
- ์๋์ ๋๋ค ์ฝ๋์์ ์์ผ์์ ๋ฐ์ data_์ ๋ด์ฉ์ ์ถ๋ ฅํ๊ณ ๋ค์ ์์ผ์ ๋ฐ์ดํฐ๋ฅผ ์์ฑํฉ๋๋ค.
In the lambda code below, the contents of data_ received from the socket are printed, and data is written back to the socket.
[this, self](boost::system::error_code ec, size_t len) {
if (!ec) {
std::cout << "๋ฐ์/Receive: " << std::string(data_, len) << "\n";
do_write(len); // ์์ฝ / echo
}
});
โ๏ธ์ ์ฒด ์ฝ๋ / Full Cdoe (tcpecho.cpp)
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
// --
/*
- std::make_shared<Session>(std::move(socket)) :
ํ ๊ณณ๋ง ํฌ์ธํฐ ๊ณต์ ์ค์ / Pointer sharing settings for only one location
- : public std::enable_shared_from_this<Session> :
shared_from_this(); ์ฌ์ฉ์ ์ํด ์์
Inheritance for using shared_from_this();
- shared_from_this :
class Session ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํด์ ์ ์ฅ
Copy and save the class Session object
** this๊ฐ ์๋ฉธ๋ ์ ์์ด์ ์์ ํ ์ธ์
์ฐ๊ฒฐ ์ ์ง๋ฅผ ์ํด Sessionํด๋์ค๋ฅผ ํ๋ ๋ ๋ณต์ฌํจ
Since 'this' can be destroyed,
an additional copy of the Session class is made to maintain a safe session connection. **
*/
class Session : public std::enable_shared_from_this<Session> {
tcp::socket socket_;
char data_[1024];
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
do_read();
}
private:
void do_read() {
// ๋๋ค ์ด์์๋ ๋์ Session ์ ์ฃฝ์ / The Session does not die as long as Lambda is alive
// ์ด ํด๋์ค ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํด์ ์ ์ฅ / Copy and save this class object
auto self = shared_from_this();
/*
* async_read_some(buffer(data_), ๋๋ค/rambda)
* 1) ๋ฐ์ดํฐ ์ค๋ฉด data_์ ์ฑ์๋๊ณ ๋๋ค ์คํ ์์ฝ๋ง ํ๋ค.
* When data arrives, fill it into data_ and just schedule the Lambda execution.
*
* 2) OS๊ฐ ์์ผ์ ๋ฐ์ดํฐ ๋ค์ด์ค๋ ๊ฑฐ ๊ฐ์ง โ data_ ๋ฐฐ์ด์ len๋ฐ์ดํธ๋งํผ ๋ณต์ฌํด ๋๋ค.
* The OS detects incoming data in the socket and copies len bytes into the data_ array.
*
* 3) ๋ณต์ฌ๊ฐ ๋๋ฌ์ผ๋ฉด ๋๋ค ์คํ
* Run Lambda once copying is finished
*
* 4) ์ด๋ฏธ data_์ ๋ค์ด์ ์๋ ๋ณ์๋ฅผ ๋๋ค์์ ์ฌ์ฉ
* Using variables already in data_ in Lambda
*/
socket_.async_read_some(boost::asio::buffer(data_),
[this, self](boost::system::error_code ec, size_t len) {
if (!ec) {
std::cout << "๋ฐ์/Receive: " << std::string(data_, len) << "\n";
do_write(len); // ์์ฝ / echo
}
});
}
void do_write(size_t len) {
auto self = shared_from_this();
boost::asio::async_write(socket_, boost::asio::buffer(data_, len),
[this, self](boost::system::error_code ec, size_t) {
if (!ec) do_read(); // ์ฝ๊ธฐ / read
});
}
};
// --
class Server {
// socket() + bind() + listen()์ํ / Execute socket() + bind() + listen()
tcp::acceptor acceptor_; // private
public:
Server(boost::asio::io_context& io, short port)
: acceptor_(io, tcp::endpoint(tcp::v4(), port)) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
// ๋๋คํจ์ / Lambda function
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
// ์๋ฌ๊ฐ ์์ผ๋ฉด ์์ผ์ ์ธ์
์ ๋๊ธฐ๊ณ start ํธ์ถ, ์์ผ์ ๋ณต์ฌ ์๋จ
// If there are no errors, pass the socket to the session and call start;
// the socket is not copied
std::make_shared<Session>(std::move(socket))->start();
}
// ๋ค์ ํด๋ผ์ด์ธํธ ๊ณ์ ๋ฐ์
// Continue accepting next client
//
// ๋ฌดํ๋ฃจํ / infinite loop
do_accept();
});
}
};
int main() {
try {
boost::asio::io_context io;
Server s(io, 12345);
std::cout << "์๋ฒ ์์. ํฌํธ 12345 / Start server. Port 12345\n";
io.run();
} catch (std::exception& e) {
std::cerr << "์๋ฌ/Error: " << e.what() << "\n";
}
return 0;
}
๐๐ป์ปดํ์ผ / Compiling
— boot๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฒฝ๋ก๋ฅผ ํฌํจํฉ๋๋ค.
Includes the path to the boot library.
g++ tcpecho.cpp -o server -std=c++17 -pthread \
-I/opt/homebrew/include \
-L/opt/homebrew/lib \
-lboost_system
๐๐ป์คํ / Run
โ๏ธ ํฐ๋ฏธ๋์ ๋๊ฐ ์ด์ด์ ํ๋๋ ์๋ฒ ํ๋๋ ํด๋ผ์ด์ธํธ๋ฅผ ์คํํฉ๋๋ค.
Open two terminals and run the server and the client in one.
โ๏ธnc(netcat)๋ TCP/UDP ํต์ ํ
์คํธ ๋๊ตฌ์
๋๋ค.
nc(netcat) is a TCP/UDP communication test tool.
โ๏ธ ํด๋ผ์ด์ธํธ๋ nc localhost 12345๋ฅผ ์คํํฉ๋๋ค.
The client runs nc localhost 12345.
# server
./server
# client
nc localhost 12345
๐๐ป์คํ ๊ฒฐ๊ณผ / Execution result
