簡介
這篇筆記是boost::asio的概覽, 主要說明了
boost
的進行CS結構程式設計的基本步驟. 在網絡程式設計中, 又很多需要IO的操作. 一種是使用Linux的原生C語言API, Linux的核心程式設計思想是操作檔案描述符, 所有的操作都是基于檔案描述符. 而在boost中, 增添了更多功能的操作, 而且更加增強了關于異步的操作. 這篇筆記主要記錄了使用boost::asio庫進行的操作.
用戶端
用戶端主要的操作步驟是:
- 建立
, 這是程式和核心的IO連接配接, 需要使用io_service
來建立. 本例子中, 使用:boost::asio::io_context
- 需要一個解析器解析伺服器的資訊, 比如IP和端口,
進行解析, 在本例子中:boost::asio::ip::tcp::resolver
- 需要一個終端抽象, 來存儲解析器解析的資訊, 在本例子中:
boost::asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(argv[1], "6768"); // ip和6768端口
- 之後, 需要一個
建立連接配接, 使用:socket
- 建立與伺服器的連結, 是
與socket
的連接配接:endpoints
- 最後是通過有關的讀寫函數進行操作, 具體參考代碼
代碼執行個體, 參考自: https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/tutorial/tutdaytime1/src.html
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main(int argc, char* argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
boost::asio::io_context io_context; // 與核心連接配接的上下文
tcp::resolver resolver(io_context); // 解析器, 需要一個上下文
// tcp::resolver::results_type endpoints =
// resolver.resolve(argv[1], "6768");
// 解析存儲的資訊, 建議使用auto自動推導, 上下兩個等價
auto endpoints = resolver.resolve(argv[1], "6768");
tcp::socket socket(io_context); // 與上下文相關的套接字
boost::asio::connect(socket, endpoints); // 建立連接配接
for (;;) {
boost::array<char, 128> buf;
boost::system::error_code error;
// 資料的緩沖區, 參考: https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/buffer.html
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof) {
std::cout << "closed by server\n";
break; // 伺服器主動關閉連接配接.
}
else if (error) {
throw boost::system::system_error(error); // 其他的錯誤.
std::cout << "error: ";
}
std::cout.write(buf.data(), len); // 控制台輸出
}
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
同步伺服器
服務程式設計的主要步驟是:
- 建立上下文與核心進行連接配接, 與用戶端一樣:
- 需要有一個接收器, 用于在指定端口接收資料, 并通過指定的套接字進行讀寫:
using boost::asio::ip::tcp; // ipv4位址, 并監聽6768端口 tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 6768));
- 建立套接字, 用于資料傳輸
tcp::socket socket(io_context); acceptor.accept(socket);
- 通過有關函數, 寫入或者讀取資料
代碼執行個體, 參考: https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/tutorial/tutdaytime2/src.html
#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string() {
using namespace std;
time_t now = time(0);
return ctime(&now);
}
int main() {
try {
boost::asio::io_context io_context;
// 接收器, 初始化指定上下文 ip和端口
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 6768));
for (;;) {
tcp::socket socket(io_context); // 建立socket
acceptor.accept(socket); // 用于讀寫資料的socket
std::string message = make_daytime_string();
boost::system::error_code ignored_error;
// 向socket寫入資料
boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
}
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
異步伺服器
異步伺服器的作用在于, 讀寫操作會立刻傳回, 而不是在函數的調用時阻塞.
#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string() {
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection : public boost::enable_shared_from_this<tcp_connection> {
public:
using pointer = boost::shared_ptr<tcp_connection>;
static pointer create(boost::asio::io_context &io_context) {
return pointer(new tcp_connection(io_context));
}
tcp::socket &socket() {
return socket_;
}
void start() {
message_ = make_daytime_string();
// 這裡, 函數立刻傳回, 不會發生阻塞.
boost::asio::async_write(socket_, boost::asio::buffer(message_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
explicit tcp_connection(boost::asio::io_context &io_context)
: socket_(io_context) {
}
void handle_write(const boost::system::error_code &, size_t) {
// 該函數一般是使用者自己實作, 在異步寫完後, 會調用該函數
}
tcp::socket socket_;
std::string message_;
};
class tcp_server {
public:
explicit tcp_server(boost::asio::io_context &io_context)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), 6768)) {
start_accept();
}
private:
void start_accept() {
auto new_connection =
tcp_connection::create(acceptor_.get_executor().context());
// 該函數調用後, 立刻傳回, 建立連接配接完成後, 會調用handle_accept函數
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code &error) {
if (!error) {
new_connection->start();
}
start_accept();
}
tcp::acceptor acceptor_;
};
int main() {
try {
boost::asio::io_context io_context;
tcp_server server(io_context);
io_context.run();
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
}
return 0;
}