|
|
|
|
移动端

使用Python一步步搭建自己的区块链

你是否会和我一样,对加密数字货币底层的区块链技术非常感兴趣,特别想了解他们的运行机制。我喜欢在实践中学习,尤其喜欢一代码为基础去了解整个工作机制。如果你我一样喜欢这种学习方式,当你学完本教程时,你将会知道区块链技术是如何工作的。

作者:佚名来源:segmentfault|2018-04-23 14:23

人工智能+区块链的发展趋势及应用调研报告


使用Python一步步搭建自己的区块链

你是否会和我一样,对加密数字货币底层的区块链技术非常感兴趣,特别想了解他们的运行机制。

但是学习区块链技术并非一帆风顺,我看多了大量的视频教程还有各种课程,最终的感觉就是真正可用的实战课程太少。

我喜欢在实践中学习,尤其喜欢一代码为基础去了解整个工作机制。如果你我一样喜欢这种学习方式,当你学完本教程时,你将会知道区块链技术是如何工作的。

写在开始之前

记住,区块链是一个 不可变的、有序的 被称为块的记录链。它们可以包含交易、文件或任何您喜欢的数据。但重要的是,他们用哈希 一起被链接在一起。

如果你不熟悉哈希, 这里是一个解释。

该指南的目的是什么? 你可以舒服地阅读和编写基础的Python,因为我们将通过HTTP与区块链进行讨论,所以你也要了解HTTP的工作原理。

我需要准备什么? 确定安装了 Python 3.6+ (还有 pip) ,你还需要安装 Flask、 Requests 库:

`pip install Flask==0.12.2 requests==2.18.4`

对了, 你还需要一个支持HTTP的客户端, 比如 Postman 或者 cURL,其他也可以。

源码在哪儿? 可以点击这里

Step 1: 创建一个区块链

打开你最喜欢的文本编辑器或者IDE, 我个人比较喜欢 PyCharm. 新建一个名为blockchain.py的文件。 我们将只用这一个文件就可以。但是如果你还是不太清楚, 你也可以参考 源码.

描述区块链

我们要创建一个 Blockchain 类 ,他的构造函数创建了一个初始化的空列表(要存储我们的区块链),并且另一个存储交易。下面是我们这个类的实例:

blockchain.py

  1. class Blockchain(object):  
  2.     def __init__(self):  
  3.         self.chain = []  
  4.         self.current_transactions = []          
  5.  
  6.     def new_block(self):  
  7.         # Creates a new Block and adds it to the chain  
  8.         pass      
  9.  
  10.     def new_transaction(self):  
  11.         # Adds a new transaction to the list of transactions  
  12.         pass      
  13.  
  14.     @staticmethod  
  15.     def hash(block):  
  16.         # Hashes a Block  
  17.         pass   
  18.  
  19.     @property  
  20.     def last_block(self):  
  21.         # Returns the last Block in the chain  
  22.         pass 

我们的 Blockchain 类负责管理链式数据,它会存储交易并且还有添加新的区块到链式数据的Method。让我们开始扩充更多Method

块是什么样的 ?

每个块都有一个 索引,一个 时间戳(Unix时间戳),一个事务列表, 一个 校验(稍后详述) 和 前一个块的散列 。

下面是一个Block的例子 :

blockchain.py

  1. block = {  
  2.     'index': 1,  
  3.     'timestamp': 1506057125.900785,  
  4.     'transactions': [  
  5.         {  
  6.             'sender'"8527147fe1f5426f9dd545de4b27ee00" 
  7.             'recipient'"a77f5cdfa2934df3954a5c7c7da5df1f" 
  8.             'amount': 5,  
  9.         }  
  10.     ],  
  11.     'proof': 324984774000,  
  12.     'previous_hash'"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"  

在这一点上,一个 区块链 的概念应该是明显的 - 每个新块都包含在其内的前一个块的 散列 。 这是至关重要的,因为这是 区块链 不可改变的原因:如果攻击者损坏 区块链 中较早的块,则所有后续块将包含不正确的哈希值。

这有道理吗? 如果你还没有想通,花点时间仔细思考一下 - 这是区块链背后的核心理念

添加交易到区块

我们将需要一个添加交易到区块的方式。我们的 new_transaction()方法的责任就是这个, 并且它非常的简单:

blockchain.py

  1. class Blockchain(object):  
  2.     ...      
  3.  
  4.     def new_transaction(self, sender, recipient, amount):  
  5.         "" 
  6.         Creates a new transaction to go into the next mined Block  
  7.         :param sender: <str> Address of the Sender  
  8.         :param recipient: <str> Address of the Recipient  
  9.         :param amount: <int> Amount  
  10.         :return: <int> The index of the Block that will hold this transaction  
  11.         "" 
  12.  
  13.         self.current_transactions.append({  
  14.             'sender': sender,  
  15.             'recipient': recipient,  
  16.             'amount': amount,  
  17.         })  
  18.  
  19.         return self.last_block['index'] + 1 

new_transaction() 方法添加了交易到列表,它返回了交易将被添加到的区块的索引---讲开采下一个这对稍后对提交交易的用户有用。

创建新的区块

当我们的  Blockchain  被实例化后,我们需要将 创世 区块(一个没有前导区块的区块)添加进去进去。我们还需要向我们的起源块添加一个 证明,这是挖矿的结果(或工作证明)。 我们稍后会详细讨论挖矿。

除了在构造函数中创建 创世 区块外,我们还会补全  new_block() 、  new_transaction()  和 hash() 函数:

blockchain.py

  1. import hashlib  
  2. import json  
  3. from time import time  
  4.  
  5. class Blockchain(object):  
  6.     def __init__(self):  
  7.         self.current_transactions = []  
  8.         self.chain = []  
  9.  
  10.         # 创建创世区块  
  11.         self.new_block(previous_hash=1, proof=100)  
  12.  
  13.     def new_block(self, proof, previous_hash=None):  
  14.         "" 
  15.         创建一个新的区块到区块链中  
  16.         :param proof: <int> 由工作证明算法生成的证明  
  17.         :param previous_hash: (Optional) <str> 前一个区块的 hash 值  
  18.         :return: <dict> 新区块  
  19.         "" 
  20.  
  21.         block = {  
  22.             'index': len(self.chain) + 1,  
  23.             'timestamp'time(),  
  24.             'transactions': self.current_transactions,  
  25.             'proof': proof,  
  26.             'previous_hash': previous_hash or self.hash(self.chain[-1]),  
  27.         } 
  28.  
  29.         # 重置当前交易记录  
  30.         self.current_transactions = []  
  31.  
  32.         self.chain.append(block)  
  33.         return block 
  34.  
  35.     def new_transaction(self, sender, recipient, amount):  
  36.         "" 
  37.         创建一笔新的交易到下一个被挖掘的区块中  
  38.         :param sender: <str> 发送人的地址  
  39.         :param recipient: <str> 接收人的地址  
  40.         :param amount: <int> 金额  
  41.         :return: <int> 持有本次交易的区块索引  
  42.         "" 
  43.         self.current_transactions.append({  
  44.             'sender': sender,  
  45.             'recipient': recipient,  
  46.             'amount': amount,  
  47.         }) 
  48.  
  49.         return self.last_block['index'] + 1  
  50.  
  51.     @property  
  52.     def last_block(self):  
  53.         return self.chain[-1]  
  54.  
  55.     @staticmethod  
  56.     def hash(block):  
  57.         "" 
  58.         给一个区块生成 SHA-256 值  
  59.         :param block: <dict> Block  
  60.         :return: <str>  
  61.         "" 
  62.  
  63.         # 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列  
  64.         block_string = json.dumps(block, sort_keys=True).encode()  
  65.         return hashlib.sha256(block_string).hexdigest() 

上面的代码应该是直白的 --- 为了让代码清晰,我添加了一些注释和文档说明。 我们差不多完成了我们的区块链。 但在这个时候你一定很疑惑新的块是怎么被创建、锻造或挖掘的。

工作量证明算法

使用工作量证明(PoW)算法,来证明是如何在区块链上创建或挖掘新的区块。PoW 的目标是计算出一个符合特定条件的数字,这个数字对于所有人而言必须在计算上非常困难,但易于验证。这是工作证明背后的核心思想。

我们将看到一个简单的例子帮助你理解:

假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc...0。设 x = 5,求 y ?用 Python 实现:

  1. from hashlib import sha256  
  2. x = 5  
  3. y = 0  # We don't know what y should be yet...  
  4. while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0" 
  5.     y += 1  
  6. print(f'The solution is y = {y}'

结果是: y = 21。因为,生成的 Hash 值结尾必须为 0。

  1. hash(5 * 21) = 1253e9373e...5e3600155e860 

在比特币中,工作量证明算法被称为 Hashcash ,它和上面的问题很相似,只不过计算难度非常大。这就是矿工们为了争夺创建区块的权利而争相计算的问题。 通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,就会获得一定数量的比特币奖励(通过交易)。

验证结果,当然非常容易。

实现工作量证明

让我们来实现一个相似 PoW 算法。规则类似上面的例子:

  找到一个数字 P ,使得它与前一个区块的 proof 拼接成的字符串的 Hash 值以 4 个零开头。

blockchain.py

  1. import hashlib 
  2. import json  
  3.  
  4. from time import time  
  5. from uuid import uuid4  
  6.  
  7. class Blockchain(object):  
  8.     ...          
  9.  
  10.     def proof_of_work(self, last_proof):  
  11.         "" 
  12.         Simple Proof of Work Algorithm:  
  13.          - Find a number p' such that hash(pp'contains leading 4 zeroes, where p is the previous p'  
  14.          - p is the previous proof, and p' is the new proof  
  15.         :param last_proof: <int 
  16.         :return: <int 
  17.         "" 
  18.  
  19.         proof = 0  
  20.         while self.valid_proof(last_proof, proof) is False 
  21.             proof += 1 
  22.  
  23.         return proof   
  24.     @staticmethod  
  25.     def valid_proof(last_proof, proof):  
  26.         "" 
  27.         Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?  
  28.         :param last_proof: <int> Previous Proof  
  29.         :param proof: <intCurrent Proof  
  30.         :return: <bool> True if correct, False if not 
  31.         ""
  32.  
  33.         guess = f'{last_proof}{proof}'.encode()  
  34.         guess_hash = hashlib.sha256(guess).hexdigest()  
  35.         return guess_hash[:4] == "0000" 

衡量算法复杂度的办法是修改零开头的个数。使用 4 个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。

现在 Blockchain 类基本已经完成了,接下来使用 HTTP requests 来进行交互。

Step 2: Blockchain 作为 API 接口

我们将使用 Python Flask 框架,这是一个轻量 Web 应用框架,它方便将网络请求映射到 Python 函数,现在我们来让 Blockchain 运行在基于 Flask web 上。

我们将创建三个接口:

  • /transactions/new 创建一个交易并添加到区块
  • /mine 告诉服务器去挖掘新的区块
  • /chain 返回整个区块链

创建节点

我们的“Flask 服务器”将扮演区块链网络中的一个节点。我们先添加一些框架代码:

blockchain.py

  1. import hashlib  
  2. import json  
  3. from textwrap import dedent  
  4. from time import time  
  5. from uuid import uuid4  
  6.  
  7. from flask import Flask  
  8.  
  9. class Blockchain(object):  
  10.     ... 
  11.  
  12. # Instantiate our Node  
  13. app = Flask(__name__)  
  14.  
  15. # Generate a globally unique address for this node  
  16. node_identifier = str(uuid4()).replace('-'''
  17.  
  18.  
  19. # Instantiate the Blockchain  
  20. blockchain = Blockchain()  
  21.  
  22. @app.route('/mine', methods=['GET'])  
  23. def mine():  
  24.     return "We'll mine a new Block" 
  25.  
  26. @app.route('/transactions/new', methods=['POST'])  
  27. def new_transaction():  
  28.     return "We'll add a new transaction"  
  29.  
  30. @app.route('/chain', methods=['GET'])  
  31. def full_chain():  
  32.     response = {  
  33.         'chain': blockchain.chain,  
  34.         'length': len(blockchain.chain),  
  35.     }  
  36.     return jsonify(response), 200  
  37.  
  38. if __name__ == '__main__' 
  39.     app.run(host='0.0.0.0', port=5000) 

简单的说明一下以上代码:

  • 第 15 行:实例化节点。阅读更多关于 Flask 内容。
  • 第 18 行:为节点创建一个随机的名称。.
  • 第 21 行:实例化 Blockchain 类。
  • 第 24--26 行:创建 /mine 接口,GET 方式请求。 
  • 第 28--30 行:创建 /transactions/new 接口,POST 方式请求,可以给接口发送交易数据。
  • 第 32--38 行:创建 /chain 接口,返回整个区块链。
  • 第 40--41 行:服务器运行端口 5000 。

发送交易

发送到节点的交易数据结构如下:

  1.  
  2.  "sender""my address" 
  3.  "recipient""someone else's address" 
  4.  "amount": 5  

因为我们已经有了添加交易的方法,所以基于接口来添加交易就很简单了。让我们为添加事务写函数:

blockchain.py

  1. import hashlib  
  2. import json  
  3. from textwrap import dedent  
  4. from time import time  
  5. from uuid import uuid4  
  6.  
  7. from flask import Flask, jsonify, request  
  8. ...  
  9.  
  10. @app.route('/transactions/new', methods=['POST'])  
  11. def new_transaction():  
  12.     values = request.get_json()  
  13.  
  14.     # Check that the required fields are in the POST'ed data  
  15.     required = ['sender''recipient''amount' 
  16.     if not all(k in values for k in required):  
  17.         return 'Missing values', 400  
  18.  
  19.     # Create a new Transaction  
  20.     index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])  
  21.  
  22.     response = {'message': f'Transaction will be added to Block {index}' 
  23.     return jsonify(response), 201 

挖矿

挖矿正是神奇所在,它很简单,做了一下三件事:

  1. 计算工作量证明 PoW
  2. 通过新增一个交易授予矿工(自己)一个币
  3. 构造新区块并将其添加到链中

blockchain.py

  1. import hashlib  
  2. import json  
  3.  
  4. from time import time  
  5. from uuid import uuid4  
  6.  
  7. from flask import Flask, jsonify, request  
  8. ...   
  9.  
  10. @app.route('/mine', methods=['GET'])  
  11. def mine():  
  12.     # We run the proof of work algorithm to get the next proof...  
  13.     last_block = blockchain.last_block  
  14.     last_proof = last_block['proof' 
  15.     proof = blockchain.proof_of_work(last_proof)  
  16.  
  17.     # We must receive a reward for finding the proof.  
  18.     # The sender is "0" to signify that this node has mined a new coin.  
  19.     blockchain.new_transaction(  
  20.         sender="0" 
  21.         recipient=node_identifier,  
  22.         amount=1,  
  23.     ) 
  24.  
  25.     # Forge the new Block by adding it to the chain  
  26.     previous_hash = blockchain.hash(last_block)  
  27.     block = blockchain.new_block(proof, previous_hash)  
  28.  
  29.     response = {  
  30.         'message'"New Block Forged" 
  31.         'index': block['index'],  
  32.         'transactions': block['transactions'],  
  33.         'proof': block['proof'],  
  34.         'previous_hash': block['previous_hash'],  
  35.     } 
  36.  
  37.     return jsonify(response), 200 

注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕 Blockchain 类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下.

Step 3: 运行区块链

你可以使用 cURL 或 Postman 去和 API 进行交互

启动 Server:

  1. $ python blockchain.py  
  2. * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 

让我们通过请求 http://localhost:5000/mine ( GET )来进行挖矿:

用 Postman 发起一个 GET 请求。

创建一个交易请求,请求 http://localhost:5000/transactions/new (POST),如图

如果不是使用 Postman,则用一下的 cURL 语句也是一样的:

  1. $ curl -X POST -H "Content-Type: application/json" -d '{  
  2.  "sender""d4ee26eee15148ee92c6cd394edd974e" 
  3.  "recipient""someone-other-address" 
  4.  "amount": 5  
  5. }' "http://localhost:5000/transactions/new" 

在挖了两次矿之后,就有 3 个块了,通过请求 http://localhost:5000/chain 可以得到所有的块信息

  1.  
  2.   "chain": [  
  3.     {  
  4.       "index": 1,  
  5.       "previous_hash": 1,  
  6.       "proof": 100,  
  7.       "timestamp": 1506280650.770839,  
  8.       "transactions": [] 
  9.      },  
  10.     {  
  11.       "index": 2,  
  12.       "previous_hash""c099bc...bfb7" 
  13.       "proof": 35293,  
  14.       "timestamp": 1506280664.717925,  
  15.       "transactions": [  
  16.         {  
  17.           "amount": 1,  
  18.           "recipient""8bbcb347e0634905b0cac7955bae152b" 
  19.           "sender""0"  
  20.         }  
  21.       ]  
  22.     },  
  23.     {  
  24.       "index": 3,  
  25.       "previous_hash""eff91a...10f2" 
  26.       "proof": 35089,  
  27.       "timestamp": 1506280666.1086972,  
  28.       "transactions": [  
  29.         {  
  30.           "amount": 1,  
  31.           "recipient""8bbcb347e0634905b0cac7955bae152b" 
  32.           "sender""0"  
  33.         }  
  34.       ]  
  35.     }  
  36.   ],  
  37.   "length": 3  

Step 4: 一致性(共识)

我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法

注册节点

在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:

  • /nodes/register 接收 URL 形式的新节点列表。
  • /nodes/resolve 执行一致性算法,解决任何冲突,确保节点拥有正确的链。

我们修改下 Blockchain 的 init 函数并提供一个注册节点方法:

blockchain.py

  1. ...  
  2. from urllib.parse import urlparse  
  3. ...  
  4.  
  5. class Blockchain(object):  
  6.     def __init__(self):  
  7.         ...  
  8.         self.nodes = set()  
  9.         ...  
  10.  
  11.     def register_node(self, address):  
  12.         "" 
  13.         Add a new node to the list of nodes  
  14.         :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'  
  15.         :return: None 
  16.         "" 
  17.         parsed_url = urlparse(address)  
  18.         self.nodes.add(parsed_url.netloc) 

我们用 set 来储存节点,这是一种避免重复添加节点的简单方法.

实现共识算法

就像先前讲的那样,当一个节点与另一个节点有不同的链时,就会产生冲突。 为了解决这个问题,我们将制定最长的有效链条是最权威的规则。换句话说就是:在这个网络里最长的链就是最权威的。 我们将使用这个算法,在网络中的节点之间达成共识。

blockchain.py

  1. ... 
  2.  
  3. import requests   
  4. class Blockchain(object)  
  5.     ...      
  6.  
  7.     def valid_chain(self, chain): 
  8.  
  9.         "" 
  10.         Determine if a given blockchain is valid  
  11.         :param chain: <list> A blockchain  
  12.         :return: <bool> True if valid, False if not  
  13.         "" 
  14.  
  15.         last_block = chain[0]  
  16.         current_index = 1  
  17.  
  18.         while current_index < len(chain):  
  19.             block = chain[current_index]  
  20.             print(f'{last_block}' 
  21.             print(f'{block}' 
  22.             print("\n-----------\n" 
  23.             # Check that the hash of the block is correct  
  24.             if block['previous_hash'] != self.hash(last_block):  
  25.                 return False  
  26.  
  27.             # Check that the Proof of Work is correct  
  28.             if not self.valid_proof(last_block['proof'], block['proof']):  
  29.                 return False  
  30.  
  31.             last_block = block  
  32.             current_index += 1  
  33.  
  34.         return True  
  35.  
  36.     def resolve_conflicts(self):  
  37.         "" 
  38.         This is our Consensus Algorithm, it resolves conflicts  
  39.         by replacing our chain with the longest one in the network.  
  40.         :return: <bool> True if our chain was replaced, False if not  
  41.         ""
  42.  
  43.         neighbours = self.nodes  
  44.         new_chain = None  
  45.  
  46.         # We're only looking for chains longer than ours  
  47.         max_length = len(self.chain)  
  48.  
  49.         # Grab and verify the chains from all the nodes in our network  
  50.         for node in neighbours:  
  51.             response = requests.get(f'http://{node}/chain' 
  52.  
  53.             if response.status_code == 200:  
  54.                 length = response.json()['length' 
  55.                 chain = response.json()['chain' 
  56.  
  57.                 # Check if the length is longer and the chain is valid  
  58.                 if length > max_length and self.valid_chain(chain):  
  59.                     max_length = length  
  60.                     new_chain = chain  
  61.  
  62.         # Replace our chain if we discovered a new, valid chain longer than ours  
  63.         if new_chain:  
  64.             self.chain = new_chain  
  65.             return True  
  66.         return False 

第一个方法 valid_chain() 负责检查一个链是否有效,方法是遍历每个块并验证散列和证明。

resolve_conflicts() 是一个遍历我们所有邻居节点的方法,下载它们的链并使用上面的方法验证它们。 如果找到一个长度大于我们的有效链条,我们就取代我们的链条。

我们将两个端点注册到我们的API中,一个用于添加相邻节点,另一个用于解决冲突:

blockchain.py

  1. @app.route('/nodes/register', methods=['POST'])  
  2. def register_nodes():  
  3.     values = request.get_json() 
  4.  
  5.     nodes = values.get('nodes' 
  6.     if nodes is None:  
  7.         return "Error: Please supply a valid list of nodes", 400  
  8.     for node in nodes:  
  9.         blockchain.register_node(node)  
  10.  
  11.     response = {  
  12.         'message''New nodes have been added' 
  13.         'total_nodes': list(blockchain.nodes),  
  14.     }  
  15.     return jsonify(response), 201  
  16.  
  17. @app.route('/nodes/resolve', methods=['GET'])  
  18. def consensus():  
  19.     replaced = blockchain.resolve_conflicts()  
  20.  
  21.     if replaced:  
  22.         response = {  
  23.             'message''Our chain was replaced' 
  24.             'new_chain': blockchain.chain  
  25.         }  
  26.     else 
  27.         response = {  
  28.             'message''Our chain is authoritative' 
  29.             'chain': blockchain.chain  
  30.         } 
  31.  
  32.     return jsonify(response), 200 

在这一点上,如果你喜欢,你可以使用一台不同的机器,并在你的网络上启动不同的节点。 或者使用同一台机器上的不同端口启动进程。 我在我的机器上,不同的端口上创建了另一个节点,并将其注册到当前节点。 因此,我有两个节点:http://localhost:5000 和 http://localhost:5001。 注册一个新节点:

然后我在节点 2 上挖掘了一些新的块,以确保链条更长。 之后,我在节点1上调用 GET /nodes/resolve,其中链由一致性算法取代:

这是一个包,去找一些朋友一起,以帮助测试你的区块链。

我希望本文能激励你创造更多新东西。我之所以对数字货币入迷,是因为我相信区块链会很快改变我们看待事物的方式,包括经济、政府、档案管理等。

【编辑推荐】

  1. 程序员也追风口:区块链开发范式
  2. 初识Python: global关键字
  3. Python爬虫抓取智联招聘(基础版)
  4. Python 人气王,JS 比 Java 更受公司青睐
  5. Python面试官问Redis的相关问题,看完这篇文章就够了
【责任编辑:庞桂玉 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

精通JavaScript动态网页编程(实例版)

本书通过大量实例代码,以ECMA-262版本3为基础,结合JavaScript 1.5和JavaScript 5.5,由浅入深、循序渐进地介绍了JavaScript知识要点与编...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊