首页 > 代码库 > Boost.Asio c++ 网络编程翻译(8)

Boost.Asio c++ 网络编程翻译(8)

TCP VS UDP VS ICMP
就像我之前所说,不是所有的成员方法在所有的套接字类中都可用。我做了一个包含成员函数不同点的列表。如果一个成员函数没有出现在这,说明它在所有的套接字类都是可用的。
名字                         TCP                 UDP                 ICMP
async_read_some              是                  -                   -
async_receive_from           -                   是                  是
async_write_some             是                  -                   -
async_send_to                -                   是                  是
read_some                    是                  -                   -
receive_from                 -                   是                  是
write_some                   是                  -                   -
send_to                      -                   是                  是

其他方法
其他与连接和I/O无关的函数如下:
local_endpoint():这个方法返回套接字本地连接的地址。
remote_endpoint():这个方法返回套接字连接到的远程地址。
native_handle():这个方法返回原始套接字的处理程序。你只有在调用一个Boost.Asio不支持的原始方法时才需要用到它
non_blocking():如果套接字是非阻塞的,这个方法返回true,否则false
native_non_blocking():如果套接字是非阻塞的,这个方法返回true,否则返回false。但是,它是基于原生的套接字来调用本地的api。所以通常来说,你不需要调用这个方法(non_blocking()已经缓存了这个结果);你只有在直接调用native_handle()这个方法的时候猜需要使用到这个方法
at_mark():如果套接字要读的是一段OOB数据,这个方法返回true。这个方法你很少会用到

其他的考虑
最后要注意,一个套接字实例不能被拷贝,因为拷贝构造方法和=操作符是不可访问的。
ip::tcp::socket s1(service), s2(service);
   s1 = s2; // 编译时报错
   ip::tcp::socket s3(s1); // 编译时报错
这是非常有意义的,因为每一个实例都拥有并管理着一个资源(原生套接字本身)。如果我们允许拷贝构造,结果是我们会有两个实例拥有同样的原生套接字;这样我们就需要去处理所有者的问题(让一个实例拥有所有权?或者使用引用计数?还是其他的方法)Boost.Asio选择不允许拷贝(如果你想要创建一个备份,请使用共享指针)
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
   socket_ptr sock1(new ip::tcp::socket(service));
   socket_ptr sock2(sock1); // ok
   socket_ptr sock3;			
   sock3 = sock1; // ok
套接字缓冲区
当从一个套接字读写内容时,你需要一个缓冲区,用来保存读入和写出的数据。缓冲区内存的有效时间必须比I/O操作的时间要长;你需要保证它们在I/O操作结束之前不被释放。
对于同步操作来说,这很容易;当然,这个缓冲区在receive和send时都存在。
char buff[512];
   ...
   sock.receive(buffer(buff));
   strcpy(buff, "ok\n");
   sock.send(buffer(buff));
但是在异步操作时就这么简单了,看下面的代码片段:			
// 非常差劲的代码 ...
   void on_read(const boost::system::error_code & err, std::size_t read_
   bytes)
   { ... }
   void func() {
 char buff[512];
       sock.async_receive(buffer(buff), on_read);
   }
在我们调用async_receive()之后,buff就已经超出有效范围,它的内存当然会被释放。当我们开始从套接字接收一些数据时,我们会把它们拷贝到一片已经不属于我们的内存中;它可能会被释放,或者被其他代码重新开辟来存入其他的数据,结果就是:内存冲突。
对于上面的问题有几个解决方案:
使用全局缓冲区
创建一个缓冲区,然后在操作结束时释放它
使用一个集合对象管理这些套接字和其他的数据,比如缓冲区数组
第一个方法显然不是很好,因为我们都知道使用全局变量很不好。此外,如果两个实例使用同一个缓冲区怎么办?
下面是第二种方式的实现:						
  1. void on_read(char * ptr, const boost::system::error_code & err,
       std::size_t read_bytes) {						
           delete[] ptr;
       }
    
       ....
       char * buff = new char[512];
    
       sock.async_receive(buffer(buff, 512), boost::bind(on_
       read,buff,_1,_2))
    
或者,如果你想要缓冲区在操作结束后自动超出范围,使用共享指针
struct shared_buffer {
    boost::shared_array<char> buff;
    int size;
    shared_buffer(size_t size) : buff(new char[size]), size(size) {
    }
    mutable_buffers_1 asio_buff() const {
        return buffer(buff.get(), size);
    }

};

// 当on_read超出范围时, boost::bind对象被释放了,

// 同时也会释放共享指针
void on_read(shared_buffer, const boost::system::error_code & err,
                               std::size_t read_bytes) {}
   sock.async_receive(buff.asio_buff(), boost::bind(on_read,buff,_1,_2));

shared_buffer类拥有实质的shared_array<>,shared_array<>存在的目的是用来保存shared_buffer实例的拷贝-当最后一个share_array<>元素超出范围时,shared_array<>就被自动销毁了,而这就是我们想要的结果。

因为Boost.Asio会给完成处理句柄保留一个拷贝,当操作完成时就会调用这个完成处理句柄,所以你的目的达到了。那个拷贝是一个boost::bind的仿函数,它拥有着实际的shared_buffer实例。这是非常优雅的!

第三个选择是使用一个连接对象来管理套接字和其他数据,比如缓冲区,通常来说这是正确的解决方案但是非常复杂。在这一章的末尾我们会对这种方法进行讨论。

Boost.Asio c++ 网络编程翻译(8)