大家好,又见面了,我是你们的朋友全栈君。
很多朋友的公司或家里有一台上网的机器,这些上网的机器有些能够获得公网IP,但是这些IP通常不固定。
大家都想充分利用这些上网设备的网络能力来搭建服务器环境,但由于IP地址老是变化,因此,即使是给这些机器分配了域名,也时常无法访问。于是,很多人想到了动态域名解析,即域名不变,IP地址变化,域名解析记录能够跟随IP地址变化,目前市场上有几种商业的解析方案实现,例如花生壳,更多的就不举例了,避免给他们做免费广告。这些都要收费,而且可能要通过CNAME(将您的域名解析成别人的域名)方式来解决,解析效率略有降低。
好在阿里云的开放精神,他们将域名解析的接口提供了给大家,经过笔者测试非常好用。
本文将实现自己的免费动态域名解析实现分享出来,实现思路如下:
1)首先有一台公网的固定域名的服务器,运行一个助手程序(getipd),来帮助获得动态IP主机的当前IP地址(一般几天变化一次);
2)在动态IP的机器上(或者跨越该路由器的内部网络主机)运行PHP编写的客户端,PHP编写的客户端定期与公网那个运行getipd的服务器通信,一般10秒一次,获得自己的公网IP;
3)客户端程序判断,如果自己的公网IP发生变化,则调用阿里云的接口来更改域名(A记录),阿里云的DNS动态解析真的非常快,一般是实时生效的。
一、PHP客户端的实现所有源代码如下(完整实现 dnsupdater.php,不依赖任何第三方库):
<?phpdate_default_timezone_set('UTC'); set_time_limit(0);$iphelper_addr = 'www.iavcast.com';$iphelper_port = '8198';$AccessKeyId = 'your AccessKeyId';$AccessKeySecret = 'your AccessKeySecret';$domain_list = Array('video.yourdomain.com','www.yourdomain.com','video.yourdomain.net','file.yourdomain.net');// $show_log = False;$old_gateway_ip = ''; $options = getopt("d:h:");if (count($options) > 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 可以修改,为了能够快速测试,可以暂时用 www.iavcast.com 网站提供的,请仅作为临时测试使用,正式使用时请搭建自己的服务器端。
$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控制台窗口的助手程序,官方网址是:
https://github.com/wenshui2008/RunHiddenConsole
dnsupdater.php 代码完全可以运行在linux上,在linux系统的shell里输入:
php dnsupdater.php &
可以作为守护进程长期运行。
二、getipd服务器端程序,这是一个用于帮助获取公网IP地址的极其简单的TCP服务器,笔者用C语言与PHP分别实现了一份,用PHP实现的 getip.php(仅仅一个文件)如下,请用PHP解释器执行:
<?phpdate_default_timezone_set('UTC'); $port = 8198; $sock_srv = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($sock_srv, SOL_SOCKET, SO_REUSEADDR, 1); if (!socket_bind($sock_srv, 0, $port)) { die('Bind Port '.$port ." Failed\n");} if (!socket_listen($sock_srv,5)) { die('Socket listen Failed On Port '.$port ."\n");} $clients = array($sock_srv); echo "IPHelper Service running on port ".$port ." ...\n"; while(true) { $readSet = $clients; $writeSet = null; $expectSet = null; $number = socket_select($readSet, $writeSet, $expectSet, 10); if($number === False) { echo "Select error:".socket_strerror(socket_last_error()) ."\n"; continue; } else if($number === 0) { //echo "Socket select nothing\n"; continue; } //echo count($readSet).':'.count($writeSet).':'.count($expectSet)."\n"; if(in_array($sock_srv, $readSet)) { $clients[] = $newsock = socket_accept($sock_srv); if (! @socket_getpeername($newsock, $ip) ) { echo "socket_getpeername Failed\n"; $ip = '0.0.0.0'; } echo "New client $ip arrived!\n"; $key = array_search($sock_srv, $readSet); unset($readSet[$key]); } foreach ($readSet as $read_sock) { $data = @socket_read($read_sock, 1024); if($data === false || empty($data) ) { $key = array_search($read_sock, $clients); if (! @socket_getpeername($clients[$key], $ip)) { echo "socket_getpeername Failed\n"; $ip = '0.0.0.0'; } socket_close($read_sock); unset($clients[$key]); echo "client $ip disconnected\n"; continue; } if (! @socket_getpeername($read_sock, $clientIp)) { //Never arrive here! $clientIp = '0.0.0.0'; } $response = Array(); $response['ip'] = $clientIp; $data = trim($data); if(!empty($data)) { echo 'Client:'.$clientIp ." login-data: ". $data."\n"; } else { echo "nothing to read\n"; } $responseS = json_encode($response,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); socket_write($read_sock, $responseS); //wait for reponse sending Sleep(1); $key = array_search($read_sock, $clients); socket_close($read_sock); unset($clients[$key]); }} socket_close($sock_srv);
在linux里输入 php getip.php & 即可执行。
用C语言实现的getip.c代码如下,需要编译成可执行程序:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/types.h>#ifdef WIN32#include <WinSock2.h>#include <WS2tcpip.h>#else#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#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 512 int fd_A[BACKLOG]; // accepted connection fdint conn_amount = 0; // current connection amount void 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#endif char 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 #endif void 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<NOFILE;i++) close(i); chdir(g_cur_exe_path); umask(0); return;}#endif #ifndef WIN32#define DAEMON_HINT "\t-d Running as a daemon process.\n"#else#define DAEMON_HINT ""#endif void Usage() { const char *progname = "getipd"; const char *debug = "";#ifdef WIN32 debug = "-debug ";#endif fprintf(stderr,"Usage:\n%s %s-r <directory> -p <port> -l <logfile> -t <TemplateFileDir> -c <ConfigurationFile> \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端口,请注意修改防火墙的规则,打开此端口。
至此,一个属于自己的高效的动态域名解析系统就完成了。
所有代码可以在
https://github.com/wenshui2008/dnsupdater
下载。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/137403.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...