我有一台使用Boost ASIO的TCP服务器。我注意到在Linux上使用链接范围的IPv6地址时,我无法在不抛出异常的情况下创建boost::asio::ip::tcp::acceptor
。使用全局IPv6地址或IPv4地址将正常工作。
我很确定问题是范围ID未正确设置但我无法弄清楚如何解决问题。
我正在使用ubuntu提供的boost 1.40.0库在Ubuntu 11.04 LTS上进行开发。这是我所拥有的服务器代码的一个非常愚蠢的版本,它显示了问题:
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
/* To Compile:
g++ -Wall -o ./asio-ipv6 ./asio-ipv6.cpp -lboost_system
*/
typedef boost::shared_ptr<boost::asio::ip::tcp::socket> TcpSocketPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::acceptor> TcpAcceptorPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::endpoint> TcpEndpointPtr;
class AsioServer{
public:
AsioServer(boost::asio::io_service& io): io_(io){};
//throws
void accept(const std::string& ipString,unsigned short port){
boost::asio::ip::address addr = boost::asio::ip::address::from_string(ipString);
std::cout << "Valid IP address " << ipString << std::endl;
this->endpoint_.reset(new boost::asio::ip::tcp::endpoint(addr,port));
std::cout << "Created endpoint" << std::endl;
//Will throw if a link local IPv6 address is used
acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_,*(this->endpoint_)));
std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
this->socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
this->acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error));
}
private:
boost::asio::io_service& io_;
TcpSocketPtr socket_;
TcpAcceptorPtr acceptor_;
TcpEndpointPtr endpoint_;
void handle_accept(const boost::system::error_code &ec){
if(!ec){
std::cout << "Accepted connection!" << std::endl;
}
else{
std::cout << "Error accepting connection" << std::endl;
}
};
};
int main(int argc, char* argv[]){
boost::asio::io_service io;
std::string ipString("0.0.0.0");
if(argc > 1){
ipString.assign(argv[1]);
}
std::cout << "IP Set to " << ipString << std::endl;
AsioServer server(io);
try{
server.accept(ipString,4444);
}
catch(const std::exception& e){
std::cout << "Error caught: " << e.what() << std::endl;
}
io.run();
std::cout << "Done!" << std::endl;
return 0;
}
在这台机器上,我为eth0配置了以下内容:
$ ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:0c:29:10:cf:0e
inet addr:192.168.97.162 Bcast:192.168.97.255 Mask:255.255.255.0
inet6 addr: 2620:1c:8000:190:20c:29ff:fe10:cf0e/64 Scope:Global
inet6 addr: fe80::20c:29ff:fe10:cf0e/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:112470185 errors:2 dropped:1 overruns:0 frame:0
TX packets:5900249 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2187237130 (2.1 GB) TX bytes:1640094885 (1.6 GB)
Interrupt:19 Base address:0x2000
使用 192.168.97.162 , 0.0.0.0 , 127.0.0.1 , :: 1 运行程序或 2620:1c:8000:190:20c:29ff:fe10:cf0e 工作正常,但使用 fe80 :: 20c:29ff:fe10:cf0e 无法创建一个受体抛出“无效参数”异常。
$ ./asio-ipv6 fe80::20c:29ff:fe10:cf0e
IP Set to fe80::20c:29ff:fe10:cf0e
Valid IP address fe80::20c:29ff:fe10:cf0e
Created endpoint
Error caught: Invalid argument
Done!
这让我想起使用ping6并看到相同的“Invalid Argument”错误。修复方法是传递附加到IPv6地址的接口。
$ ping6 fe80::219:b9ff:fe2b:3a53 #Won't work
connect: Invalid argument
$ ping6 fe80::219:b9ff:fe2b:3a53%eth0 #Pass the interface to use to ping6
PING fe80::219:b9ff:fe2b:3a53%eth0(fe80::219:b9ff:fe2b:3a53) 56 data bytes
当我尝试从字符串创建IP地址时,这对Boost Asio似乎不起作用。
$ ./asio-ipv6 fe80::20c:29ff:fe10:cf0e%eth0
IP Set to fe80::20c:29ff:fe10:cf0e%eth0
Error caught: Invalid argument
Done!
我的问题是你如何使用Boost ASIO监听链接范围的IPv6地址? boost::asio::ip::address_v6
类有一个scope_id() member function,但我不知道在哪里可以将范围ID作为无符号长整型,或者甚至是问题。
答案 0 :(得分:3)
这可以使用boost::asio::ip::tcp::resolver
--- ipv6.cc.orig 2012-04-24 12:03:02.349911481 -0500
+++ ipv6.cc 2012-04-24 12:02:07.053037095 -0500
@@ -10,42 +10,67 @@
typedef boost::shared_ptr<boost::asio::ip::tcp::socket> TcpSocketPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::acceptor> TcpAcceptorPtr;
+typedef boost::shared_ptr<boost::asio::ip::tcp::resolver> TcpResolverPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::endpoint> TcpEndpointPtr;
class AsioServer{
public:
AsioServer(boost::asio::io_service& io): io_(io){};
- //throws
- void accept(const std::string& ipString,unsigned short port){
- boost::asio::ip::address addr = boost::asio::ip::address::from_string(ipString);
- std::cout << "Valid IP address " << ipString << std::endl;
-
- this->endpoint_.reset(new boost::asio::ip::tcp::endpoint(addr,port));
- std::cout << "Created endpoint" << std::endl;
-
- //Will throw if a link local IPv6 address is used
- acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_,*(this->endpoint_)));
-
- std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
- this->socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
- this->acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error));
+ void resolve(const std::string& ipString, const std::string& service){
+ this->resolver_.reset(new boost::asio::ip::tcp::resolver(io_));
+ boost::asio::ip::tcp::resolver::query query( ipString, service );
+ this->resolver_->async_resolve( query, boost::bind( &AsioServer::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );
}
-
private:
boost::asio::io_service& io_;
TcpSocketPtr socket_;
TcpAcceptorPtr acceptor_;
TcpEndpointPtr endpoint_;
+ TcpResolverPtr resolver_;
+
+ void accept(const boost::asio::ip::tcp::resolver::iterator iterator) {
+ endpoint_.reset(new boost::asio::ip::tcp::endpoint(iterator->endpoint()));
+ //Will throw if a link local IPv6 address is used
+ acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_));
+ acceptor_->open(this->endpoint_->protocol());
+ acceptor_->bind(*(this->endpoint_));
+ acceptor_->listen();
+
+
+ std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
+ socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
+ acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error));
+ }
+
void handle_accept(const boost::system::error_code &ec){
if(!ec){
std::cout << "Accepted connection!" << std::endl;
}
else{
- std::cout << "Error accepting connection" << std::endl;
+ std::cout << "Error accepting connection: " << boost::system::system_error(ec).what() << std::endl;
+ }
+ }
+
+ void handle_resolve(
+ const boost::system::error_code& ec,
+ boost::asio::ip::tcp::resolver::iterator iterator
+ )
+ {
+ if(ec) {
+ std::cerr << "could not resolve: " << boost::system::system_error(ec).what() << std::endl;
+ return;
}
- };
+
+ if ( iterator == boost::asio::ip::tcp::resolver::iterator() ) {
+ std::cerr << "no endpoints resolved" << std::endl;
+ return;
+ }
+
+ this->accept(iterator);
+
+ }
};
@@ -62,7 +87,7 @@
AsioServer server(io);
try{
- server.accept(ipString,4444);
+ server.resolve(ipString,"4444");
}
catch(const std::exception& e){
std::cout << "Error caught: " << e.what() << std::endl;
示例会话
[samm@t410 Desktop]$ ifconfig | grep fe80
inet6 addr: fe80::5a94:6bff:fe7c:e760/64 Scope:Link
[samm@t410 Desktop]$ g++ -g ipv6.cc -lboost_system -lboost_thread-mt
[samm@t410 Desktop]$ ./a.out fe80::5a94:6bff:fe7c:e760%wlan0
IP Set to fe80::5a94:6bff:fe7c:e760%wlan0
About to accept on [fe80::5a94:6bff:fe7c:e760%wlan0]:4444
^C
[samm@t410 Desktop]$
答案 1 :(得分:1)
使用命令行中的ip link ls
或代码中的if_nametoindex()
来获取给定设备的接口索引。例如。在我的笔记本电脑上:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000
link/ether f0:de:f1:5c:39:37 brd ff:ff:ff:ff:ff:ff
在我的例子中,eth0
的接口索引是2.如果Boost只接受数值,则使用此作为范围ID。
答案 2 :(得分:1)
使用spawn函数这是一个更简单的解决方案。
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
class AsioServer{
public:
AsioServer(boost::asio::io_service& io): m_io(io) {}
void accept(const std::string& ip_address, const std::string& service) {
boost::asio::spawn(m_io, [this, ip_address, service](boost::asio::yield_context yield) {
try {
using tcp = boost::asio::ip::tcp;
boost::system::error_code ec;
tcp::resolver resolver(m_io);
tcp::resolver::iterator iterator =
resolver.async_resolve(tcp::resolver::query(ip_address, service), yield);
if (iterator == tcp::resolver::iterator()) {
std::cerr << "No endpoints resolved for "<< ip_address << std::endl;
return;
}
tcp::endpoint endpoint(iterator->endpoint());
std::cout << "Endpoint: " << endpoint << std::endl;
tcp::acceptor acceptor(m_io);
acceptor.open(endpoint.protocol());
acceptor.bind(endpoint);
acceptor.listen();
std::cout << "About to accept on " << endpoint << std::endl;
for (;;)
{
tcp::socket socket(m_io);
acceptor.async_accept(socket, yield[ec]);
if (ec) {
std::cerr << "Error accepting connection: " << boost::system::system_error(ec).what() << std::endl;
} else {
std::cout << "Accepted connection from " << socket.remote_endpoint() << std::endl;
//std::make_shared<session>(std::move(socket))->go();
}
}
}
catch( std::exception &e) {
std::cerr << "Error setting up accepting port: " << e.what() << std::endl;
}
});
}
private:
boost::asio::io_service& m_io;
};
int main(int argc, char* argv[])
{
boost::asio::io_service io;
std::string ipString("0.0.0.0");
if(argc > 1){
ipString.assign(argv[1]);
}
std::cout << "IP Set to " << ipString << std::endl;
AsioServer server(io);
try{
server.accept(ipString,"4444");
}
catch(const std::exception& e){
std::cout << "Error caught: " << e.what() << std::endl;
}
io.run();
std::cout << "Done!" << std::endl;
return 0;
}
执行结果
AsioServer$ ifconfig | grep fe80
adr inet6: fe80::be5f:f4ff:fef7:8a46/64 Scope:Lien
AsioServer$ ./a_out fe80::be5f:f4ff:fef7:8a46%eth0
IP Set to fe80::be5f:f4ff:fef7:8a46%eth0
Endpoint: [fe80::be5f:f4ff:fef7:8a46%eth0]:4444
About to accept on [fe80::be5f:f4ff:fef7:8a46%eth0]:4444