天天看點

Boost.Asio的網絡程式設計簡介用戶端同步伺服器異步伺服器

簡介

這篇筆記是boost::asio的概覽, 主要說明了

boost

的進行CS結構程式設計的基本步驟. 在網絡程式設計中, 又很多需要IO的操作. 一種是使用Linux的原生C語言API, Linux的核心程式設計思想是操作檔案描述符, 所有的操作都是基于檔案描述符. 而在boost中, 增添了更多功能的操作, 而且更加增強了關于異步的操作. 這篇筆記主要記錄了使用boost::asio庫進行的操作.

用戶端

用戶端主要的操作步驟是:

  • 建立

    io_service

    , 這是程式和核心的IO連接配接, 需要使用

    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;
}