Boost 准标准库中的网络库 asio
这篇文章带给读者什么东西?
这篇文章对Boost.asio进行介绍,且涵盖网络编程知识,以及asio 网络库的使用与注意事项。
本篇文章涉及到的源码地址
讲解源码,不要忘了给个小星星✨哦,Thank!😜:源码
查看BoostAsioStudy即可,如下图:
Boost.asio介绍
Boost.Asio是用于网络和底层I / O编程的跨平台C ++库,它使用现代C ++方法为开发人员提供一致的异步模型。
基于 Proactor模式的事件驱动模型。
同步编程与异步编程 (线程)
请参考博主的博客: 深入理解同步编程与异步编程
Clion配置Boost “准”标准库
关于C++IDE JetBrains Clion可以参照我的这篇博客:
1. 下载Boost库
2. 在Clion中项目的 CMakeList 中添加引用
- 打开解压后的Boost文件:
- 在Clion 利用 include_directories( 头文件引用目录的绝对路径 )
- 在Clion利用 link_dorectories( 库文件引用目录的绝对路径 )
注意: 反斜杠都需要是两个,因为反斜是转义符。
之后Reload 整个 CMakeList ,这时Clion就会去集成 Boost 库到项目中。
3. 在使用时添加动态链接库
在使用其Boost库做一定的功能时,引用了其Boost 库中某个库中的功能,注意在CMakeList中添加其,链接到其需要的动态库。
注意其添加的位置:在 add_executable 之前。
link_libraries( 动态库)
asio 以同步阻塞 I/O模型实现 (时间 Client / Server)
时间C/S: 演示客户端连接到服务器,服务器将当前时间发送给客户端,客户端接收并打印
client:
int SyncTcpDaytimeClient() {
try {
boost::asio::io_service io;
using boost::asio::ip::tcp;
tcp::resolver resolver(io); //解析
//将主机号解析为ip地址,daytime解析为其上的应用端口,返回其搜集到的所有集合,返回类型其实是vector,数据类型是<ip, endpoint>
boost::system::error_code error_code;
tcp::resolver::results_type end_points = resolver.resolve("192.168.1.175", "daytime");
tcp::socket socket(io);
boost::asio::connect(socket, end_points);
for(;;) {
std::array<char, 128> buf;
size_t len = socket.read_some(boost::asio::buffer(buf), error_code);
if(error_code == boost::asio::error::eof)
break;
else if(error_code)
throw boost::system::error_code(error_code);
std::cout.write(buf.data(), len);
}
}
catch (std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
server:
std::string MakeDaytimeString() {
std::time_t now = std::time(0);
return ctime(&now);
}
int SyncTcpDaytimeServer() {
try {
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 13));
std::cout << "Server running!!!" << std::endl;
for(;;) {
tcp::socket socket1(io_service);
acceptor.accept(socket1);
std::string message = MakeDaytimeString();
boost::system::error_code ignore_error;
socket1.write_some(boost::asio::buffer(message), ignore_error);
std::cout << "message already arrived" << std::endl;
}
}
catch(std::exception &e) {
std::cout << e.what() << std::endl;
}
return 0;
}
asio 以异步I/O模型实现 (时间 Server)
Header文件
// 用于管理客户端连接的类对象
class TCPConnection: std::enable_shared_from_this<TCPConnection>{
using tcp = boost::asio::ip::tcp;
public:
using pointer = std::shared_ptr<TCPConnection>;
static pointer Create(boost::asio::io_service &io);
tcp::socket& socket() { return socket_; }
void start();
private:
explicit TCPConnection(boost::asio::io_service &io): socket_(io) { }
void handle_write(const boost::system::error_code &error, std::size_t bytes_transferred);
tcp::socket socket_;
std::string message_;
};
// 服务器
class TCPServer {
using tcp = boost::asio::ip::tcp;
public:
TCPServer(boost::asio::io_service &io): io_service_(io), acceptor_(io_service_, tcp::endpoint(tcp::v4(), 13)) {
start_accpect();
}
private:
void start_accpect();
void handle_accpect(TCPConnection::pointer pointer, const boost::system::error_code &e);
boost::asio::io_service &io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
//一个异步TCP daytime 服务器
int AsyncTcpDaytimeServer();
void TCPServer::start_accpect() {
std::cout << acceptor_.local_endpoint() << " run!" << std::endl;
auto tcp_connection = TCPConnection::Create(io_service_);
acceptor_.async_accept(tcp_connection->socket(),[this, tcp_connection](const boost::system::error_code &error_code) {
handle_accpect(tcp_connection, error_code);
});
}
void TCPServer::handle_accpect(TCPConnection::pointer pointer, const boost::system::error_code &e) {
if(!e)
pointer->start();
std::cout << e.message() << std::endl;
start_accpect();
}
TCPConnection::pointer TCPConnection::Create(boost::asio::io_service &io) {
return pointer(new TCPConnection(io));
}
void TCPConnection::start() {
message_ = MakeDaytimeString();
socket_.async_write_some(boost::asio::buffer(message_), [&](const boost::system::error_code &error, std::size_t bytes_transferred){
handle_write(error, bytes_transferred);
});
}
void TCPConnection::handle_write(const boost::system::error_code &error, std::size_t bytes_transferred) {
if(!error)
std::cout << "write success!" << std::endl;
else
std::cerr << "write error!" << std::endl;
}
std::string MakeDaytimeString() {
std::time_t now = std::time(0);
return ctime(&now);
}
int AsyncTcpDaytimeServer() {
try {
boost::asio::io_service io;
TCPServer tcp_server(io);
io.run();
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
asio 异步IO模型实现 (Echo Clent/Server)
服务器方面
// 用于描述服务器与客户端之间的会话并为服务器提供额外的功能
class Session: public std::enable_shared_from_this<Session> {
using tcp = boost::asio::ip::tcp;
public:
Session(tcp::socket socket): socket_(std::move(socket)) { } //注意,在构造函数如果去生成自身,是违法行为的,尤其是生成其动态内存
void Start() { do_read(); }
private:
enum { max_length = 1024 };
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](const boost::system::error_code &error, std::size_t len) {
if(!error) {
(std::cout << "receive a message :").write(data_, len) << std::endl;
do_write(len);
}
});
}
void do_write(std::size_t len) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data_, len),
[this, self](const boost::system::error_code &error, std::size_t len){
std::cout << "write success" << std::endl;
if(!error)
do_read();
else
std::cerr << error.message() << std::endl;
});
}
tcp::socket socket_;
char data_[max_length];
};
// 可回调消息的服务器
class EchoServer {
using tcp = boost::asio::ip::tcp;
public:
EchoServer(boost::asio::io_service &io_service, int port): acceptor_(io_service, tcp::endpoint(tcp::v4(), port)){ }
void Start() { do_accpect(); }
private:
void do_accpect() {
acceptor_.async_accept([this](const boost::system::error_code &error, tcp::socket socket) {
if(!error) {
std::make_shared<Session>(std::move(socket))->Start();
}
std::cout << "one client accpect! " << std::endl;
do_accpect();
});
}
tcp::acceptor acceptor_;
};
客户端方面 (同步阻塞IO):
int EchoClient() {
try {
enum { max_length = 1024 };
using tcp = boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::socket socket(io_service);
tcp::resolver resolver(io_service);
boost::asio::connect(socket, resolver.resolve({"192.168.1.175", "2525"}));
std::cout << "please enter: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_len = strlen(request);
boost::asio::write(socket, boost::asio::buffer(request, request_len));
char reply[max_length];
auto sz = boost::asio::read(socket, boost::asio::buffer(reply, request_len));
(std::cout << "receive a message : ").write(reply,sz);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
main启动服务器开始接收客户端:
void main() {
try {
boost::asio::io_service io_service;
EchoServer server(io_service, 2525); //将服务器程序绑定到该端口上
server.Start();
std::cout << "server is running!!!" << std::endl;
io_service.run();
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
asio 异步IO模型实现 聊天室(Client / Server)
1. 聊天室的组成及逻辑原理
共有六个类:
ChatMessage:
- 提供了用于传输的聊天消息的包的构成:
4个字节的消息包头(实际上真正消息大小),最大可接收512字节的消息空间(保存实际消息)
- 提供了对于在 传输包中取出内容(解包),将内容封装成传输包(装包) 操作。
bool ChatMessage::DecodeHeader() {
char header[HEADER_LENGTH + 1] = "";
std::strncat(header, data_, HEADER_LENGTH);
body_length_ = atoi(header);
if (body_length_ > MAX_BODY_LENGTH) {
body_length_ = 0;
return false;
}
return true;
}
void ChatMessage::EncodeHeader() {
char header[HEADER_LENGTH + 1] = "";
std::sprintf(header, "%4d", static_cast<int>(body_length_));
std::strncpy(data_, header, HEADER_LENGTH);
}
ChatParticipant(聊天参与者):
- 抽象类,仅提供了用于将消息传递给自身客户端的抽象函数。
- 由于是抽象类,由此派生的参与者可设置不同的权限,代表了不同程度的参与者(超级会员参与者等等)
class ChatParticipant {
public:
virtual ~ChatParticipant() = default;
virtual void Deliver(const ChatMessage &msg) = 0;
};
ChatRoom:
提供了管理聊天室的功能:
- 添加 参与者
- 移除参与者
- 将参与者发送的消息派发给全聊天室参与者
class ChatRoom {
public:
void Join(ChatParticipantPtr participant);
void Leave(ChatParticipantPtr participant);
void Deliver(const ChatMessage &msg);
private:
std::set<ChatParticipantPtr> participants_;
enum { MAX_RECENT_MSGS = 100 };
ChatMessageQue recent_msgs_;
};
void ChatRoom::Join(ChatParticipantPtr participant) {
participants_.insert(participant);
for (const auto &msg : recent_msgs_)
participant->Deliver(msg);
}
void ChatRoom::Leave(ChatParticipantPtr participant) {
participants_.erase(participant);
}
void ChatRoom::Deliver(const ChatMessage &msg) {
recent_msgs_.push_back(msg);
for (const auto &participant_ptr: participants_) {
participant_ptr->Deliver(msg);
}
}
ChatSession (继承自 ChatParticipand 的派生类):
聊天会话:每个客户端连接到服务器后都会通过服务器建立一个 ChatSession,并加入到指定的 ChatRoom
- 读取客户端消息,并通过 ChatRoom将消息派送给ChatRoom 的所有参与者
- 将在ChatRoom中其他参与者发送来的消息寄送给 该ChatSession 的客户端。
class ChatSession: public ChatParticipant , public std::enable_shared_from_this<ChatSession> {
private:
void do_write();
void do_read_body();
void do_read_header();
int id_;
tcp::socket socket_;
ChatRoom &room_;
ChatMessage read_msg_;
ChatMessageQue write_msgs_;
public:
ChatSession(int id, tcp::socket socket, ChatRoom &room) : id_(id), socket_(std::move(socket)), room_(room) {}
void Start() {
room_.Join(shared_from_this());
std::cout<< "You have joined Han Zhenjiang's chat room!" << std::endl;
do_read_header();
}
void Deliver(const ChatMessage &msg) override {
bool write_in_process = !write_msgs_.empty();
write_msgs_.push_back(msg);
if(!write_in_process)
do_write();
}
};
void ChatSession::do_write() {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front().data(), write_msgs_.front().length()),
[this, self](const boost::system::error_code &ec, std::size_t bytes_transferred) {
if (!ec) {
write_msgs_.pop_front();
if (!write_msgs_.empty())
do_write();
} else
room_.Leave(shared_from_this());
});
}
void ChatSession::do_read_body() {
auto self(shared_from_this());
boost::asio::async_read(socket_, boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this, self](const boost::system::error_code &ec, std::size_t bytes_transferred) {
if (!ec) {
std::cout.write(read_msg_.body(), read_msg_.body_length());
room_.Deliver(read_msg_);
do_read_header();
} else
room_.Leave(self);
});
}
void ChatSession::do_read_header() {
auto self(shared_from_this());
boost::asio::async_read(socket_, boost::asio::buffer(read_msg_.data(), ChatMessage::HEADER_LENGTH),
[this, self](const boost::system::error_code &ec, std::size_t bytes_transferred) {
if (!ec && read_msg_.DecodeHeader()) {
do_read_body();
} else
room_.Leave(self);
});
}
ChatServer:
- 绑定服务IP,端口
- 提供对客户端的接收,并为客户端创建 ChatSession,使其加入到该服务器管理的 ChatRoom中
//聊天服务器:接收客户端的连接并创建 ChatSession 管理客户端的行为
class ChatServer {
private:
void do_accept() {
acceptor_.async_accept([this](const boost::system::error_code &ec, tcp::socket socket) {
if(!ec) {
std::cout << "a client accpet! \n";
std::make_shared<ChatSession>(++client_id_, std::move(socket), room_)->Start();
}
do_accept();
});
}
int client_id_ = 0;
tcp::acceptor acceptor_;
ChatRoom room_;
public:
ChatServer(boost::asio::io_service &io_service, const tcp::endpoint &endpoint): acceptor_(io_service, endpoint){}
void Start() { do_accept(); }
};
**ChatClient:**
- 连接服务器
- 将自身消息写到与服务器的 ChatSession 中,通过ChatSession 将消息派送到聊天室内,供所有参与者接收
- 读取ChatSession 中其他参与者发送来的消息。
//聊天客户端
class ChatClient {
private:
void do_connect(const tcp::resolver::results_type &end_points);
void do_read_header();
void do_read_body();
void do_write();
boost::asio::io_service &io_service_;
tcp::socket socket_;
ChatMessage read_msg_;
ChatMessageQue write_msgs_;
public:
ChatClient(boost::asio::io_service &io_service, const tcp::resolver::results_type &end_points): io_service_(io_service), socket_(io_service) { }
void Write(const ChatMessage &msg);
void Close() { boost::asio::post(io_service_,[this](){ socket_.close(); }); }
};
void ChatClient::do_connect(const boost::asio::ip::basic_resolver<tcp,boost::asio::executor>::results_type &end_points) {
boost::asio::async_connect(socket_, end_points,
[this]( const boost::system::error_code& ec,
const tcp::endpoint&){
if(!ec)
do_read_header();
});
}
void ChatClient::do_read_header() {
boost::asio::async_read(socket_, boost::asio::buffer(read_msg_.data(), ChatMessage::HEADER_LENGTH),
[this](const boost::system::error_code &ec, std::size_t){
if(!ec && read_msg_.DecodeHeader())
do_read_body();
else
socket_.close();
});
}
void ChatClient::do_read_body() {
boost::asio::async_read(socket_, boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this](const boost::system::error_code &ec, std::size_t){
if(!ec) {
(std::cout).write(read_msg_.body(), read_msg_.body_length()) << "\n";
do_read_header();
}
else
socket_.close();
});
}
void ChatClient::do_write() {
boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front().data(), write_msgs_.front().length()),
[this](const boost::system::error_code &ec, std::size_t){
if(!ec) {
write_msgs_.pop_front();
if(!write_msgs_.empty())
do_write();
} else
socket_.close();
});
}
void ChatClient::Write(const ChatMessage &msg) {
boost::asio::post(io_service_,[this, &msg](){
bool write_in_process = !write_msgs_.empty();
write_msgs_.push_back(msg);
if(!write_in_process)
do_write();
});
}
2. 聊天室 C/S 运行主函数
- 开始服务器
- 绑定服务器 IP, Port,开始运行接收客户端
- 异常处理
int StartServer() {
try {
boost::asio::io_service io_service;
ChatServer server(io_service, tcp::endpoint(tcp::v4(), 2525));
server.Start();
std::cout << "server is running!\n";
io_service.run();
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}
- 开启客户端
一般情况下客户端不仅仅收发消息,可能有 UI 渲染,资源加载方面的操作, 这里我们开启一个线程处理网络连接的收发消息这一块,主线程用于输入消息,并传递消息给副线程。
int StartClient() {
try {
boost::asio::io_service io_service;
tcp::resolver::query q("192.168.0.103", "2525");
tcp::resolver resolver(io_service);
auto end_points = resolver.resolve(q);
ChatClient client(io_service, end_points);
std::thread t([&io_service]() {
io_service.run();
});
char line[ChatMessage::MAX_BODY_LENGTH + 1];
while(std::cin.getline(line, ChatMessage::MAX_BODY_LENGTH + 1)) {
ChatMessage chat_message;
chat_message.set_body_length(strlen(line));
std::memcpy(chat_message.body(), line, chat_message.body_length());
chat_message.EncodeHeader();
client.Write(chat_message);
}
client.Close();
if(t.joinable())
t.join();
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
3. 整体聊天室的实现图
消息协议的制定
消息协议指的是客户端与服务器之间通信的 消息制定标准(格式,大小,作用),
例如:客户端向服务器发送的是字节流的数据,通过消息协议变成指定 大小,格式,用于攻击的消息请求,这便是消息协议的作用,服务器向客户端发送也遵守这样的标准。
接下来:我们将介绍其消息协议指定的几种实现方式,描述其优缺点。
使用 cstruct 制定消息协议
这里我们使用 c 语言风格的 struct 定义消息协议,描述消息的抽象数据类型。
遵循下图描述的客户端消息协议的消息结构:
- 客户端向服务器端发送自身名字时 消息须遵循的消息协议
- 客户端向服务器发送消息(例如:聊天短信) 须遵循的消息协议
- 服务器向客户端发送消息 须遵循的消息协议
优点:
使用 cstruct 还是比较轻松地使用的,且传输速度也比较快。
缺点:
- 定长是最大的缺点,因为其 cstruct 中消息不可变(即使我发送一个 1字节的东西,我也需要消耗掉, 8+256 = 264)
- 改动太大,且消息协议的重建与拆分使用的是原c函数,不安全,对于使用 cstruct 的消息的重建与拆分 都需要底层的 c 代码进行执行,十分的不安全:
例如: cstruct 的拆分需要 strlen 取其实际数据长度, 重建需要将其他 const char* 的字符串使用 std::memcpy 重建到 cstruct 中的字符数组中。
- 客户端编程语言限制(c, c++):客户端也同样遵循 cstruct消息协议 对消息进行重建与拆分,其中不免需要支持指针的语言,目前常用 c,c++。