Boost 准标准库中的网络库 asio(带官方案例剖析)

这篇文章带给读者什么东西?

这篇文章对Boost.asio进行介绍,且涵盖网络编程知识,以及asio 网络库的使用与注意事项。

本篇文章涉及到的源码地址

讲解源码,不要忘了给个小星星✨哦,Thank!😜:源码

查看BoostAsioStudy即可,如下图:

在这里插入图片描述

Boost.asio介绍

Boost.Asio是用于网络和底层I / O编程的跨平台C ++库,它使用现代C ++方法为开发人员提供一致的异步模型。

基于 Proactor模式的事件驱动模型。

Boost.asio 官方地址

同步编程与异步编程 (线程)

请参考博主的博客: 深入理解同步编程与异步编程

Clion配置Boost “准”标准库

关于C++IDE JetBrains Clion可以参照我的这篇博客:

欲编 C++ 好代码,须先利其 IDE:Clion

1. 下载Boost库

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 运行主函数

  1. 开始服务器
  • 绑定服务器 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;
}
  1. 开启客户端

一般情况下客户端不仅仅收发消息,可能有 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++。

C++的 Class

JSON

Protobuf(重要)

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值