Linux下MyIpAdd库的使用

一、MyIpAdd库简介

MyIpAdd库是一个纯C++库,可以用它获取我们的公网IP地址。其github地址为:https://github.com/felmur/MyIpAdd

二、下载编译MyIpAdd源码

从 https://github.com/felmur/MyIpAdd 下载MyIpAdd源码:

解压,进入源码目录,可以看到其目录结构如下:

编译:

cmake . -DCMAKE_INSTALL_PREFIX=$(pwd)/install make make install

然后源码目录下会生成MyIpAdd的库文件libMyIpAdd.so:

三、MyIpAdd的使用例子

以C++为例,编写一个使用示例。main.cpp内容如下:

#include <iostream> #include <myipadd.h> using namespace std; int main() { MyIpAdd * m = new MyIpAdd(); cout << "My public ip address is: '" << m->ip << "'" << endl; return 0; }

编译:

g++ main.cpp -o main -g -IXXX/MyIpAdd-master -LXXX/MyIpAdd-master -lMyIpAdd

运行,效果如下。可以看到本机的公网ip被成功打印出来了:

该ip和通过百度查询到的公网ip一致:

四、MyIpAdd源码里的bug

MyIpAdd源码里,myipadd.cpp的构造函数 MyIpAdd::MyIpAdd()的代码为:

MyIpAdd::MyIpAdd() { buf = static_cast<char *>(malloc(1024)); sk = socket(AF_INET, SOCK_STREAM, 0); if (sk==0) { if (DEBUG) cout << "Socket error" << endl; exit(-1); } if (DEBUG) cout << "Socket creation OK" << endl; host = gethostbyname(addr.c_str()); if (host == nullptr ) { if (DEBUG) cout << "Bad host '" << addr << "'" << endl; exit(-3); } addrin.sin_family = AF_INET; addrin.sin_port = htons(80); addrin.sin_addr.s_addr = *(reinterpret_cast<unsigned int*>(host->h_addr)); if (connect(sk, reinterpret_cast<struct sockaddr *>(&addrin), sizeof(addrin))<0){ if (DEBUG) cout << "Connection Failed" << endl; exit(-2); } if (DEBUG) cout << "Connection OK" << endl; string msg = "GET / HTTP/1.1\r\nHost: " + addr + "\r\nConnection: close\r\n\r\n"; send(sk, msg.c_str(), msg.length(), 0); ssize_t ret = 0; string html; while((ret = read( sk , buf, sizeof(buf)))>0) { *(buf+ret) = 0; html += buf; } list<string> cont; split(html,cont,'\n'); size_t r = 0; for(auto& line : cont) { if (line.length()>20) { if ((r = line.find("Current IP Address:"))!=string::npos) { string a = line.substr(r+20); a = a.substr(0,a.find("<")); if (DEBUG) cout << a << endl; ip = a; } } } close(sk); }

可以看到里面调用了exit函数:

host = gethostbyname(addr.c_str()); if (host == nullptr ) { if (DEBUG) cout << "Bad host '" << addr << "'" << endl; exit(-3); }

也就是说,当解析默认域名:checkip.dyndns.com 失败时,MyIpAdd 构造函数会直接 exit(-3),从而把整个调用MyIpAdd函数的进程退出。MyIpAdd 构造函数会把DNS/socket/connect失败当成致命错误,直接退出整个进程。

五、修复MyIpAdd源码里的退出bug

修改 MyIpAdd 源码,把MyIpAdd::MyIpAdd() 里的 exit(...) 改成“失败但不退出进程”的形式,然后重新编译 libMyIpAdd.so,就能解决调用MyIpAdd的进程退出问题。

比如修改为:

if (host == nullptr) { ip = ""; return; }

或者更完整一点:

if (host == nullptr) { if (buf) free(buf); if (sk > 0) close(sk); ip = ""; return; }

修复后,整个MyIpAdd::MyIpAdd()函数改为:

MyIpAdd::MyIpAdd() { sk = socket(AF_INET, SOCK_STREAM, 0); if (sk < 0) { if (DEBUG) cout << "Socket error" << endl; return; } if (DEBUG) cout << "Socket creation OK" << endl; host = gethostbyname(addr.c_str()); if (host == nullptr || host->h_addr == nullptr) { if (DEBUG) cout << "Bad host '" << addr << "'" << endl; close(sk); sk = -1; return; } addrin.sin_family = AF_INET; addrin.sin_port = htons(80); addrin.sin_addr.s_addr = *(reinterpret_cast<unsigned int*>(host->h_addr)); if (connect(sk, reinterpret_cast<struct sockaddr *>(&addrin), sizeof(addrin))<0){ if (DEBUG) cout << "Connection Failed" << endl; close(sk); sk = -1; return; } if (DEBUG) cout << "Connection OK" << endl; string msg = "GET / HTTP/1.1\r\nHost: " + addr + "\r\nConnection: close\r\n\r\n"; if (send(sk, msg.c_str(), msg.length(), 0) < 0) { if (DEBUG) cout << "Send Failed" << endl; close(sk); sk = -1; return; } ssize_t ret = 0; string html; char read_buf[1024]; while((ret = read(sk, read_buf, sizeof(read_buf))) > 0) { html.append(read_buf, ret); } close(sk); sk = -1; list<string> cont; split(html,cont,'\n'); size_t r = 0; for(auto& line : cont) { if (line.length()>20) { if ((r = line.find("Current IP Address:"))!=string::npos) { string a = line.substr(r+20); a = a.substr(0,a.find("<")); if (DEBUG) cout << a << endl; ip = a; } } } }

六、反思

我在某个项目中为了获取公网ip使用了MyIpAdd::MyIpAdd库。但是它的源码内部有异常退出的bug导致我的程序一直偶发挂掉。所以这给我的启发是:1.尽量用多人用的开源库,小众的开源库没什么人用往往有很多bug;2.一定要有能阅读开源库代码的能力,这样万一开源库出现问题的时候你才能修改这些bug。