1. 程式人生 > 其它 >二、asio使用教程--套接字

二、asio使用教程--套接字

使用TCP實現daytime協議

同步的daytime客戶端

利用asio實現一個TCP的客戶端應用。

引入標頭檔案

#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>

這個應用程式需要訪問daytime伺服器,所以需要指定伺服器。

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], "daytime");
    tcp::socket socket(io_context);
	//會自動遍歷並嘗試連線,直到成功
    boost::asio::connect(socket, endpoints);

連線建立後,就可以從daytime伺服器讀取響應資訊。

我們使用boost::array來儲存接收的資料。boost::asio::buffer() 函式自動確定陣列的大小以幫助防止緩衝區溢位。我們可以使用 char [] std::vector 來代替 boost::array

    for (;;)
    {
      boost::array<char, 128> buf;
      boost::system::error_code error;

      size_t len = socket.read_some(boost::asio::buffer(buf), error);

當伺服器關閉連線時,ip::tcp::socket::read_some()

函式將退出並出現 boost::asio::error::eof 錯誤,這就是退出迴圈的方式。

      if (error == boost::asio::error::eof)
        break; // Connection closed cleanly by peer.
      else if (error)
        throw boost::system::system_error(error); // Some other error.

      std::cout.write(buf.data(), len);
    }

最後,處理丟擲來的異常。

  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

完整程式碼如下:

#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], "daytime");

    tcp::socket socket(io_context);
    boost::asio::connect(socket, endpoints);

    for (;;)
    {
      boost::array<char, 128> buf;
      boost::system::error_code error;

      size_t len = socket.read_some(boost::asio::buffer(buf), error);

      if (error == boost::asio::error::eof)
        break; // Connection closed cleanly by peer.
      else if (error)
        throw boost::system::system_error(error); // Some other error.

      std::cout.write(buf.data(), len);
    }
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}

同步的daytime服務端

本節顯示瞭如何使用asio實現一個TCP伺服器應用。

首先引入標頭檔案

#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

我們定義了函式make_daytime_string()來生成返回給客戶端的字串。該函式可以被我們所有的daytime伺服器應用重用。

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

int main()
{
  try
  {
    boost::asio::io_context io_context;

建立一個acceptor物件來監聽連線。接受連線後,將時間字串傳送到客戶端。

    tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
    for (;;)
    {
      tcp::socket socket(io_context);
      acceptor.accept(socket);
      //訪問的時間
      std::string message = make_daytime_string();
		//傳送到客戶端
      boost::system::error_code ignored_error;
      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/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);
}

int main()
{
  try
  {
    boost::asio::io_context io_context;

    tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));

    for (;;)
    {
      tcp::socket socket(io_context);
      acceptor.accept(socket);

      std::string message = make_daytime_string();

      boost::system::error_code ignored_error;
      boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
    }
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}

非同步的daytime服務端

我們建立一個伺服器物件來接受傳入的客戶端連線。

在主函式中只需要啟動伺服器即可。

int main() {
    try{
        boost::asio::io_context ioc;
        tcp_server server(ioc);
        ioc.run();
    } catch(std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
    return 0;
}

關鍵在於tcp_server伺服器類的實現,作為伺服器,一定要有一個接收連線的接收器,然後還有對接收的連線的處理方法。

建構函式在TCP的埠13初始化接收器。

class tcp_server
{
public:
  tcp_server(boost::asio::io_context& io_context)
    : io_context_(io_context),
      acceptor_(io_context, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }

private:

start_accept()建立一個套接字並使用一個非同步accept操作等待一個新連線。

  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(io_context_);

    acceptor_.async_accept(new_connection->socket(),
        boost::bind(&tcp_server::handle_accept, this, new_connection,
          boost::asio::placeholders::error));
  }

當由 start_accept() 發起的非同步接受操作完成時,將呼叫函式 handle_accept()。 它為客戶端請求提供服務,然後呼叫 start_accept() 以啟動下一個接受操作。

  void handle_accept(tcp_connection::pointer new_connection,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }

    start_accept();
  }

tcp_connection類

我們將使用 shared_ptr enable_shared_from_this 因為我們想讓 tcp_connection 物件保持活動狀態,只要有操作引用它。

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;

  static pointer create(boost::asio::io_context& io_context)
  {
    return pointer(new tcp_connection(io_context));
  }

  tcp::socket& socket()
  {
    return socket_;
  }

tcp_connection物件就是我們用來封裝對連線的操作的。

在函式 start() 中,我們呼叫 boost::asio::async_write() 將資料提供給客戶端。 請注意,我們使用的是 boost::asio::async_write(),而不是 ip::tcp::socket::async_write_some(),以確保傳送整個資料塊。

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

此客戶端連線的任何進一步操作現在由 handle_write() 負責。

  }

private:
  tcp_connection(boost::asio::io_context& io_context)
    : socket_(io_context)
  {
  }

  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }

  tcp::socket socket_;
  std::string message_;
};

這裡使用了智慧指標,意在讓TcpConnection物件執行完後自動釋放,對於非同步操作是有等待時間的,故為了讓連線不會在等待時間內釋放,那麼我們就要將智慧指標同樣傳遞給回撥引數。

完整程式碼如下:

#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind/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:
  typedef boost::shared_ptr<tcp_connection> pointer;

  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:
  tcp_connection(boost::asio::io_context& io_context)
    : socket_(io_context)
  {
  }

  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }

  tcp::socket socket_;
  std::string message_;
};

class tcp_server
{
public:
  tcp_server(boost::asio::io_context& io_context)
    : io_context_(io_context),
      acceptor_(io_context, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }

private:
  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(io_context_);

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

  boost::asio::io_context& io_context_;
  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;
}

使用UDP實現

同步的daytime客戶端

我們使用 ip::udp::resolver 物件根據主機名和服務名找到要使用的正確遠端端點。 該查詢被ip::udp::v4()引數限制為僅返回 IPv4 端點。

    udp::resolver resolver(io_context);
    udp::endpoint receiver_endpoint =
      *resolver.resolve(udp::v4(), argv[1], "daytime").begin();

如果 ip::udp::resolver::resolve() 函式沒有失敗,它保證至少返回列表中的一個端點。 這意味著直接解引用的返回值是安全的。

由於 UDP 是面向資料報的,因此我們不會使用流套接字。 建立一個 ip::udp::socket 並啟動與遠端端點的聯絡。

    udp::socket socket(io_context);
    socket.open(udp::v4());

    boost::array<char, 1> send_buf  = {{ 0 }};
    socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint);

與TCP不同的是,我們UDP客戶端必須要先發送資料過去,服務端才能收到客戶端的資訊。

現在我們需要準備好接受伺服器發回給我們的任何內容。 我們這邊接收伺服器響應的端點將由 ip::udp::socket::receive_from() 初始化。

    boost::array<char, 128> recv_buf;
    udp::endpoint sender_endpoint;
    size_t len = socket.receive_from(
        boost::asio::buffer(recv_buf), sender_endpoint);

    std::cout.write(recv_buf.data(), len);
  }

完整程式碼如下:

#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>

using boost::asio::ip::udp;

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;

    udp::resolver resolver(io_context);
    udp::endpoint receiver_endpoint =
      *resolver.resolve(udp::v4(), argv[1], "daytime").begin();

    udp::socket socket(io_context);
    socket.open(udp::v4());

    boost::array<char, 1> send_buf  = {{ 0 }};
    socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint);

    boost::array<char, 128> recv_buf;
    udp::endpoint sender_endpoint;
    size_t len = socket.receive_from(
        boost::asio::buffer(recv_buf), sender_endpoint);

    std::cout.write(recv_buf.data(), len);
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}

同步的daytime伺服器

建立一個UDP的套接字物件用來接收UDP埠13上的請求。

每接收到一個數據報就意味著客戶端的一次請求,我們要將響應傳送過去。

完整程式碼如下:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>

using boost::asio::ip::udp;

int main(int argc, char* argv[]) {
  try {
    if(argc != 2) {
      std::cerr << "Usage client <host> " << std::endl;
      return 1;
    }
    boost::asio::io_context ioc;
    udp::resolver resolver(ioc);
    //解析出端點,我們只使用一個ipv4的
    udp::endpoint endpoint = *resolver.resolve(udp::v4(),argv[1],"daytime").begin();

    //建立UDP套接字
    udp::socket socket(ioc);
    socket.open(udp::v4());

    //向服務端傳送資料,然後接受響應並打印出來
    boost::array<char,1> send_buf = {0};
    socket.send_to(boost::asio::buffer(send_buf),endpoint);

    boost::array<char,128> recv_buf;
    udp::endpoint send_endpoint;
    size_t len = socket.receive_from(boost::asio::buffer(recv_buf),send_endpoint);

    std::cout.write(recv_buf.data(),len);

  } catch(std::exception& e) {
    std::cerr << e.what() << std::endl;
  }
} 

非同步的daytime伺服器

通過一個伺服器物件來接收客戶端請求

int main() {
  try {
    boost::asio::io_context ioc;
    UdpServer server(ioc);

    ioc.run();
  } catch(const std::exception& e) {
    std::cerr << e.what() << '\n';
  }
  return 0;
}

伺服器中啟動接收資料

void start_receive() {
    boost::array<char,1> recv_buf;
    socket_.async_receive_from(boost::asio::buffer(recv_buf),
                                remote_endpoit_,
                                boost::bind(&UdpServer::handle_receive,
                                            this,
                                            boost::asio::placeholders::error,
                                            boost::asio::placeholders::bytes_transferred));
  }

接收完畢後,開始向對端傳送響應,處理結束後等待下一個請求。

void handle_receive(const boost::system::error_code& error,std::size_t len) {
    std::string message = make_daytime_string();
    socket_.async_send_to(boost::asio::buffer(message),
                          remote_endpoit_,
                          boost::bind(&UdpServer::handle_send,
                                      this,
                                      boost::asio::placeholders::error,
                                      boost::asio::placeholders::bytes_transferred));
    start_receive();
  }

完整程式碼如下:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <ctime>
#include <string>

using boost::asio::ip::udp;

std::string make_daytime_string() {
  time_t now = time(0);
  return ctime(&now);
}

class UdpServer {
 public:
  UdpServer(boost::asio::io_context& ioc) : socket_(ioc,udp::endpoint(udp::v4(),13)) {
    start_receive();
  }
 private:
  void start_receive() {
    boost::array<char,1> recv_buf;
    socket_.async_receive_from(boost::asio::buffer(recv_buf),
                                remote_endpoit_,
                                boost::bind(&UdpServer::handle_receive,
                                            this,
                                            boost::asio::placeholders::error,
                                            boost::asio::placeholders::bytes_transferred));
  }
  void handle_receive(const boost::system::error_code& error,std::size_t len) {
    std::string message = make_daytime_string();
    socket_.async_send_to(boost::asio::buffer(message),
                          remote_endpoit_,
                          boost::bind(&UdpServer::handle_send,
                                      this,
                                      boost::asio::placeholders::error,
                                      boost::asio::placeholders::bytes_transferred));
    start_receive();
  }
  void handle_send(const boost::system::error_code& ,std::size_t len) {

  }
  udp::socket socket_;
  udp::endpoint remote_endpoit_;
};

int main() {
  try {
    boost::asio::io_context ioc;
    UdpServer server(ioc);

    ioc.run();
  } catch(const std::exception& e) {
    std::cerr << e.what() << '\n';
  }
  return 0;
}

TCP/UDP結合的非同步伺服器

我們將剛才的兩個TCP和UDP非同步伺服器結合到一個伺服器應用中。

其實就是在主函式中啟動兩個伺服器而已,其他程式碼都不需要更改,直接就可以使用了。

int main()
{
  try
  {
    boost::asio::io_context io_context;
    tcp_server server1(io_context);
    udp_server server2(io_context);
    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}