个人电脑也能做服务器

一般家庭网络是没有固定 ip 的,这就导致了外部无法方便地访问家里电脑上的文件或网络服务。花生壳等内网穿透工具免费的又不好使,收费的又划不来,怎么办?今天我来给大家分享一个方法,轻松搞定。

准备

  • 阿里云账号
  • 阿里云域名
  • 一台路由器

思路

阿里云解析DNS提供接口可以让我们通过程序更新域名设置,这是核心要点。查看文档
另外我们还需要知道家里的IP地址。
然后只要我们根据运营商跟换ip的频率(一般一天换一个)定时执行任务,进行更新域名指向,这样就能实现只要访问固定域名就能指向指定IP了。
然后再设置一下路由器,映射到内网ip地址(即提供网路服务的主机地址),可以使用树莓派、老旧电脑或者NAS作为主机,搭建自己的博客或者提供简单的网络服务。
大功告成!

widows 可以利用计划任务,Linux 可以利用 crontab,来完成定时任务。

话不多说,都在代码里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import logging
import requests
import hmac
import random
import string
from datetime import datetime, timezone
import base64

logger = logging.getLogger('test')
logger.setLevel(logging.DEBUG)

# 控制台输出
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)

logger.addHandler(ch)


class DynamicRouter:
    """ 利用阿里云解析接口,将域名动态解析到指定 ip

    适用于家庭宽带的IP是公网IP的小伙伴,配合路由器的转发功能对外提供服务。类似花生壳的功能。
    注意:80 端口一般被运营商封掉,在可以尝试其他端口。
    """

    def __init__(self, access_key_id, access_key_secret):
        self.APP_ID = access_key_id
        self.APP_SECRET = access_key_secret
        self.base_host = "https://alidns.aliyuncs.com/"
        self._common_req_params = {
            "Format": "JSON",
            "Version": "2015-01-09",
            "AccessKeyId": self.APP_ID,
            "SignatureMethod": "HMAC-SHA1",
            "Timestamp": datetime.now(tz=timezone.utc).isoformat().split('.')[0] + 'Z',
            "SignatureVersion": "1.0",
            "SignatureNonce": ''.join([random.choice(string.ascii_letters + string.digits) for i in range(10)]),
        }

    def get_signature(self, **kwargs) -> bytes:
        """ 根据规则生成签名

        :return  signature
        """
        signature_string_base = 'POST' + '&' + '%2F' + '&'
        signature_string_to_params = ''
        for key in sorted(kwargs):
            if key == "Timestamp":
                kwargs[key] = kwargs[key].replace(':', '%253A')
            signature_string_to_params += f'{key}%3D{kwargs[key]}%26'
        signature_string_to_sign = signature_string_base + signature_string_to_params.rstrip('%26')

        signature = hmac.new(key=(self.APP_SECRET + '&').encode(), msg=signature_string_to_sign.encode(),
                             digestmod="SHA1").digest()
        return base64.b64encode(signature)

    def get_record_id(self, domain_name: str, rr: str) -> str:
        """ 获取该域名下二级域名的解析记录 id

        :param domain_name:域名名称
        :param rr:主机记录(二级域名,若没有,默认为'@')
        :return record_id or None
        """
        data = {}
        data.update(self._common_req_params)
        data.update({'Action': 'DescribeDomainRecords', "DomainName": domain_name})
        signature = self.get_signature(**data)
        data['Signature'] = signature
        res = requests.post(self.base_host, data=data).json()

        records = res['DomainRecords']['Record']
        for i in records:
            if i['RR'] == rr:
                return i['RecordId']
        else:
            logger.error('未获取到解析记录,检查参数是否正确。')
            raise Exception("can't get value")

    def update_domain_record(self, record_id: str, rr, value: str, ttl: int = 600, rtype: str = 'A') -> None:
        """ 更新解析记录为 record_id 的设置,包括但不限于ip地址、主机记录、解析类型等

        :param record_id : 解析记录
        :param rr : 主机记录(二级域名)
        :param value : 解析记录(若是 记录类型 A,则为 ip地址)
        :param ttl:解析生效时间
        :param rtype:记录类型(默认为'A',即跳转到 ip4 地址)
        :return None
        """
        data = {
            'Action': 'UpdateDomainRecord',
            'RecordId': record_id,
            "RR": rr,
            "Type": rtype,
            "Value": value,
            "TTL": ttl
        }
        data.update(self._common_req_params)

        signature = self.get_signature(**data)
        data['Signature'] = signature
        res = requests.post(self.base_host, data=data)
        logger.info("更新域名记录结果:" + res.text)
        return None

    @staticmethod
    def get_ip():
        """
        获取本机外网ip
        接口是利用阿里云函数计算服务(有免费额度)提供,比爬取网络获取方便。
        """
        req_url = 'https://1142905836248003.cn-shanghai.fc.aliyuncs.com/2016-08-15/proxy/musicApi/get_ip/'
        return requests.get(req_url).text


if __name__ == "__main__":
    eg = DynamicRouter(access_key_id='your-appid', access_key_secret='your-secret')
    record_id = eg.get_record_id('your-DomainName', 'your-RR')
    eg.update_domain_record(record_id=record_id, rr='blog', value=eg.get_ip())