boost asio相关的使用-基本概念

1端点

boost asio 的endpoint的使用,可以将ip和端口合并成一个端点(endpoint),端点是使用某个端口连接到的一个地址。不同类型的socket有它自己的endpoint类,比如ip::tcp::endpoint、ip::udp::endpoint和ip::icmp::endpoint

如果想连接到本机的80端口,你可以这样做: ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);

有三种方式来让你建立一个端点:
1) endpoint():这是默认构造函数,某些时候可以用来创建 UDP/ICMP socket 。
2)endpoint(protocol, port) :这个方法通常用来创建可以接受新连接的服务器端 socket 。
3)endpoint(addr, port) 这个方法创建了一个连接到某个地址和端口的端点。

例子如下:  ip::tcp::endpoint ep1;

                    ip::tcp::endpoint ep2(ip::tcp::v4(), 80);

                    ip::tcp::endpoint ep3( ip::address::from_string("127.0.0.1), 80);

如果你想连接到一个主机(不是IP地址),你需要这样做:

// 输出 "87.248.122.122"
 io_service service; 
ip::tcp::resolver resolver(service);
ip::tcp::resolver::query query("www.yahoo.com", "80"); 
ip::tcp::resolver::iterator iter = resolver.resolve( query);
ip::tcp::endpoint ep = *iter; std::cout << ep.address().to_string() << std::endl;

你可以用你需要的socket类型来替换tcp。首先,为你想要查询的名字创建一个查询器,然后用resolve()函数解析它。如果成功,它至少会返回一个入口。你可以利用返回的迭代器,使用第一个入口或者遍历整个列表来拿到全部的入口。

如何遍历?

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
using namespace std;
using boost::asio::ip::tcp;
int main(int argc, const char** argv) {
  boost::asio::io_service ioservice;
  tcp::resolver resolver(ioservice);
  tcp::resolver::query query("www.google.com", "80");
  tcp::resolver::iterator it = resolver.resolve(query);
  tcp::resolver::iterator end;
  
  while(it!=end)
  {
    tcp::endpoint ep = *it;
    it++;
    std::cout << ep.address().to_string() << "," << ep.port() << std::endl;
  }
  
  return 0;
}

给定一个端点,可以获得他的地址,端和IP协议(v4或者v6):

std::cout << ep.address().to_string() << ":" << ep.port() << "/" << ep.protocol() << std::endl;

 

io_service 异步的post,dispatch,wrap

解释y: 

post()会将Handler加入到任务队列中,然后在调用了run()、run_one()、 poll()、 poll_one()的线程中执行。post 优先将任务排进处理队列,然后返回,任务会在某个时机被完成。

dispatch()会先进行判断,如果执行dispatch的线程之后就会调用run()、run_one()、 poll()、 poll_one(),那么直接在当前线程执行。否则同post()。
dispatch会即时请求io_service去调用指定的任务

这取决于调用的上下文,即它是从 io_service 内部运行还是不从 io_service 内部运行:

  • post永远不会直接调用该函数,而是推迟调用。
  • dispatch如果调度调用者是从 io_service 本身调用的,则将立即调用它,否则将其排队。

Boost.Asio提供了三种让你把处理方法添加为异步调用的方式:
1)service.post(handler):这个方法能确保其在请求 io_service 实例,然后调用指定的
处理方法之后立即返回。 handler 稍后会在某个调用了 service.run() 的线程中被调用。
2) service.dispatch(handler):这个方法请求 io_service 实例去调用给定的处理方法,
但是另外一点,如果当前的线程调用了 service.run(),它可以在方法中直接调用
handler 。
3) service .wrap( handler):这个方法创建了一个封装方法,当被调用时它会调用
service.dispatch(handler) handler),这个会让人有点困惑,接下来我会简单地解释它是什么
意思。

using namespace boost::asio;
io_service service; 
void func(int i) { std::cout << "func called, i= " << i << std::endl; } 
void run_dispatch_and_post() 
{
     for ( int i = 0; i < 10; i += 2) { 
       service.dispatch(boost::bind(func, i)); 
       service.post(boost::bind(func, i + 1)); 
     } 
} 

int main(int argc, char* argv[])
 {
 service.post(run_dispatch_and_post);
 service.run(); 
}

 结果如下:

func called, i= 0 
func called, i= 2
 func called, i= 4 
func called, i= 6
 func called, i= 8 
func called, i= 1
 func called, i= 3 
func called, i= 5 
func called, i= 7
 func called, i= 9

 

偶数先输出,然后是奇数。这是因为我用dispatch()输出偶数,然后用post()输出奇数。dispatch()会在返回之前调用hanlder,因为当前的线程调用了service.run(),而post()每次都立即返回了。

现在,让我们讲讲service.wrap(handler)。wrap()返回了一个仿函数,它可以用来做另外一个方法的参数:

using namespace boost::asio;
io_service service; 
void dispatched_func_1() 
{
 std::cout << "dispatched 1" << std::endl;
 } 
void dispatched_func_2() { 
std::cout << "dispatched 2" << std::endl; 
} 

void test(boost::function<void()> func) { 
std::cout << "test" << std::endl; 
service.dispatch(dispatched_func_1); func();
}
void service_run() { service.run(); } int main(int argc, char* argv[]) { test( service.wrap(dispatched_func_2)); boost::thread th(service_run); boost::this_thread::sleep( boost::posix_time::millisec(500));
th.join(); }

test(service.wrap(dispatched_func_2));会把dispatched_ func_2包装起来创建一个仿函数,然后传递给test当作一个参数。当test()被调用时,它会分发调用方法1,然后调用func()。这时,你会发现调用func()和service.dispatch(dispatched_func_2)是等价的,因为它们是连续调用的。

程序的输出证明了这一点:

      test

      dispatched 1

      dispatched 2

详见:

 https://blog.csdn.net/wojiuguowei/article/details/78392960

 

3.缓冲区封装函数

纵观所有代码,你会发现:无论什么时候,当我们需要对一个buffer进行读写操作时,代码会把实际的缓冲区对象封装在一个buffer()方法中,然后再把它传递给方法调用:

char buff[512];

sock.async_receive(buffer(buff), on_read);
基本上我们都会把缓冲区包含在一个类中以便Boost.Asio的方法能遍历这个缓冲区,比方说,

使用下面的代码: sock.async_receive(some_buffer, on_read);
实例some_buffer需要满足一些需求,叫做ConstBufferSequence或者MutableBufferSequence(你可以在Boost.Asio的文档中查看它们)。创建你自己的类去处理这些需求的细节是非常复杂的,但是Boost.Asio已经提供了一些类用来处理这些需求。所以你不用直接访问这些缓冲区,而可以使用buffer()方法。

自信地讲,你可以把下面列出来的类型都包装到一个buffer()方法中:
1. 一个 char[] const 数组
2. 一个字节大小的 void * 指针
3.  一个 std::string 类型的字符串
4.  一个 POD const 数组( POD 代表纯数据,这意味着构造器和释放器不做任何操作)
5. 一个 pod 数据的 std::vector
6.  一个包含 pod 数据的 boost::array
7. 一个包含 pod 数据的 std::array
下面的代码都是有效的:

struct pod_sample { int i; long l; char c; }; ... 

char b1[512];

void * b2 = new char[512];
std::string b3; b3.resize(128);
pod_sample b4[16];
std::vector<pod_sample> b5; b5.resize(16);
boost::array<pod_sample,16> b6;
std::array<pod_sample,16> b7;
sock.async_send(buffer(b1), on_read);
sock.async_send(buffer(b2,512), on_read);
sock.async_send(buffer(b3), on_read);
sock.async_send(buffer(b4), on_read);
sock.async_send(buffer(b5), on_read);
sock.async_send(buffer(b6), on_read);
sock.async_send(buffer(b7), on_read);



总的来说就是:与其创建你自己的类来处理ConstBufferSequence或者MutableBufferSequence的需求,不如创建一个能在你需要的时候保留缓冲区,然后返回一个mutable_buffers_1实例的类,而我们早在shared_buffer类中就这样做了。

同步读写:

boost::asio提供了几种同步写的api,write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。

void  wirte_to_socket(asio::ip::tcp::socket& sock) {
    std::string buf = "Hello World!";
    std::size_t  total_bytes_written = 0;
    //循环发送
    //write_some返回每次写入的字节数
    //total_bytes_written是已经发送的字节数。
    //每次发送buf.length()- total_bytes_written)字节数据
    while (total_bytes_written != buf.length()) {
        total_bytes_written += sock.write_some(
            asio::buffer(buf.c_str()+total_bytes_written, 
                buf.length()- total_bytes_written));
    }
}

write_some使用起来比较麻烦,需要多次调用,asio提供了send函数。send函数会一次性将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成。

 int send_length = sock.send(asio::buffer(buf.c_str(), buf.length()));

即如果返回则,只有三种情况

<0:发生网络错误

=0:连接被对端关闭

=send_length:即buf.length=send_length,只会相等,不会小于,未发生完毕则一直阻塞,直到发生完毕为止

int send_data_by_send(){
    std::string raw_ip_address = "127.0.0.1";
    unsigned short port_num = 3333;
    try {
        asio::ip::tcp::endpoint
            ep(asio::ip::address::from_string(raw_ip_address),
                port_num);
        asio::io_service ios;
        // Step 1. Allocating and opening the socket.
        asio::ip::tcp::socket sock(ios, ep.protocol());
        sock.connect(ep);
        std::string buf = "Hello World!";
        int send_length = sock.send(asio::buffer(buf.c_str(), buf.length()));
        if (send_length <= 0) {
            cout << "send failed" << endl;
            return 0;
        }
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code()
            << ". Message: " << e.what();
        return e.code().value();
    }
    return 0;
}

类似send方法,asio还提供了一个write函数,可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。

同步读read_some

同步读和同步写类似,提供了读取指定字节数的接口read_some

std::string read_from_socket(asio::ip::tcp::socket& sock) {
    const unsigned char MESSAGE_SIZE = 7;
    char buf[MESSAGE_SIZE];
    std::size_t total_bytes_read = 0;
    while (total_bytes_read != MESSAGE_SIZE) {
        total_bytes_read += sock.read_some(
            asio::buffer(buf + total_bytes_read,
                MESSAGE_SIZE - total_bytes_read));
    }
    return std::string(buf, total_bytes_read);
}
int read_data_by_read_some() {
    std::string raw_ip_address = "127.0.0.1";
    unsigned short port_num = 3333;
    try {
        asio::ip::tcp::endpoint
            ep(asio::ip::address::from_string(raw_ip_address),
                port_num);
        asio::io_service ios;
        asio::ip::tcp::socket sock(ios, ep.protocol());
        sock.connect(ep);
        read_from_socket(sock);
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code()
            << ". Message: " << e.what();
        return e.code().value();
    }
    return 0;
}

其他类似

同步读receive可以一次性同步接收对方发送的数据,同步读boost::asio::read可以一次性同步读取对方发送的数据,与send类似都是阻塞的

 

 详细见:

https://www.cnblogs.com/milanleon/p/7609574.html

         

 

posted @ 2023-06-30 18:59  白伟碧一些小心得  阅读(612)  评论(0编辑  收藏  举报