博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于阿里云Aliddns动态域名解析的客户端PHP实现与服务器端(包含C与PHP)实现
阅读量:2222 次
发布时间:2019-05-08

本文共 14710 字,大约阅读时间需要 49 分钟。

       很多朋友的公司或家里有一台上网的机器,这些上网的机器有些能够获得公网IP,但是这些IP通常不固定。

       大家都想充分利用这些上网设备的网络能力来搭建服务器环境,但由于IP地址老是变化,因此,即使是给这些机器分配了域名,也时常无法访问。于是,很多人想到了动态域名解析,即域名不变,IP地址变化,域名解析记录能够跟随IP地址变化,目前市场上有几种商业的解析方案实现,例如花生壳,更多的就不举例了,避免给他们做免费广告。这些都要收费,而且可能要通过CNAME(将您的域名解析成别人的域名)方式来解决,解析效率略有降低。

       好在阿里云的开放精神,他们将域名解析的接口提供了给大家,经过笔者测试非常好用。

       本文将实现自己的免费动态域名解析实现分享出来,实现思路如下:

      1)首先有一台公网的固定域名的服务器,运行一个助手程序(getipd),来帮助获得动态IP主机的当前IP地址(一般几天变化一次);

      2)在动态IP的机器上(或者跨越该路由器的内部网络主机)运行PHP编写的客户端,PHP编写的客户端定期与公网那个运行getipd的服务器通信,一般10秒一次,获得自己的公网IP;

      3)客户端程序判断,如果自己的公网IP发生变化,则调用阿里云的接口来更改域名(A记录),阿里云的DNS动态解析真的非常快,一般是实时生效的。

一、PHP客户端的实现所有源代码如下(完整实现 dnsupdater.php,不依赖任何第三方库):

0) { if (!empty($options['d'])) $show_log = True;}if (!function_exists('random_int')) { //php 5.x compatible function random_int($min,$max) { return mt_rand($min,$max); }}/** * Class AlicloudDNSUpdater */class AlicloudDNSUpdater { /** * @var string */ public $domainName; /** * @var string */ public $rR; /** * @var string */ public $type; /** * @var string */ public $value; /** * @var string */ public $accessKeyId; /** * @var string */ public $accessKeySecret; /** * AlicloudUpdateRecord constructor. * * @param string $accessKeyId * @param string $accessKeySecret */ function __construct( $accessKeyId, $accessKeySecret ) { $this->accessKeyId = $accessKeyId; $this->accessKeySecret = $accessKeySecret; } /** * @param string $CanonicalQueryString * @return string */ public function getSignature($CanonicalQueryString) { $HTTPMethod = 'GET'; $slash = urlencode('/'); $EncodedCanonicalQueryString = urlencode($CanonicalQueryString); $StringToSign = "{$HTTPMethod}&{$slash}&{$EncodedCanonicalQueryString}"; $StringToSign = str_replace('%40', '%2540', $StringToSign); $HMAC = hash_hmac('sha1', $StringToSign, "{$this->accessKeySecret}&", true); return base64_encode($HMAC); } /** * @return string */ public function getDate() { $timestamp = date('U'); $date = date('Y-m-d', $timestamp); $H = date('H', $timestamp); $i = date('i', $timestamp); $s = date('s', $timestamp); return "{$date}T{$H}%3A{$i}%3A{$s}"; } /** * @return string * @throws Exception */ public function getRecordId() { $queries = [ 'AccessKeyId' => $this->accessKeyId, 'Action' => 'DescribeDomainRecords', 'DomainName' => $this->domainName, 'Format' => 'json', 'SignatureMethod' => 'HMAC-SHA1', 'SignatureNonce' => random_int(1000000000, 9999999999), 'SignatureVersion' => '1.0', 'Timestamp' => $this->getDate(), 'Version' => '2015-01-09' ]; $response = $this->doRequest($queries); if (!isset($response['DomainRecords'])) { return ''; } $recordList = $response['DomainRecords']['Record']; $RR = null; foreach ($recordList as $key => $record) { if ($this->rR === $record['RR']) { $RR = $record; } } if ($RR === null) { //die('RR ' . $this->rR . ' not found.'); return ''; } return $RR['RecordId']; } /** * @param string $domainName */ public function setDomainName($domainName) { $this->domainName = $domainName; } /** * @param string $value */ public function setValue($value) { $this->value = $value; } /** * @param string $rR */ public function setRR($rR) { $this->rR = $rR; } /** * @param string $recordId */ public function setRecordId($recordId) { $this->recordId = $recordId; } /** * @param string $type */ public function setRecordType($type) { $this->type = $type; } /** * @param array $queries * @return array */ public function doRequest($queries) { $CanonicalQueryString = ''; $i = 0; foreach ($queries as $param => $query) { $CanonicalQueryString .= $i === 0 ? null : '&'; $CanonicalQueryString .= "{$param}={$query}"; $i++; } $signature = $this->getSignature($CanonicalQueryString); $requestUrl = "http://dns.aliyuncs.com/?{$CanonicalQueryString}&Signature=" . urlencode($signature); $response = file_get_contents($requestUrl, false, stream_context_create([ 'http' => [ 'ignore_errors' => true ] ])); return json_decode($response, true); } /** * @return array * @throws \Exception */ public function sendRequest() { $RecordId = $this->getRecordId(); if (empty($RecordId)) { return Array( 'Code'=>'Error', 'Message'=>$this->domainName .' record not found' ); } $queries = [ 'AccessKeyId' => $this->accessKeyId, 'Action' => 'UpdateDomainRecord', 'Format' => 'json', 'RR' => $this->rR, 'RecordId' => $RecordId, 'SignatureMethod' => 'HMAC-SHA1', 'SignatureNonce' => random_int(1000000000, 9999999999), 'SignatureVersion' => '1.0', 'Timestamp' => $this->getDate(), 'Type' => $this->type, 'Value' => $this->value, 'Version' => '2015-01-09' ]; return $this->doRequest($queries); } public function sendAddRequest() { $queries = [ 'AccessKeyId' => $this->accessKeyId, 'Action' => 'AddDomainRecord', 'Format' => 'json', 'RR' => $this->rR, 'Type' => $this->type, 'Value' => $this->value, 'DomainName' => $this->domainName, 'SignatureMethod' => 'HMAC-SHA1', 'SignatureNonce' => random_int(1000000000, 9999999999), 'SignatureVersion' => '1.0', 'Timestamp' => $this->getDate(), 'Version' => '2015-01-09' ]; return $this->doRequest($queries); }}while(true) { $client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $result = @socket_connect($client, $iphelper_addr, $iphelper_port); if (!$result) { if ($show_log) { echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error($client)) . "\n"; } socket_close($client); } else { $login_info = Array( 'server'=>'server 1', 'time'=>time() ); socket_write($client, json_encode($login_info)); $response = @socket_read($client, 1024); if ($response !== False && !empty($response) ) { $info = json_decode($response,True); if (is_array($info) && isset($info['ip'])) { if ($old_gateway_ip != $info['ip']) { if ($show_log) { echo date('Y-m-d H:i:s'). " do refresh dns ip :".$info['ip']."\n"; } $updater = new AlicloudDNSUpdater($AccessKeyId, $AccessKeySecret); foreach($domain_list as $domain) { $dotpos = strpos($domain,'.'); if ($dotpos !== False) { $recoreName = substr($domain,0,$dotpos); $domainName = substr($domain,$dotpos + 1); if ($show_log) { echo 'Update DNS Record:'.$recoreName.'.'.$domainName .' -> '. $info['ip'] ."...\n"; } $updater->setDomainName($domainName); $updater->setRecordType('A'); $updater->setRR($recoreName); $updater->setValue($info['ip']); $result = $updater->sendRequest(); if ($show_log) { print_r($result); echo "\n"; } } } $old_gateway_ip = $info['ip']; } else { if ($show_log) { echo "IP:{$old_gateway_ip} keep!\n"; } } } } socket_close($client); } Sleep(10);}

其中:$AccessKeyId ,$AccessKeySecret 是阿里云分配给你的,只要您能够登录阿里云的控制台即可获取。获取位置如下:

公网IP地址获取服务的主机地址 $iphelper_addr 可以修改,为了能够快速测试,可以暂时用  网站提供的,请仅作为临时测试使用,正式使用时请搭建自己的服务器端。

$domain_list 为需要刷新的IP地址列表,请先在阿里云的控制台的域名解析操作页面添加初始化解析记录,例如www.domain.com,live.domain.com 等,添加解析记录时的IP地址可以是任何值,以后dnsupdater.php会修改这个值的。

dnsupdater.php 下载下来,并设置好必要的$AccessKeyId ,$AccessKeySecret 变量,假设PHP解释器安装在C:\PHP7\php.exe,运行 如下命令即可:

C:\PHP7\php.exe dnsupdater.php 

请用php5.6以上运行本客户端。PHP需要开启sockets扩展,即去掉php.ini里的如下行的注释(去掉分号)

extension=php_sockets.dll

如果想让dnsupdater.php 在后台运行,请用RunHiddenConsole.exe,这是一个用于隐藏Windows控制台窗口的助手程序,官方网址是:

dnsupdater.php 代码完全可以运行在linux上,在linux系统的shell里输入:

 php dnsupdater.php &

可以作为守护进程长期运行。

二、getipd服务器端程序,这是一个用于帮助获取公网IP地址的极其简单的TCP服务器,笔者用C语言与PHP分别实现了一份,用PHP实现的 getip.php(仅仅一个文件)如下,请用PHP解释器执行:

在linux里输入 php getip.php & 即可执行。

用C语言实现的getip.c代码如下,需要编译成可执行程序

#include 
#include
#include
#include
#include
#ifdef WIN32#include
#include
#else#include
#include
#include
#include
#define closesocket(s) close(s)#endif#define MYPORT 8198 // the port users will be connecting to#define BACKLOG 5 // how many pending connections queue will hold#define BUF_SIZE 512int fd_A[BACKLOG]; // accepted connection fdint conn_amount = 0; // current connection amountvoid showclient(){ int i; printf("client amount: %d\n", conn_amount); for (i = 0; i < BACKLOG; i++) { printf("[%d]:%d ", i, fd_A[i]); } printf("\n\n");}const char * g_app_dir = NULL;const char * g_exe_name = "getipd";volatile long g_b_exit_server = 0;int g_web_root_len = 4;int g_app_dir_len = 0;#ifdef _WIN32#define PATH_DEL '\\'#define PTHREAD_INITIALIZED {0,0}#else#define PATH_DEL '/'#define PTHREAD_INITIALIZED 0#endif#undef MAX_PATH #ifndef MAX_PATH#define MAX_PATH 1024#endifchar g_cur_exe_path[MAX_PATH];void getExePath(){ char * pch;#ifdef _WIN32 GetModuleFileNameA(NULL,g_cur_exe_path,ARRAYSIZE(g_cur_exe_path)); pch = strrchr(g_cur_exe_path,'\\'); pch ++ ; *pch = '\0';#else int cnt = readlink("/proc/self/exe", g_cur_exe_path, MAX_PATH); if (cnt < 0 || cnt >= MAX_PATH) { strcpy(g_cur_exe_path,"/usr/local/"); } pch = strrchr(g_cur_exe_path,'/'); if (pch) { pch ++; *pch = 0; }#endif g_app_dir = g_cur_exe_path; g_app_dir_len = strlen(g_app_dir);}#ifdef _WIN32void init_daemon() {};#else#ifndef NOFILE #define NOFILE 3 #endifvoid init_daemon(){ int pid; int i; pid=fork(); if(pid<0) exit(1); else if(pid>0) exit(0); setsid(); pid=fork(); if(pid>0) exit(0); else if(pid<0) exit(1); for(i=0;i
-p
-l
-t
-c
\n" "Parameters:\n" "\t-p tcp port,default: [%s]\n" DAEMON_HINT, progname,debug,MYPORT ); exit(1);}int main(int argc,char * argv[]){ int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd struct sockaddr_in server_addr; // server address information struct sockaddr_in client_addr; // connector's address information socklen_t sin_size; int yes = 1,b_daemon = 0; char buf[BUF_SIZE]; int ret; int i; fd_set fdsr; int maxsock,remove_count; struct timeval tv; unsigned short port = MYPORT;#ifdef WIN32 WSADATA wsaData; WORD wVersionRequested; wVersionRequested =MAKEWORD( 1, 1 ); ret = WSAStartup( wVersionRequested, &wsaData ); if ( ret != 0 ) { /* Tell the user that we couldn't find a useable */ /* winsock.dll. */ exit(1); }#endif getExePath(); g_exe_name = strrchr(argv[0],PATH_DEL); if (g_exe_name) { g_exe_name ++; } else { g_exe_name = argv[0]; } /* Parse command line arguments */ for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-p") == 0) { port = atoi(argv[++i]); } else if (!strcmp(argv[i],"-d")) { b_daemon = 1; break; } else if (!strcmp(argv[i],"-?") || !strcmp(argv[i],"-h")) { Usage(); break; } } if (b_daemon) { init_daemon(); } if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } server_addr.sin_family = AF_INET; // host byte order server_addr.sin_port = htons(MYPORT); // short, network byte order server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero)); if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } if (listen(sock_fd, BACKLOG) == -1) { perror("listen"); exit(1); } printf("listen port %d\n", MYPORT); conn_amount = 0; sin_size = sizeof(client_addr); maxsock = sock_fd; memset(fd_A,0,sizeof (fd_A)); while (1) { // timeout setting tv.tv_sec = 30; tv.tv_usec = 0; // initialize file descriptor set FD_ZERO(&fdsr); FD_SET(sock_fd, &fdsr); // add active connection to fd set for (i = 0; i < BACKLOG; i++) { if (fd_A[i] != 0) { FD_SET(fd_A[i], &fdsr); } } ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv); if (ret < 0) { perror("select"); break; } else if (ret == 0) { printf("timeout\n"); continue; } remove_count = 0; // check every fd in the set for (i = 0; i < conn_amount; i++) { if (FD_ISSET(fd_A[i], &fdsr)) { ret = recv(fd_A[i], buf, sizeof(buf), 0); if (ret <= 0) { // client close printf("client[%d] close\n", i); closesocket(fd_A[i]); FD_CLR(fd_A[i], &fdsr); fd_A[i] = 0; remove_count ++; } else { // receive data int ret2; char ipAddr[128]; if (ret < BUF_SIZE) memset(&buf[ret], '\0', 1); printf("client[%d] send:%s\n", i, buf); sin_size = sizeof(client_addr); ret2 = getpeername(fd_A[i],(struct sockaddr *)&client_addr, &sin_size); if (ret2 == 0) { int len = sprintf(buf,"{\"ip\":\"%s\"}", inet_ntop(AF_INET, &client_addr.sin_addr, ipAddr, sizeof(ipAddr))); send(fd_A[i], buf, len, 0); remove_count ++; closesocket(fd_A[i]); FD_CLR(fd_A[i], &fdsr); fd_A[i] = 0; } } } } //resort the socket if (remove_count > 0) { int j=0; for (i = 0; i < conn_amount; i++) { if (fd_A[i]) { fd_A[j] = fd_A[i]; j++; } } for (i = j; i < conn_amount; i++) { fd_A[i] = 0; } conn_amount -= remove_count; } // check whether a new connection comes if (FD_ISSET(sock_fd, &fdsr)) { new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size); if (new_fd <= 0) { perror("accept"); continue; } // add to fd queue if (conn_amount < BACKLOG) { char ipAddr[128]; fd_A[conn_amount++] = new_fd; printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntop(AF_INET, &client_addr.sin_addr, ipAddr, sizeof(ipAddr)), ntohs(client_addr.sin_port)); if (new_fd > maxsock) maxsock = new_fd; } else { printf("max connections arrived, exit\n"); send(new_fd, "bye", 3, 0); closesocket(new_fd); break; } } showclient(); } // close other connections for (i = 0; i < conn_amount; i++) { if (fd_A[i] != 0) { closesocket(fd_A[i]); } }#ifdef WIN32 WSACleanup();#endif exit(0);}

getip.c 如果要在Windows下编译请用VC新建一个简单项目,添加此文件即可;

在linux下编译请用:

 gcc -O2 -o getipd getip.c

在linux下通过以上命令编译后,输入 ./getipd -d 即以守护进程的方式运行。getipd 用到了8198端口,请注意修改防火墙的规则,打开此端口。

至此,一个属于自己的高效的动态域名解析系统就完成了。

所有代码可以在

下载。

 

 

 

转载地址:http://xuvfb.baihongyu.com/

你可能感兴趣的文章
【C语言】简单的了解递归(求斐波那契,n的阶乘,字符串长度,把一个整型(无符号),转化为字符型并打印出来)
查看>>
【数据结构】动态栈的实现
查看>>
【数据结构】简单的迷宫(用递归实现)
查看>>
【数据结构】队列的基本认识和队列的基本操作
查看>>
【数据结构】循环队列的认识和基本操作
查看>>
【LeetCode】无重复字符的最长子串
查看>>
时间复杂度
查看>>
【C++】动态内存管理 new和delete的理解
查看>>
【Linux】了解根目录下每个文件的作用
查看>>
【Linux】进程的理解(一)
查看>>
【Linux】进程的理解(二)
查看>>
【C语言】深度理解函数的调用(栈帧)
查看>>
【Linux】进程的理解(三)
查看>>
【C++】带头节点的双向线链表的实现
查看>>
【C++】STL -- Vector容器的用法
查看>>
【Linux】Linux中的0644 和 0755的权限
查看>>
【数据结构】有关二叉树的面试题
查看>>
【Linux】内核态和用户态
查看>>
【Linux】HTTP的理解
查看>>
【Linux】HTTPS的理解
查看>>