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