DHT协议

BitTorrent 使用“分布式哈希表” (DHT) 来存储种子的 peer 信息,且不需要专门的服务器。这样,每一个 peer 都变成一个 tracker。DHT 协议基于 Kademila,且用 UDP 实现。

请注意文档中的术语以免产生误解。
peer 实现了 BitTorrent 协议,既是服务器也是客户端,且监听在 TCP 端口。
node 实现了 DHT 协议,同样既是服务器也是客户端,监听在 UDP 端口。DHT 由 node 和 peer 的位置信息构成。
BitTorrent 客户端有一个 DHT node,用来和 DHT 中的其他 node 通信用,从而获取 peer 的位置。

概述

每一个 node 都有一个全球唯一标识符,称作 node ID。Node ID 是从 160-bit 的空间中随机选取的,这和 BitTorrent 的infohashes 一致。距离参数(Distance metric)用来比较两个 node ID 之间的接近程度。Nodes 必须维护一个包含其他 node 联系信息的路由表。对那些和自身接近的其他 Node ID,路由表中会有跟多细节。所以 DHT 中,接近自己的 ID 有很多,远的 ID 则较少。

在 Kademila 中,距离参数使用异或计算,结果为无符号整形,即

distance(A, B) = | A XOR B |

值越小越接近。

当一个 node 想要获取种子的其他 peer 时,它先计算种子的 infohash 和路由表中已有 nodes 的距离参数。然后向距离种子最近的 node 询问 peer 信息。如果恰巧该 node 有,则返回给询问的 node。否则,该 node 把自己路由表中离种子最近的 node 信息返回。本 node 拿到返回信息之后,不停重复上述步骤直到没有新 node 信息返回。搜索结束时,客户端将自己的 peer 信息返回给离种子最近的 node。

peer 查询的返回值必须包含令牌(token)。如果一个 node 宣称自己拥有的 peer 正在下载种子时,必须使用最近接收到的 token。当本 node 试图下载种子时,被请求的 node 检查 IP 地址对应的 token 是否一致。这么做是为了防止恶意主机从其他主机下载种子。由于 token 仅仅由请求 node 返回给被请求 node,故协议并未规定其实现。token 在分发后一段时间内必须被接受。BitTorrent 使用 IP + secret 的 SHA1 值作为 token,每 5 分钟改变一次,有 10 分钟有效期。

路由表

每一个 node 维护了一个好 node 的路由表。路由表的中 node 被用作查询的起始节点,并返回查询的响应信息。

并非所有 node 是等价的,有些是“好”的,有些则不。大部分 node 能够使用 DHT 协议发送查询和接受响应,但却不能响应其他 node 的查询。所以在路由表中,必须只包含好的 node。一个好的 node 被定义为能在 15 分钟内响应查询,或者 15 分钟内曾经响应过。若 15 钟没响应,则是可疑的。若对一系列请求都失去响应,则为坏的 node。已知的好的 node 拥有更高的优先级。

路由表覆盖从 0 到 2^160 完整的 node ID 空间。路由表又被分为 bucket,每一个拥有一部分子空间。空的表只有一个 bucket,其 ID 范围为 min=0 到 max=2^160。当 ID 为 N 的 node 插入到表中时,它必须放在 min=0 到 max=2^160 之间。由于空的表只有一个 bucket,所有 node 都在该 bucket 中。每个 bucket 可以存放 K 个 node,目前 K <= 8。当 bucket 存满好的 node 时,不允许再插入,除非其本身就在该 bucket 中。在这种情况下,原 bucket 分裂为两个相同大小的 bucket,比如原始 bucket 分裂为 0 到 2^159,2^159 到 2^160。

当 bucket 装满了好的 node,新的 node 会被丢弃。一旦 bucket 中的某个 node 变坏,就会用新的 node 来替换这个坏的 node。如果 bucket 中有在 15 分钟内都没有活跃过的 bucket,则其为可疑的 node,这时我们向最久没有联系的 node 发送 ping。如果有回复,那么我们向下一个可疑的 node 发送 ping,不断这样循环下去,直到有某一个 node 没有给出 ping 的回复,或者当前 bucket 中的所有 node 都是好的 (即都不是可疑节点,且在过去 15 分钟内都有活动)。如果 bucket 中的某个 node 没有对我们的 ping 给出回复,还会再试一次 (再发送一次 ping,也许仍然是活跃的,但由于网络拥塞,所以发生了丢包现象,注意 DHT 的包都是 UDP 的),而不是立即丢弃该 node 或者直接用新 node 来替代它。这样,路由表中永远是活跃的 node。

每个 bucket 都应该维护一个 lastchange 字段来表明自身的”新鲜”程度。当 bucket 中的 node 被 ping 并给出了回复,或者一个 node 被加入到了 bucket,或者有新的 node 替代了旧的,bucket 的 lastchange 字段都应当更新。如果一个 bucket 的 lastchange 在过去的 15 分钟内都没有变化,那么将重更新它。这个重更新为:从这个 bucket 所覆盖的范围中随机选择一个 node ID,并对这个 ID 执行 find_nodes 搜索。请注意,收到请求的 node 通常不需要常常更新自己所在的 bucket。反之,不常常收到请求的 node 才需要周期性地更新所在 bucket。有了上述措施,在 DHT 时,才能有足够多的好的 node。

当插入第一个 node 到路由表并启动时,这个 node 应试着查找 DHT 中离自己最近的 node。这个查找工作是通过不断的发出 find_node 消息给越来越近的 node 来完成的,当没有更近的 node 时,搜索停止。路由表应当由客户端软件保存。

BitTorrent协议扩展

BitTorrent 协议被扩展为可通过 tracker 得到的 peer 之间互换 node 的 UDP 端口号。在这种方式下,当下载种子时,客户端能够自动更新路由表。新安装的客户端在无 tracker 的种子中获取不到路由表,必须在种子文件中找到联系信息。

Peers 如果支持 DHT 协议,会将 BitTorrent 协议握手消息的 8 字节保留位的最后一位置为 1。如果 peer 收到一个握手消息,表明对方支持 DHT 协议,则应该发送 PORT 消息。它由字节 0x09 开始,payload 的长度是 2 个字节,包含了这个 peer 的 DHT 所使用的 UDP 端口号。当 peer 收到这样的消息是应当向对方的 IP 和消息中指定的端口号的节点发送 ping。如果收到了 ping 的回复,则将新 node 的联系信息加入到路由表中。

种子文件扩展种子文件扩展

无 tracker 的种子文件中不含有 announce 关键字,相反,其包含有 nodes 关键字。该关键字应该包含种子创建者路由表的 K 个最近 node。也可以选择设置成已知的可用 node,比如这个种子的创建者。请不要加入 router.bittorrent.com。

nodes = [["<host>", <port>], ["<host>", <port>], ...]
nodes = [["127.0.0.1", 6881], ["your.router.node", 4804]]

KRPC协议

KRPC 协议是一个简单的 RPC 通信框架,其在 UDP 上使用 bencoded 编码的字典,包含请求与回复,但没有重试。有三种消息类型:query, response, error。对于 DHT 协议来说,有 4 种 query: ping, find_node, get_peers, announce_peer。

KRPC 消息是一个简单字典,包含两个必填关键字,附加的关键字取决于消息类型。第一个必填关键字是 t,这是一个字符串表示的 transaction ID。它由请求 node 产生,且包含在回复中,所以回复有可能对应单个 node 的多个请求。transaction ID 应该被编码成字符串表示的二进制数字,通常是两个字符,这样就能包含 2^16 种请求。另一个必填关键字是 y,其对应值表示消息类型,为 q, r 或 e。

联系信息编码

peer 的联系信息被编码成 6 字节的字符串。4 字节为 IP 地址,2 字节为端口号,均用网络字节序表示。

node 的联系信息被编码成 26 字节的字符串。20 字节为 node ID,剩余为 IP 和端口号信息,均用网络字节序表示。

Queries

query 为键值对 y:q,含有两个附加关键字,q 和 a。关键字 q 的值包含了请求类型的字符串表示。关键字 a 的值包含了一个所有返回值的字典。

Responses

response 为键值对 y:r,含有一个附加关键字 r。关键字 r 的值包含了一个所有返回值的字典。

Errors

error 为键值对 y:e,含有一个附加关键字 e。e 为一个列表。第一个元素是整形表示的错误码。第二个元素是字符串表示的错误信息。以下为可能的错误码,

Code        Description 
=======================
201         Generic Error 
202         Server Error 
203         Protocol Error, such as a malformed 
            packet, invalid arguments, or bad 
            token 
204         Method Unknown

示例,

generic error = {"t":"aa", "y":"e", "e":[201, "A Generic Error Ocurred"]}
bencoded = d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee

DHT Queries

所有的 query 都有一个键值对 ‘id:请求 node ID’。所有的 response 也有一个键值对 ‘id:响应的 node ID’。

ping

最基本的 query 是 ping。这时候 q=ping,id 为 20 字节网络字节序表示的发送者 node ID。该 query 的响应为 id=响应者 node ID。

arguments:  {"id" : "<querying nodes id>"}
response: {"id" : "<queried nodes id>"}

示例,

ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}
bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe

Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

find_node

find_node 被用来查找给定 ID 的 node 的联系信息。这时 q == find_node。find_node 请求包含 2 个参数,第一个参数是 id,包含了请求 node ID。第二个参数是 target,包含了请求者正在查找的 node ID。当一个 node 接收到了 find_node 的 query,他应该给出对应的回复,回复中包含 2 个关键字 id 和 nodes,nodes 是字符串类型,包含了被请求 node 的路由表中最接近目标 node 的 K(8) 个最接近的 node 的联系信息。

arguments:  {"id" : "<querying nodes id>", "target" : "<id of target node>"}
response: {"id" : "<queried nodes id>", "nodes" : "<compact node info>"}

示例,

find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}
bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re

get_peers

get_peers 与种子文件的 infohash 有关。这时 q=get_peers。get_peers 请求包含 2 个参数。第一个参数是 id,包含了请求 node 的 ID。第二个参数是 info_hash,它代表种子文件的 infohash。如果被请求的 node 有对应 info_hash 的 peers,他将返回一个关键字 values,这是一个列表类型的字符串。每一个字符串包含了 CompactIP-address/portinfo 格式的 peers 信息。如果被请求的 node 没有这个 infohash 的 peers,那么他将返回关键字 nodes,这个关键字包含了被请求 node 的路由表中离 info_hash 最近的 K 个 node,使用 Compactnodeinfo 格式回复。在这两种情况下,关键字 token 都将被返回。之后的 annouce_peer 请求中必须包含 token。token 是一个短的二进制字符串。

arguments:  {"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>"}
response: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "values" : ["<peer 1 info string>", "<peer 2 info string>"]}
or: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "nodes" : "<compact node info>"}

示例,

get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe
Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}
bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re
Response with closest nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}}
bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re

announce_peer

announce_peer 表示某个端口正在下载种子文件。announce_peer 包含 4 个参数。第一个参数是 id,包含了请求 node ID;第二个参数是 info_hash,包含了种子文件的 infohash;第三个参数是 port 包含了整型的端口号,表明 peer 在哪个端口下载;第四个参数是 token,这是在之前的 get_peers 请求中收到的回复中所包含的。收到 announce_peer 请求的 node 必须检查这个 token 与回复的 token 是否相同。如果相同,那么被请求的 node 将记录发送者的 IP 和端口号,记录在 peer 联系信息中对应的 infohash 下。

arguments:  {"id" : "<querying nodes id>",
  "implied_port": <0 or 1>,
  "info_hash" : "<20-byte infohash of target torrent>",
  "port" : <port number>,
  "token" : "<opaque token>"}

response: {"id" : "<queried nodes id>"}

示例,

announce_peers Query = {"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "implied_port": 1, "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:<br />
mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

 

BT种子嗅探器:DHT

之前写了原理篇,在原理篇里简单的介绍了一下DHT,但是还不够详细。今天我们就专门详细的讲一下嗅探器的核心-DHT,这里默认原理篇你已经读了。

背景知识

DHT全称 Distributed Hash Table,中文翻译过来就是分布式哈希表。它是一种去中心化的分布式系统,特点主要有自动去中心化,强大的容错能力,支持扩展。另外它规定了自己的架构,包括keyspace和overlay network(覆盖网络)两部分。但是他没有规定具体的算法细节,所以出现了很多不同的实现方式,比如Chord,Pastry,Kademlia等。BitTorrent中的DHT是基于Kademlia的一种变形,它的官方名称叫做 Mainline DHT。

DHT人如其名,把它看成一个整体,从远处看它,它就是一张哈希表,只不过这张表是分布式的,存在于很多机器上。它同时支持set(key, val),get(key)操作。DHT可以用于很多方面,比如分布式文件系统,DNS,即时消息(IM),以及我们最熟悉的点对点文件共享(比如BT协议)等。

下面我们提到的DHT默认都是Mainline DHT,例子都是用伪代码来表示。读下面段落的时候要时刻记着,DHT是一个哈希表。

Mainline DHT

Mainline DHT遵循DHT的架构,下面我们分别从Keyspace和Overlay network两方面具体说明。

Keyspace

keyspace主要是关于key的一些规定。

Mainline dht里边的key长度为160bit,注意是bit,不是byte。在常见的编译型编程语言中,最长的整型也才是64bit,所以用整型是表示不了key的,我们得想其他的方式。我们可以用数组方式表示它,数组类型你可以选用长度不同的整型,比如int8,int16,int32等。这里为了下边方便计算,我们采用长度为20的byte数组来表示。

在mainline dht中,key之间唯一的一种计算是xor,即异或(还记得异或的知识吧?)。我们的key是用长度为20的byte数组来表示,因此我们应该从前往后依次计算两个key的相对应的byte的异或值,最终结果得到的是另外一个长度为20的byte数组。算法如下:

​for i = 0; i < 20; i++ {
​ result[i] = key1[i] ^ key2[i];
​}

 

读到这里,你是不是要问xor有啥用?还记得原理篇中DHT的工作方式吗?

xor是为了找到好友表中离key最近的k个节点,什么样的节点最近?就是好友中每个节点和key相异或,得到的结果越小就越近。这里又衍生另外一个问题,byte数组之间怎么比较大小?很简单,从前往后,依次比较每一个byte的大小即可。

在Mainline DHT中,我们用160bit的key来代表每个节点和每个资源的ID,我们查找节点或者查找资源的时候实际上就是查找他们的ID。回想一下,这是不是很哈希表? 🙂

另外聪明的你可能又该问了,我们怎么样知道每个节点或者每个资源的ID是多少?在Mainline DHT中,节点的ID一般是随机生成的,而资源的ID是用sha1算法加密资源的内容后得到的。

OK,关于key就这么多,代码实现你可以查考这里。

Overlay network

Overlay network主要是关于DHT内部节点是怎么存储数据的,不同节点之间又是怎样通信的。

首先我们回顾一下原理篇中DHT的工作方式:

DHT 由很多节点组成,每个节点保存一张表,表里边记录着自己的好友节点。当你向一个节点A查询另外一个节点B的信息的时候,A就会查询自己的好友表,如果里边包含B,那么A就返回B的信息,否则A就返回距离B距离最近的k个节点。然后你再向这k个节点再次查询B的信息,这样循环一直到查询到B的信息,查询到B的信息后你应该向之前所有查询过的节点发个通知,告诉他们,你有B的信息。

整个DHT是一个哈希表,它把自己的数据化整为零分散在不同的节点里。OK,现在我们看下,一个节点内部是用什么样的数据结构存储数据的。

节点内部数据存储 – Routing Table

用什么样的数据结构得看支持什么样的操作,还得看各种操作的频繁程度。从上面工作方式我们知道,操作主要有两个:

在我(注意:“我”是一个节点)的好友节点中查询离一个key最近的k个节点(在Mainline DHT中,k=8),程度为频繁
把一个节点保存起来,也就是插入操作,程度为频繁

首先看到“最近”、“k”,我们会联想到top k问题。一个很straightforward的做法是,用一个数组保存节点。这样的话,我们看下算法复杂度。top k问题用堆解决,查询复杂度为O(k + (n-k)*log(k)),当k=8时,接近于O(n);插入操作为O(1)。注:n为一个节点的好友节点总数。

当n很大的时候,操作时间可能会很长。那么有没有O(log(n))的算法呢?

联想到上面堆的算法,你可能说,我们可以维护一个堆啊,插入和查询的时候都是O(log(n))。这种做法堆是根据堆中元素与某一个固定不变的key的距离来维护的,但是通常情况下,我们查询的key都是变化的,因此这种做法不可行。

那还有其他O(log(n))的算法吗?

经验告诉我们,很多O(log(n))的问题都和二叉树相关,比如各种平衡二叉树,我们能不能用二叉树来解决呢?联想到每个ID都是一个160bit的值,而且我们知道key之间的距离是通过异或来计算的,并且两个key的异或结果大小和他们的共同前缀无关,我们应该想到用Trie树(或者叫前缀树)来解决。事实上,Mainline DHT协议中用的就是Trie树,但是与Trie树又稍微有所不同。在Trie树里边,插入一个key时,我们要比对key的每一个char和Trie里边路径,当不一致时,会立刻分裂成一个子树。但是在这里,当不一致时,不会立刻分裂,而是有一个长度为k的buffer(在Mainline DHT中叫bucket)。分两种情况讨论:

  • 如果bucket长度小于k,那么直接插入bucket就行了。
  • 如果bucket长度大于或等于k,又要分两种情况讨论:
    • 第一种情况是当前的路径是该节点ID(注意不是要插入的key,是“我”自己的ID)的前缀,那么就分裂,左右子树的key分别是0和1,并且把当前bucket中的节点根据他们的当前char值分到相应的子树的bucket里边。
    • 第二种情况是当前路径不是该节点ID的前缀,这种情况下,直接把这个key丢掉。

如果还没有理解,你可以参照Kademlia这篇论文上面的图。

插入的时候,复杂度为O(log(n))。查询离key最近的k个节点时,我们可以先找到当前key对应的bucket,如果bucket里边不够k个,那么我们再查找该节点前驱和后继,最后根据他们与key的距离拍一下序即可,平均复杂度也为O(log(n))。这样插入和查询都是O(log(n))。

代码实现你可以查考这里。

节点之间的通信 – KRPC

KRPC比较简单,它是一个简单的rpc结构,其是通过UDP传送消息的,报文是由bencode编码的字典。它包含3种消息类型,request、response和error。请求又分为四种:ping,find_node, get_peers, announce_peer。

  • ping 用来侦探对方是否在线
  • find_node 用来查找某一个节点ID为Key的具体信息,信息里包括ip,port,ID
  • get_peers 用来查找某一个资源ID为Key的具体信息,信息里包含可提供下载该资源的ip:port列表
  • announce_peer 用来告诉别人自己可提供某一个资源的下载,让别人把这个消息保存起来。还记得Angelababy那个例子吗?在我得到她的微信号后,我会通知所有我之前问过的人,他们就会把我有Angelababy微信号这个信息保存起来,以后如果有人再问他们有没有Angelababy微信号的话,他们就会告诉那个人我有。BT种子嗅探器就是根据这个来得到消息的,不过得到消息后我们还需要进一步下载。

跳出节点,整体看DHT这个哈希表,find_node和get_peers就是我们之前说的get(key),announce_peer就是set(ke, val)。

剩下的就是具体的消息格式,你可以在官方文档上看到,这里就不搬砖了。

实现KRPC时,需要注意的有以下几点:

  • 每次收到请求或者回复你都需要根据情况更新你的Routing Table,或保存或丢掉。
  • 你需要实现transaction,transaction里边要包含你的请求信息以及被请求的ip及端口,只有这样当你收到回复消息时,你才能根据消息的transaction id做出正确的处理。Mainline DHT对于如何实现transaction没有做具体规定。
  • 一开始你是不在DHT网络中的,你需要别人把你介绍进去,任何一个在DHT中的人都可以。一般我们可以向 router.bittorrent.com:6881、 dht.transmissionbt.com:6881 等发送find_node请求,然后我们的DHT就可以开始工作了。

KRPC的实现你可以参考这里。

总结

DHT整体就是一张哈希表,首先我们本身是里边的一个节点,我们向别人发送krpc find_node或get_peers消息,就是在对这个哈希表执行get(key)操作。向别人发送announce_peer消息,就是在对这个哈希表执行set(key, val)操作。

OK,今天就说到这里,关于怎么样下载,我们下篇再说

转自:https://github.com/shiyanhui/dht/wiki

bt种子搜索原理

前言

之前看到L灯 这个十分火的工具,其利用了P2P的思想,就想了解一下P2P相关的协议。看了下最流行的BT协议官方文档,就产生了实现BT协议的想法,顺便根据协议实现了一个BT种子嗅探器。

也有人将BT种子嗅探器称为BT种子爬虫,个人觉得其行为特性和传统的web爬虫相差较大,反而和嗅探器很类似,因此暂且称之为BT种子嗅探器吧。

接下来将写一系列文章来介绍其原理和具体实现方式。这篇文章先提纲挈领,介绍其工作原理,以对全局有一个把握。后序的文章再介绍具体细节。

背景知识

在讲原理之前首先你得具备BitTorrent(简称BT)协议的一些基本知识,以便于理解接下来要讲的嗅探器。BT协议其实是一个协议簇,BEP-3 是其基本协议内容,其他的大部分都是围绕这个来进行扩展或补充。要想从BT网络中下载一个资源,必须具备以下部分:

  • 种子文件(也就是我们常说的种子,后缀是 .torrent,本质上是一个由bencode编码的文本文件,其把资源分成很多虚拟块,并记录每个块的hash值,另外上面还记录着其他信息,比如文件大小、名字、Tracker服务器等)
  • BT客户端(需要有专门解析BT协议的程序,这样才能下载,比如迅雷,电驴)
  • Tracker服务器 (记录着peer和种子相关信息,起着中心调控的作用)

下载资源的时候,客户端首先根据bencode(bencode是BT协议中的编码方式)解码种子文件,得到Tracker服务器的地址和资源信息,通过和Tracker服务器沟通得到其他已经下载该资源的peers信息(其他已经拥有该资源的客户端或者发布该资源的人),然后再和这些peers沟通得到自己想要的部分,即互通有无。由于把文件分成很多块来同时从不同的地方下载,这也就是为什么BT通常下载快的原因。

DHT协议

通过上面我们知道,Tracker服务器在资源下载的过程中起着至关重要的作用,只有通过它我们才能得到其他peers的信息,才能够下载,但这同时也成了BT协议的一个弱点,如果Tracker服务器挂掉了或者被封被屏蔽,整个网络也就瘫痪了。由于一些资源都是有版权的,还有一些资源是限制级的,比如色情资源,Tracker服务器很容易被迫关闭或被墙。后来聪明的人类发明了另外一种协议,就是 Distributed hash table, 简称DHT,这个协议就是用来弥补这个弱点的。

BT协议簇中的DHT协议 是基于 Kademlia协议 建立的,其基本思想很好理解。DHT 由很多节点组成,每个节点保存一张表,表里边记录着自己的好友节点。当你向一个节点A查询另外一个节点B的信息的时候,A就会查询自己的好友表,如果里边包含B,那么A就返回B的信息,否则A就返回距离B距离最近的k个节点。然后你再向这k个节点再次查询B的信息,这样循环一直到查询到B的信息,查询到B的信息后你应该向之前所有查询过的节点发个通知,告诉他们,你有B的信息。

举个例子,比如我现在想要Angelababy的微信号(额…我要干嘛),我就从自己的微信好友中挑出k个最可能认识她的人,然后依次问他们有没有Angelababy的微信号,假如其中一个认识,那么他就会给我Angelababy的微信号,我也就不继续问其他人了。假如他不认识,他就给我推荐k个他微信好友中最有可能认识Angelababy的k个人,然后我再继续这k个人,就这样循环一直到我问到为止。OK,现在我已经得到了Angelababy的微信号,我就会告诉之前所有我问过的人,我有Angelababy的微信号。

当客户端下载资源的时候,他会利用上述方式查找peers信息,这样每个人都充当了Tracker的作用,也就解决了上面那个问题。

嗅探器原理

终于到核心部分了。

BT种子嗅探器就是利用了DHT协议得到peer信息后会向他之前查询过的节点发送通知这一点,这就是嗅探器的核心。

剩下的工作就是我们要让更多的节点发给我们通知。那么如何让更多的节点发给我们通知呢?

  • 我们要不断的查询自己的好友节点表,并对返回回来的节点进行查询,这样才会有更多的人认识我们
  • 别人向我们查询Target的时候,我们要伪装成Target的好友,返回结果里边包括自己,这样会有更多被查询、收到通知的机会

这就是BT种子嗅探器的原理,简单吧 🙂

种子下载器

在BT网络中,通过上述原理收到信息并不是种子,而是发送消息者的ip和port、种子infohash(可以理解为种子的id)。我们如果想要得到种子的话,还需要做一番工作。这里涉及到另外一个非常重要的协议 BEP-09,BEP-09规定了如何通过种子infohash得到种子。

这里不铺开讲,仅说下大致过程。首先同我们收到的消息里边的 ip:port 建立TCP连接,然后发送握手消息,并告知对方自己支持BEP-09协议,然后向对方请求种子的信息,收到对方返回的种子信息后,依次或同时请求每一个块。最有所有块收集完后,对其进行拼接并通过sha1算法计算其infohash,如果和我们请求的infohash值相同则保存起来,否则丢掉。

应用

这样你可以得到非常多的种子信息,你可以对其进行索引建立自己的BT种子搜索引擎,建立自己的海盗湾。但你需要注意版权问题和色情资源问题。
转自:https://github.com/shiyanhui/dht/wiki

boost asio广播

一篇boost asio UDP广播示例。

为什么要广播

通常我们需要在局域网来进行广播来查找主机,广播的意思就是向同网段的全部主机发送数据包。而在广域网是不允许的,可以想象广域网主机数量之多,广播造成网络堵塞。广播只能是udp,是由路由器向所有主机发送数据包,包括发送者本身。如果发送的是本身,可以留意下你收的IP:192.168.56.1。还有个组播的,这个在广域网和局域网都允许。

示例代码

服务器:

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <thread>
void handle_send()
{

};
int main()
{
  namespace ip = boost::asio::ip;
  boost::asio::io_service io_service;

  // Server binds to any address and any port.
  ip::udp::socket socket(io_service,
                         ip::udp::endpoint(ip::udp::v4(), 0));
  socket.set_option(boost::asio::socket_base::broadcast(true));

  // Broadcast will go to port 8888.
  ip::udp::endpoint broadcast_endpoint(ip::address_v4::broadcast(), 8888);

  // Broadcast data.
  boost::array<char, 4> buffer;
 // socket.send_to(boost::asio::buffer(buffer), broadcast_endpoint);
  socket.async_send_to(boost::asio::buffer(buffer), broadcast_endpoint,
	  boost::bind(&handle_send));
  io_service.run();
  //std::thread run_thread([&]{ io_service.run(); });
  //run_thread.join();
}

客户端:

#include <iostream>

#include <boost/asio.hpp>
#include <boost/array.hpp>
int main()
{
  namespace ip = boost::asio::ip;
  boost::asio::io_service io_service;

  // Client binds to any address on port 8888 (the same port on which
  // broadcast data is sent from server).
  ip::udp::socket socket(io_service,
                         ip::udp::endpoint(ip::udp::v4(), 8888 ));

  ip::udp::endpoint sender_endpoint;

  // Receive data.
  boost::array<char, 4> buffer;
  std::size_t bytes_transferred =
    socket.receive_from(boost::asio::buffer(buffer), sender_endpoint);

  std::cout << "remote host ip : " << sender_endpoint.address().to_string() << std::endl;
 // std::cout << "got " << bytes_transferred << " bytes." << std::endl;
}

总结

上面的例子是阻塞模式的广播,有兴趣的可以把它修改成异步模式的,记得udp不是可靠的,上面的例子出处来自国外忘记,,。。。

转:游戏中的网络同步机制——Lockstep

0x00 前言

每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的乐趣,而这个虚拟世界是如何完美的将身处天南地北的玩家连接在一起的呢?我们每个人的电脑配置都不一样,网络延迟也不同,但是在玩FPS(第一人称射击)游戏时,战斗感受与真实世界并无二致,网游是如何做到这一点的呢?

本文将介绍和分析早期广泛在RTS(即时策略)游戏中应用的同步机制——Lockstep

RTS游戏有很多,比如我们都玩过的的Warcraft III(大家耳熟能详的Dota是它的一张地图)和StarCraft,还有EA的代表作命令与征服系列(Command & Conquer)等等,以及现在非常流行的Dota2LOL

那么为什么要强调早期呢?因为Dota2LOL等新兴的游戏使用的同步机制不再是传统的Lockstep了。严格来说,Warcraft和现在意义上的网游有很大区别,因为它所谓的网是局域网(LAN)。早期RTS游戏出现时互联网还没有现在那么普及,网速也很慢,更没有什么像样的网游,能够支持局域网对战已经很不错了。

有人可能会有疑问,我们平时经常在对战平台上和全国各地的人打Dota,你为什么说Warcraft III只支持局域网呢?这又是一个很有意思的话题,实际上,对战平台使用了虚拟局域网(VLAN)技术,通过进程注入,HOOK WinSock函数调用,将数据包发送到对战平台服务器上,由服务器分配虚拟IP,这里还能够进行天梯匹配等等,在随后的游戏过程中游戏数据包都是通过对战平台的服务器进行转发,但是这一切对Warcraft III进程本身来说是透明的,它依然感觉自己在一个局域网环境中。

0x01 为什么要有同步机制

一致性

在虚拟世界中,保证游戏的一致性是一个基本前提。什么是一致性?通俗的说就是虚拟世界中的事实,比如在一个FPS游戏中,大家的延迟都很高,A、B两个玩家同时发现了对方,并向对方射击,如果没有很好的同步机制,那么A的屏幕上显示B还没有开枪就被击杀,而B的屏幕上显示A还没有开枪就被击杀,这就出现了不一致的问题,那么这个游戏还怎么愉快的进行下去?

可以这么说,延迟是造成不一致问题的主要原因。如果延迟都为0(即A玩家作出行动的同时B玩家就能看到),那么也就不存在不一致的问题了,就像在真实世界中一样。而同步机制除了基本的通信作用外,最重要的任务就是解决不一致问题,即保证游戏的一致性。同步机制有许多种,根据游戏类型、技术条件甚至时代背景的不同,选择的同步机制也会不同。

分类

游戏的网络同步机制有很多,国外也有这方面的论文,抛开具体实现细节,总体来看可以分为下面几类

  • Peer-to-Peer,在这类方法中,没有服务器,游戏参与者的身份是对等的,依靠参与游戏的玩家电脑自行解决同步问题的,最为典型的就是Lockstep
  • Client-Server,在这类方法中,Server端是绝对的权威,所有计算基本在Server上完成。例如,在游戏中向前移动一步,要等待服务器确认“你向前移动了一步”之后,才可以在客户端上进行这个行为。(延迟较低的时候是察觉不到这个过程的,延迟高时会有明显的卡顿现象)
  • Client-Side Prediction,严格来说这并不是一类方法,而是对第二类方法的改进。试想如果所有的操作都必须在得到服务器的确认,然后才在客户端上进行,在延迟较高时用户体验会非常的差。这时可以把常用一部分计算转移到客户端进行,服务器辅助校正即可。

0x02 什么是Lockstep

Lockstep最初是军队行进中使用的,后来在19世纪的时候广泛在美国监狱使用,成为那个时期美国监狱的一个标识。就像这样

WAeN1vjpp6pDTWCO1J5QPhGlHRiJ60vKXuXxAmK1

 

或者这样

rRiG7oC1Lma1suliNdBAU737nFDJnWt9LBHNCkF9

 

意思就是大家同步的走,谁超前了要等待,落后了的要赶上。后来就引申到游戏的网络同步机制上了

上一章节中我们说到Lockstep是Peer-to-Peer架构中的一种同步方式,而我们平时在局域网中玩Dota时,也的确没有大型的游戏服务器,只有一台所谓的主机,那么你可能会想,是不是所有的计算都是在那台主机上完成的呢?也就是说其他玩家的机器只发送施放了某某技能这样的数据包给主机,而造成多少伤害、某某效果是由主机计算并返回的。

但事实并不是这样的,玩Dota所有的一切都是在本地计算完成的。包括技能伤害、效果,命中与否,随机刷新野怪等等。也就是说每个玩家的电脑都完整计算了整盘游戏的全过程,且计算过程与计算结果都一模一样。而主机只是负责把每个玩家的操作指令(鼠标点击、键盘按键等等)广播给其他玩家。

是不是感到难以理解?

想要理解Lockstep的机制,先看看下面三个问题。

  • 什么是动画?是会动的画吗?当然不是,人眼的记忆时间为0.1s,也就是大约100ms,只要把一帧一帧的静态图像快速播放一遍,我们就会感觉画面就动了起来,比如下面这样PY28PKq2E3P24OWxeKV2GIRjxfoKCE5yeSf74bnY

 

  • 最容易实现同步的游戏类型是什么?当然是回合制游戏,比如棋类游戏和卡牌游戏,它们有严格的先后顺序,不容易出现逻辑错误,更不会出现不一致的情况;而且回合时间较长,能够容忍高延迟。
  • 什么是状态机?状态机是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。给定一个状态机模型F,处在某一个状态S1,这时给定一个输入I,此时状态机会转移到一个新的状态S2。在这个过程中,只要FS1I是确定的,那么S2就是确定的。

其实把以上三点结合起来,就是Lockstep的基本思路

我们来看下面这张图

VWRJNtLhYdWxr9bxLzDPfjwdSOl4CxFG12eJ42Fo

图中是A、B、C三个玩家的时间轴,这个时间轴不是电脑上的本地时间,而是A、B、C联机时定义的一个时间轴。虚线分隔出来时间片称为turn,可以理解成一个回合。箭头表示该玩家将自己的操作指令广播给其他玩家。我们把一盘游戏看成一个大型的状态机,因为大家玩的是同一款的游戏,因此F是相同的,初始状态S0也是相同的。在第一个turn结束时,所有玩家都接收到了完全一样的输入I,注意这里的I不是一个值,而是包含了当前游戏中所有玩家的操作指令集合。t1时刻所有玩家的电脑自行计算结果。由于FS0I是固定的,所以每个玩家电脑上计算出的下一个状态S1一定是相同的。

同理,第二个turn也是如此qzobYsq3v0JyMYKr9ma6mqjiVNWIddttuINF0cbx

 

可以看出,Lockstep其实也是“回合制”的,当然这个所谓的回合与我们理解的棋类、卡牌游戏的回合是不太一样的。Lockstep的回合(也就是turn)中,所有玩家都可以采取行动,最终结果是在回合结束时统一计算的。在同一个turn接收到的操作指令,是不分行动先后顺序的,只要是在同一个turn里,就认为是同时发生的。

举个例子,假设A、B、C是游戏中3个互相敌对的单位,攻击力都为100。在某一个turn内,A和B都右键点击了C(warcraft这类游戏好像都是右键普攻),C右键点击了A,这些操作指令都广播到了其他玩家电脑上,则该turn的输入为“A攻击C、B攻击C、C攻击A”。那么该turn结束后,每个人的电脑都开始计算,且计算结果是相同的,即“A损失100生命值,B不变,C损失200生命值”。

这就是Lockstep同步机制,其实也没有多复杂是吧~这里还有几点需要注意:

  • Lockstep把游戏过程划分成了一个个turn,为什么游戏不会出现卡顿的现象呢?回到前面动画的那个问题,人眼是容易被欺骗的,人的反应其实也是很慢的(相对电脑来说)。人眼的记忆时间为0.1s,只要每秒进行10turn,我们是感觉不出卡顿的。而通常游戏的帧数为60fps,即每秒60幅图像在屏幕上显示,你会感觉游戏非常流畅~当然“帧”和Lockstep中的“turn”并不是一一对应的,这里只是想说明一个turn的时间是非常短的,至少比我们的反应要快的多。
  • Lockstep对网络延迟的要求是非常高的,因为每个turn要向所有玩家广播操作,同时也要接收来自其他玩家的操作。只有当每个turn集齐了所有玩家的操作指令,也就是输入确定了之后,才可以进行计算,进入下一个turn,否则就要等待最慢的玩家。当然局域网可以很好的满足这个要求,延迟基本都在1ms左右。
  • Lockstep中会不会出现延迟导致的不一致问题?显然不会,从上面的分析可以知道,使用Lockstep的游戏是严格按照turn向前推进的,如果有人延迟比较高,其他玩家必须等待该玩家跟上之后再继续计算,不存在某个玩家领先或落后其他玩家若干个turn的情况。使用Lockstep同步机制的游戏中,每个玩家的延迟都等于延迟最高的那个人。(当然这个说法还有待讨论,后面还会说到这个问题)
  • Lockstep是非常严格的,要求每一步的的计算结果都完全一样,任何计算错误都有可能导致蝴蝶效应,产生严重的后果,因为状态不能同步的话游戏根本就没有办法进行下去,最终将崩溃退出。
  • 《Algorithms and Networking for Computer Games》这本书中关于Lockstep的部分与本文的说法有些不同,我觉得也没有谁对谁错之分,Lockstep更多的是一种思想,算是不一样的理解吧,感兴趣的同学也可以看看书中的章节。书中将Lockstep分为commit和reveal两个阶段,并且采用了流水线的方式。如下图所示

sBEhIr9XvJJtC7vSfguIHHiILopdq0veg3lrLmbUgpok2fQ9LXAx8MAPhErQKE8kbJDndV0FNK0OktDJ

 

0x03 与Lockstep无关的细节问题

为什么要讨论与Lockstep无关的问题呢?因为这些问题虽然与Lockstep无关,但却与游戏本身有关,而且很多问题是由Lockstep引起的

野怪刷新与暴击

我们都知道,Dota中有许多问题是与概率相关的,比如整点时野怪是随机刷新的,出了水晶剑之后是有概率暴击的。那么按照Lockstep同步机制,计算都是在每个玩家自己电脑上完成的,那么在有概率存在的情况下,怎么可能保证每台电脑的计算结果一致呢?!

这时就轮到伪随机数派上用场了,我们来看下面这个Java程序

import java.util.Random;
public class PseudoRandom {
    public static void main(String[] args) {
        Random r1 = new Random(10);//这里的10就是随机种子(Random Seed)
        for (int i = 0; i < 10; i++) {
            System.out.print(r1.nextInt(100) + "t");
        }
        System.out.println();
        Random r2 = new Random(10);
        for (int i = 0; i < 10; i++) {
            System.out.print(r2.nextInt(100) + "t");
        }
    }
}

//Output
//13	80	93	90	46	56	97	88	81	14
//13	80	93	90	46	56	97	88	81	14

 

如果你的jdk版本没有什么问题的话,那么你看到的结果一定也是上面那样,而且无论你运行多少次都是这个结果。大部分编程语言内置库里的随机数都是利用线性同余发生器产生的,如果不指定随机种子(Random Seed),默认以当前系统时间戳作为随机种子。一旦指定了随机种子,那么产生的随机数序列就是确定的。

所以,游戏开始前,参与游戏的玩家电脑协商确定一个随机种子,就可以保证在游戏进行过程中大家产生的随机数序列是相同的,也就可以保证计算结果一致。

例如一个英雄的暴击率为30%,对某个目标持续普攻,如果随机数序列为12 32 90 25,小于等于30判定暴击,大于30判定不暴击,那么每个玩家电脑的计算结果都是暴击 不暴击 不暴击 暴击。(这里只是举例说明原理,游戏中的实现细节不一定是这样)

外挂问题

在对战平台打Dota的人都见识过全图挂,而且屡禁不止,那么为什么Warcraft III中会有全图挂,而没有其他游戏中的变态挂(如加强攻击力,瞬间移动等等)呢?为什么无法从根本上杜绝全图挂呢?

前面已经说过了,使用Lockstep同步机制,所有计算都是在本地完成的,每轮turn都必须接收来自其他所有玩家的操作指令才能完成计算,也就是说其他玩家的一举一动你的电脑其实是知道的,只不过没有在你的屏幕上展现出来罢了(被战争迷雾遮挡了)。因此,全图挂只要修改Warcraft III的内存数据,就可以去除战争迷雾,达到开全图的效果。而对战平台不可能改变Lockstep这种同步机制,只能在本地检测是否有其他程序修改Warcraft III的内存数据(就像病毒查杀一样),而外挂程序总有办法绕过检测,所以全图挂总是层出不穷。

而另一方面,恰恰因为Lockstep同步机制,其他比较变态的外挂根本不可能在Warcraft III上存在。比如强化攻击力,我们同样可以利用修改内存的方式将增加自己的攻击力,可以直接秒杀其他任何单位,但是别忘了其他玩家电脑也在同步的进行计算,而我们的攻击力在其他玩家电脑上仍然是不变的,这就造成了状态不一致。前面说过了,Lockstep中出现状态不同步的情况时很容易产生蝴蝶效应,最终崩溃退出。

断线重连

这个问题一直被广大玩家诟病,因为其他网游从来没有断线之后连不回去的情况,为什么Warcraft III不支持断线重连呢?

Lockstep同步机制是非常严格的,中途加入游戏是从技术上来讲是非常困难的。首先中途加入的玩家要进行状态同步,而状态同步本身包含的内容太多了,其中包括时间戳、伪随机数序列、所有单位的位置属性信息等等,不是简单的复制粘贴就能同步的。这说起来容易,要具体实现起来没那么简单,而且在局域网中掉线情况并不常见,因此早期暴雪的开发人员可能也就忽略这个问题了。

事实上使用对战平台的人才会经常遇到这个问题,在互联网中,延迟高、掉线的情况时有发生。那么11平台的掉线重连功能是怎么来的呢?个人观点,那并不是真的掉线重连,只是我们的丢包情况比较严重,暂时卡了而已。11平台可能做了一些优化,将其他玩家的操作指令保存起来,等延迟稳定的时候再发送给我们。但由于此时我们电脑所处的turn可能落后了其他玩家,所以此时游戏就会像快放一样赶上其他玩家的进度。如果是真的掉线,如停电、程序崩溃等直接退出的情况,我们是无法重新加入游戏的。

Warcraft III是不是使用严格的Lockstep机制?

接上一个问题,如果Warcraft III是严格的Lockstep同步机制,那么一定会出现一人卡、大家都卡的情况,而事实上只有当我们是主机时才会出现这种情况,其他情况下我们延迟高甚至掉线都不影响其他玩家的操作。

因此,Lockstep实际是一种理想的模型,如果在实际中使用会造成非常差的用户体验。那么Warcraft III使用的到底是什么同步机制呢?参考What every programmer needs to know about game networking这篇文章后面一个评论的说法

TOADCOP

FEBRUARY 10, 2010 AT 9:15 AM

btw afaik StarCraft don’t use peer-to-peer it uses client-server model with lockstep (at least warcraft 3 does so). It has the advantage what theoreticaly laggers will not affect gameplay/response latency at all (but to not let them fall behind server do timeouts so the lagger can catch up, also doing temporary local game speed increasing) and imo it’s the only and true way to do sync in RTS like games. (and for some reasons this technic isn’t good covered in the web)

然后是作者的回复

GLENN FIEDLER

FEBRUARY 10, 2010 AT 10:20 AM

You are correct. Also something cool is that in a C/S RTS model the server could also theoretically arbitrate to ignore turns from lagging players, and kick them if they don’t catch up – removing various exploits where you can time-shift your packets and lag out other players.

也就是说Warcraft III使用的是基于Client-Server的Lockstep模型。这就是为什么Warcraft III中有主机这个概念,当然这里主机的作用并不是完成所有计算。

(以下为个人观点)

Warcraft III中的主机的主要功能是广播并设置timeout,也就是说在每个turn内,游戏玩家并非直接将自己的操作指令广播给其他玩家,而是先发送给主机,由主机负责广播,且每个turn都有timeout,如果超过了timeout仍然没有收到某个掉线玩家的操作指令,则忽略该玩家在该turn的行为,即认定他什么都没有做,并与其他延迟正常的玩家同步进入下一个turn。而当掉线玩家网络恢复时,主机会将之前保存的turn中操作指令集合发送给该名玩家,而该名玩家为了赶上进度,就会出现游戏快放的情况。

所以Warcraft III中只有在主机延迟高或掉线时,其他玩家才会受影响,否则不受影响。在局域网中,如果主机是正常退出的,那么会选定另一玩家电脑作为主机,如果是崩溃退出的,则所有人都会直接掉线。至于在对战平台上是否有优化就不太清楚了。

0x04 总结

Lockstep是出现较早的一种同步机制,不过现在很多RTS游戏中依然能够看到它的影子,当然都对它进行了一定程度的改进。

国内关于游戏编程和网络同步的教材、文献寥寥无几,不知道是文化因素还是什么其他原因,难道与游戏相关的技术都是玩物丧志、不学无术?

在我查询资料的过程中,发现国外不仅有游戏编程和网络同步的理论教材,还有大学开设的游戏课程,甚至还有硕士论文是关于设计一个MMORPG游戏的……看看国外繁荣的游戏(使命召唤、刺客信条、魔兽世界),再看看国内繁荣的游戏市场(页游?手游?),看看国外知名的游戏公司(Blizzard、EA、UBISOFT),再看看国内知名的游戏公司(……企鹅?)

这里不是想黑什么,而是觉得计算机作为一门多样化的学科,我们不能固步自封,排斥其中的一些东西。有人说研究游戏有什么用?真的没用吗?看看下面这些应用

  • 网络游戏中广泛应用的航位推测法(Dead Reckoning)美国军方也在使用
  • 游戏中的自动寻路算法,在机器人和自动驾驶中也有应用
  • 游戏中的决策与博弈论也有关联
  • 游戏服务器集群之间的负载均衡与一致性,和现在热门的大数据息息相关

虽然不一定是由游戏本身推动的,但是这些研究之间是相辅相成的,不会做无用功的,何况游戏本身就是增加GDP的一个好手段╮( ̄▽ ̄)╭

本文计划写成一个系列,后续还会介绍其他同步机制~

0x05 相关资料

写在后面

出于转载的目的:原文地址:http://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep/

我找到目前的关于网络位置同步文章,较好的文章,所以全程转载记录

TrinityCore MMORPG服务器设计分析

本文是通过TrinityCore研究从而对MMORPG服务器设计分析。

网络字节序

ByteBuffer是字节(unsigned char类型)处理,没有大小端处理。在网络通信应用程序上,这种是经常有的,网络是通过字节发送。

消息包格式:包头+包类型+包体

包头rc4加密

登录认证是(srp6)安全远程密码第六版协议,包类型是占一字节。

游戏中,包类型占1.5字节。

1、封包

重载&operator<<
按顺序把数据封包

2、解包

重载&operator>>
按顺序把数据解出

消息包处理

网络是采用boost asio

1、发送

这里需要注意,在这个执行,在多线程里面操作加锁

std::unique_lock<std::mutex> guard(_writeLock);

boost::asio的发送函数:_socket.async_write_some

1.1 登录认证的发送

void AuthSession::SendPacket(ByteBuffer& packet)
{
    if (!IsOpen())
        return;

    if (!packet.empty())
    {
        MessageBuffer buffer;
        buffer.Write(packet.contents(), packet.size());

        std::unique_lock<std::mutex> guard(_writeLock);

        QueuePacket(std::move(buffer), guard);
    }
}

 2.2在游戏中发送

void WorldSocket::SendPacket(WorldPacket const& packet)
{
    if (!IsOpen())
        return;

    if (sPacketLog->CanLogPacket())
        sPacketLog->LogPacket(packet, SERVER_TO_CLIENT, GetRemoteIpAddress(), GetRemotePort());

    ServerPktHeader header(packet.size() + 2, packet.GetOpcode());

    std::unique_lock<std::mutex> guard(_writeLock);

    _authCrypt.EncryptSend(header.header, header.getHeaderLength());

#ifndef TC_SOCKET_USE_IOCP
    if (_writeQueue.empty() && _writeBuffer.GetRemainingSpace() >= header.getHeaderLength() + packet.size())
    {
        _writeBuffer.Write(header.header, header.getHeaderLength());
        if (!packet.empty())
            _writeBuffer.Write(packet.contents(), packet.size());
    }
    else
#endif
    {
        MessageBuffer buffer(header.getHeaderLength() + packet.size());
        buffer.Write(header.header, header.getHeaderLength());
        if (!packet.empty())
            buffer.Write(packet.contents(), packet.size());

        QueuePacket(std::move(buffer), guard);
    }
}

2、接收

接收函数_socket.async_read_some

处理已收到数据包MessageBuffer& packet = GetReadBuffer();

登录认证接收处理:void WorldSocket::ReadHandler()

游戏过程处理:

void WorldSocket::ReadHandler()

bool WorldSocket::ReadHeaderHandler()

bool WorldSocket::ReadDataHandler()

流程

消息通过函数指针映射处理。

1、登录游戏

std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers();

2、认证通过后

void WorldSocket::HandleAuthSession(WorldPacket& recvPacket)

内部把这次玩家会话添加 :

新建一个会话_worldSession = new WorldSession(id, shared_from_this(), AccountTypes(security), expansion, mutetime, locale, recruiter, isRecruiter);

添加到世界:sWorld->AddSession(_worldSession);

判断是否有同个帐号登录,如果有,把它踢下线

2、全部会话处理:

void World::UpdateSessions(uint32 diff)
{
..
 for (SessionMap::iterator itr = m_sessions.begin(), next; itr != m_sessions.end(); itr = next)
..

}

2、游戏过程中

extern OpcodeHandler opcodeTable[NUM_MSG_TYPES]

写在后面

TrinityCore常用了TCP通信,tcp通信也是存在心跳的,网络是异常退出(比如,任务管理器直接杀死游戏进程),tcp协议不会通知说我已经退出。如果是实时再线格斗类型游戏,建议常用UDP。TrinityCore大量使用c++ 0x11,有些地方,我也不是明白,如果有存在错误的地方也请指出。

mmorpg魔兽世界服务器框架TrinityCore构建

本文对TrinityCore 版本7.x | 3.3.5编译说明通用。

简介

TrinityCore 是c++实现MMORPG框架

来自MaNGOS,大型网络对象服务,随着时间的推移,该项目代码广泛的优化,改善和清理代码。

这是完全开源的,社区参与是高度鼓励

如果你有好的想法请访问网站github 库https://github.com/TrinityCore/TrinityCore,提出。

更多TrinityCore项目信息,请访问TrinityCore.org

要求

  • 平台:Linux, Windows or Mac>
  • cpu支持SSE2
  • Boost ≥ 1.49
  • MySQL ≥ 5.1.0
  • CMake ≥ 2.8.11.2 / 2.8.9 (Windows / Linux)
  • OpenSSL ≥ 1.0.0
  • GCC ≥ 4.7.2 (Linux only)
  • MS Visual Studio ≥ 12 (2013) (Windows only)
  • 按里面提示的软件进行安装:http://collab.kpsn.org/display/tc/Requirements现在windwos的

安装

本节只讨论windows 7系统安装

TrinityCore:https://github.com/TrinityCore/TrinityCore/releases

1、生成vs2013工程解决方案

  1. 下载名字ource code (zip)
  2. 打开cmake
  3. Where is the Source code: E:/SDK/TrinityCore-stable
  4. Where to build th binaries:E:/SDK/TrinityCore-stable/build
  5. 点击Configure 现在 vs2013,把TOOLS勾上,点击Generate生成TrinityCore.sln
  6. 编译解决方案

如果是64位系统需要手动去把mysql库,指向win32的,否则出现link 2019错误。

2、生成地图

  1. 安装好World of Warcraft – 3.3.5a (12340) – enUS (No Install),建议编译Release的版本,生成地图快
    可C:\TrinityCore\contrib 复制 “extractor.bat”及地图生成工具4个exe 放wow.exe同目录,(2 3 4 5步骤可跳过)
  2. 在Wow.exe同目录下一次点击mapextractor.exe
  3. mmaps_generator.exe
  4. vmap4extractor.exe
  5. vmap4assembler.exe
  6. 生成的dbc maps mmaps vmaps拷贝v到 authserver.exe worldserver.exe目录下

3、数据库安装

  1. 默认mysql安装,添加帐号:trinity,密码:trinity
  2. 数据库图形管理工具HeidiSQL或者SQLyog,推荐用SQLyog
  3. 工具->现在执行sql文件…sqlcreatecreate_mysql.sql
  4. 选择auth数据库 工具->现在执行sql文件…sqlbaseauth_database.sql
  5. characters数据库 同上面操作执行…sqlbasecharacter_database.sql
  6. world数据库 同上面操作执行…”TDB_full_*.sql。下载名字是TDB_full

4、运行

  1. authserver.conf.dist和worldserver.conf.dist 去掉dist
  2. 运行authserver.exe,再运行worldserver.exe
  3. worldserver窗口里创建游戏帐号:account create username password
  4. 修改游戏客户端DataenUSrealmlist.wtf内容为:set realmlist 127.0.0.1
  5. 运行wow登录。。

结束语

大概步骤是和Mangos构建是类似的,上面没说到是用了那个版本,自己下载数据库和源码版本一致。

最后来个截图,这图只是研究使用,无意对暴雪侵权,图:魔兽世界:巫妖王之怒3.35

wow 3.35

Unity3d 使用boost asio在windows android

本文简单说明Unity3d c++插件,并采用boost asio打开串口设备状态在windows android平台下的交互通信。

Unity3d插件简介

Unity3D底层是c++实现的,用户用c#进行开发,不过是用mono开源,开源的 .NET 开发框架。在实际应用中,我们总有一些代码是其他编程语言实现,历史遗留。插件可以是C、C++、Objective-C等语言实现的。

构建插件

1、插件代码

1、百度云盘:里面有SimplestPluginExample-4.0和boost asio打开串口的例子http://pan.baidu.com/s/1mgy47jU

2、目录jni的MyClass.h是关于boost asio打开串口设备代码

3、下面代码来自官方SimplestPluginExample-4.0

#if _MSC_VER // this is defined when compiling with Visual Studio
#define EXPORT_API __declspec(dllexport) // Visual Studio needs annotating exported functions with this
#else
#define EXPORT_API // XCode does not need annotating exported functions, so define is empty
#endif

// ------------------------------------------------------------------------
// Plugin itself


// Link following functions C-style (required for plugins)
extern "C"
{

// The functions we will call from Unity.
//
const EXPORT_API char*  PrintHello(){
    return "Hello";
}

int EXPORT_API PrintANumber(){
    return 5;
}

int EXPORT_API AddTwoIntegers(int a, int b) {
    return a + b;
}

float EXPORT_API AddTwoFloats(float a, float b) {
    return a + b;
}

} // end of export C block

2、构建Windows插件

打开SimplestPluginExample-4.0SimplestPluginExampleVS2008PluginVisualStudioASimplePlugin.sln

3、构建Android插件

  1. 本机环境windows7
  2. 下载NDK,将Androidandroid-ndk-r9d加到系统环境变量
  3. 进入目录Unity3dPlugin-boostAsioboost-asio
  4. 执行:ndk-build

注意:一定要动态libgnustl_shared.so

 Unity3d插件boost asio应用

1、拷贝库

将编译好的dll和so文件放对应入AssetsPlugins目录下的Android、x86

2、添加到unity3d工程代码

using System.Runtime.InteropServices;
[DllImport("paokunet")]
private static extern void startNet();
[DllImport("paokunet")]
private static extern void stopNet();
[DllImport("paokunet")]
private static extern bool getNetStats();

 3、windows应用

boost库地址:http://sourceforge.net/projects/boost/files/boost-binaries/

没什么好说的

4、安卓应用

  • boost 安卓库 里面有说明编译,如不想看就结尾有现成库。
  • unity3d导出安卓项目记得导出地方对google android打勾
  • boost asio是因动态加载stl才能工作正常

1、添加一个java文件类似这样的

import com.unity3d.player.UnityPlayerActivity;

import android.os.Bundle;
import android.util.Log;

public class OverrideExample extends UnityPlayerNativeActivity {
    static
    {
        //加载.so文件
      Log.d("OverrideActivity", "paokunet freeyun ???????");
      {
          System.loadLibrary("gnustl_shared");
          System.loadLibrary("paokunet");
       }
    }
}

2、AndroidManifest.xml类似这样

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.Unity3d.u3d" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:theme="@android:style/Theme.NoTitleBar" android:icon="@drawable/app_icon" android:label="@string/app_name" >
    <activity android:label="@string/app_name" android:screenOrientation="landscape" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" android:name="com.hj.u3d.OverrideExample">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
    </activity>
  </application>
  <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22" />
  <uses-feature android:glEsVersion="0x00020000" />
  <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
</manifest>

 总结

只是用用boost aiso串口代码,注意串口的设备打开是不一样的,安卓和Linux设备类似“”/dev/ttyS1”,windows类“COM8”如果需要用tcp udp通信也是可以的。

MaNGOS编译搭建

Mangos ,中文芒果服务器,是大名鼎鼎魔兽世界的模拟服务器,一个开源的c++实现的项目。只是作为研究用途,商业用是要给追究法律责任,跨平台性支持windows、linux、BSD操作系统。mmorpg服务器开发架构有的帮助,当然众所周知的原因,网络库那里是是ACE,一般人无法驾驭,写下本文是知做个笔记,因为网络部分不是很容易懂,TrinityCore是它分支,建议去深入这个大量的c++ 0x11重写Mangos ,本文只是环境是在windows环境下搭建MaNGOS编译经典版本的Zero,即是60级wow的1.12.1和1.12.2。之前也转过MaNGOS编译,那个比较老旧了,不过它的前言还是挺值的一看,至于目前最新的版本MaNGOS就参考这篇文章,文章在写的同事MaNGOS也是在一直更新,不过整的来说,它的编译方法差不了多少。

需求软件:

  • 暴雪官方魔兽世界 wow 1.12.1版本:
  • 编辑器:Notepad ++

Mangos编译

  1. 打开/win/BuildEverything_vc120.sln
  2. 编译将会在buildbinDebug目录下看到exe

制作地图

  1. 安装wow客户端
  2. 将Mangos编译出来的exe拷贝到World of Warcraft 1.12目录(Launcher.exe、WoW.exe)下
  3. 执行ExtractResources.sh,再执行make_vmaps.bat 漫长的等待,然后你看到dbc、maps、mmaps及vmaps。

数据库安装

  1. 按默认把mysql数据库管理系统安装上去
  2. 添加管理员帐号:mangos,密码:mangos
  3. 在database-release20目录下执行InstallDatabases.bat,(这个脚本有错误,后面会给批处理test.bat只是创建mangos的所有数据库)按照提示输入mysql 帐号和密码,
  4. 利用数据库工具更新mangos的全部数据库。

配置Mangos

mangosd.cfg

文件从mangosd.conf.dist.in修改后缀名

LoginDatabaseInfo = "127.0.0.1;3306;mangos;mangos;realmd"
WorldDatabaseInfo = "127.0.0.1;3306;mangos;mangos;mangos"
CharacterDatabaseInfo = "127.0.0.1;3306;mangos;mangos;characters"
ScriptDev2DatabaseInfo = "127.0.0.1;3306;mangos;mangos;mangos"
#下面是方便中断调试用,不会断开连接
MaxOverspeedPings =0 /

realmd.cfg

文件从realmd.conf.dist.in修改后缀名,直接默认。

运行并调试

  1. 把dbc、maps、mmaps及vmaps拷贝到buildbinDebug目录下。
  2. realmd是认证服务器,mangosd是游戏服务器,必须先启动认证服务器,如果要调试设置默认的为启动调试项目。
  3. 添加游戏用户,在mangosd的窗口里面输入,表示帐号username,密码Password的命令:create “username” “Password”
  4. 本机调试编辑魔兽世界官方客户端realmlist.wtf内容为,set realmlist 127.0.0.1
  5. 公网用:realmlist.wtf内容为,set realmlist 公网IP,用数据库工具打开realmd数据库,修改realmlist里面的address,为公网IP

Mangos 源码,数据库 wow客户端三者打包:

http://pan.baidu.com/s/1pJGEEan

更多资料

https://www.getmangos.eu/wiki.php

MaNGOS编译

这的资料比较老旧,目前最新版的是rel19编译上很简单!!

开源社区常常有很多出人意表的项目,让人往往击节叹赏。例如当年在普通PC电脑上虚拟运行苹果机系统的Pearpc。把微软游戏机xbox改装成家庭影音中心的xbox-media center,以及在ipod上安装linux的ipodlinux等项目。在电脑游戏方面当然也有很多出色的开源项目。
暴 雪公司的游戏历来都是精品。网络游戏大热以后,它推出的基于10年游戏历史的网游:《魔兽世界》也更是名声在外(当然有好有坏,坏名声在于它太吸引人以致 于很多人沉湎其中)。网络游戏的盈利模式和传统电脑游戏不同,月费和其他的周边产品使得其赢利空间远远大于传统电脑游戏。对于网游运营商来说很大的一个问 题是私服的问题。很多私服的软件是从网游开发公司或者运营公司内部非法的流出来的,比如说网游《仙境传说》的私服。
而现在大多数魔兽世界 的“私服”实际上是服务端软件的模拟器。是在对客户端游戏软件和与服务端的进行通信的数据进行分析解密以后,开发出来的模拟原游戏服务器功能的软件,这样 的模拟器软件大都也是开源软件。比如像现在大部分魔兽私服使用的Mangos服务端软件,mangos项目是一个开源的自由软件(如同linux或者 firefox),并且遵守其中最为严格的GPL协议。
是(Massive Network Game Object Server) 的缩写。由于暴雪公司对类似的开发小组采取过一些法律行动。为了避免麻烦,如同它的名字,mangos强调自己并非一个魔兽服务器模拟器,而是一个开源的 多人在线游戏服务器的软件。说到底是个网游的游戏引擎。Mangos开发小组强调其软件是用c++和C#编程语言,实现 的一个支持大型多人在线角色扮演游戏服务器的程序框架,在这个框架下,它理论上应该支持任何客户端的网络游戏,由于现在很多人使用魔兽世界来对它进行测 试,所以针对魔兽世界的脚本和数据库文件比较完善,很多人就利用这个开源项目来实现魔兽私服。而游戏的内容,例如故事情节,任务场景的脚本等都是由别的小 组独立开发的。
开发小组一再强调,这是个研究,教育性质的对怎样开发大型网游的服务器端有好处的项目,是一个技术细节毫无保留向公众开放的软件,是一件很有 意义的事情,如果你使用它作为盈利目的,那你本身就违反了软件的协议。所以任何利用mangos项目进行私服活动的组织和个人都违反了mangos的宗 旨,mangos项目也不会对它们负责。
mangos的技术细节上是这样的,核心部分是个和特定游戏没有关系的核心框架程序,主要是进行进程调度,创造世界,建立心跳机制,处理网络接入 等。数据库可以使用的开源数据库软件MySQL。至于游戏内容数据库,游戏人物,时间,世界脚本,都是由这个核心程序所支持的扩展 脚本来实现,所以有一些独立出来的项目专门模拟魔兽世界来开发支持mangos的核心程序。现在mangos的核心程序已经放到著名的协同开发网站 sourceforge上开发了,使用的协同开发工具是subversion。大家都可以从sourceforge的subversion数据库中下载最新的 源代码程序。 为 了测试这个Mangos我自己安装了一个测试了以下,自己对魔兽世界的游戏本身没有太多的了解,请了几个玩过魔兽世界的朋友测试了一下,都说各方面已经很 完美了。最新的Mangos项目已经支持了魔兽世界的扩展版“燃烧的远征”,简称TBC,对应的魔兽客户端版本到了2.0以上。以下的安装测试步骤适用于 魔兽世界客户端1.12.1或1.12.2的版本。
一、安装需求:

  1. 操作系统Windows Xp,解压软件推荐7-zip 。(mangos论坛上有人在linux,macosx上安装,为了提取地图方便这里使用windows XP)
  2. 魔兽世界的客户端软件,升级到1.12.1版本以上。
  3. 编译好的Mangos二进制文件。(你也可以自己编译,我这里提供的是Mangos Rev 3462,3462是开发数据库的版本号),下载地址:    http://filebeam.com/22b7839a746adbd95f3ac3853f3bf9ed
    http://www.megaupload.com/?d=T0SGUOQB
  4. 地图提取工具ad.exe(这个地图提取工具是专门针对1.12.x版本的客户端的,TBC需要比较新的ad.exe,TBC的地图提取工具不适用老版本的地图),下载地址:http://www.megaupload.com/?d=DZW472B4
  5. mpq文件包提取工具MPQE_1.2.rar,暴雪公司的游戏都采用一种叫mpq的文件格式,是Mike O’Brien Pack的缩写,这个Mike就是暴雪公司的首席游戏程序开发人员,和创始者。我们需要使用mpqe.exe从客户端里提取相关文件。下载地址:
    http://www.megaupload.com/?d=VL2DXHXW
  6. 开源数据库软件MySql
    官网:MySQL
    或者用我下载的版本:http://www.megaupload.com/?d=ODYRBWSF
  7. MySQL的客户端软件SQLyog,编译管理操作数据库
    官网:here
    我下载的版本5.30:http://www.megaupload.com/?d=DD0LK19H
  8. 游戏内容数据库,配合Mangos Rev 3462的版本。
    文件名:CompleteSDB156-Mangos3462.rar
    下载地址:
    http://www.megaupload.com/?d=0EME2U4Y

二、安装MySQL数据库,以及SQLyog客户端

  1. 点击安装文件选择典型安装(Typical)
  2. 配置数据库服务器”Configure the MySQL Server Now”,选择标准配置”Standard Configuration”。
  3. 确认安装成windows的服务程序和自动启动服务”Install as Windows Service”、”Launch the MySQL Server Automatically”。
  4. 输入你的root用户的密码。可以打开的使用root远程管理数据库的选项,便于远程管理,和链接。
  5. 应用你的设置。
  6. 点击安装SQLyog的安装文件,安装到缺省路径,运行SQLyog。
  7. 当SQLyog运行后,需要输入以下信息:
    • MySQL主机地址:可以输入你的本机IP地址或者”localhost”。
    • 用户名(UserName):root
    • 密码(Password):在第4步里面你配置的密码。
    • 数据库(database(s))输入:”realmd;mangos”
  8. 点击连接,这样客户端就连接到你的MySQL数据库服务器上了。
  9. 在左边一栏右键点击“root@localhost”,选择建立数据库”Create Database”。
  10. 当提示输入数据库名称的时候输入:”mangos”。
  11. 重复第9,10步建立数据库”realmd”。

三、安装Mangos服务器,配置路径

  1. 建立一个目录,例如:”C:MaNGOS”
  2. 把你下载的mangos的二进制文件解压到这个目录里面。
  3. 检查你的两个重要的服务器配置文件:“mangosd.conf”和“realmd.conf”。其中“mangosd.conf”里面可以配置物品,金钱的掉落率,经验值的增长率等等。

四、解压安装地图文件

  1. 把ad.exe放在魔兽世界的安装目录下,例如(“C:World of Warcraft”)
  2. 建立一个子目录叫maps,结构为:(“C:World of Warcraftmaps”)
  3. 运行ad.exe文件。耐心等待ad.exe把魔兽世界的地图信息提取到maps目录下。
  4. 将maps子目录拷贝到”C:MaNGOS”目录下,结构为:”C:MaNGOSmaps”,里面的地图文件大概有2400多个。
  5. 可以安全的删除”C:World of Warcraft”下的maps子目录了。

五、解压安装提取的*.dbc文件

  1. 在你的mangos的安装目录下建立dbc子目录,结构为:(“C:MaNGOSdbc”)
  2. 为了将暴雪公司的mpq文件的内容解压出来,将提取工具mpqe.exe拷贝在魔兽世界安装目录的data目录下,结构为:(“C:World of Warcraftdata”)
  3. 打开dos窗口,进入魔兽世界的data目录(cd “C:World of Warcraftdata”)。
  4. 执行以下命令:
    mpqe /p dbc.MPQ   DBFilesClient*.dbc
  5. mpqe工具将魔兽的游戏信息提取到了MPQOUTDBFilesClient目录下,整个结构为:(“C:World of WarcraftdataMPQOUTDBFilesClient”)
  6. 当mpqe运行结束后,可以将所有的*.dbc拷贝到你的mangos的dbc目录下:(“C:MaNGOSdbc”),你大概有158个*.dbc文件。
  7. 你可以安全删除魔兽世界下的dbc文件目录:(“C:World of WarcraftdataMPQOUT”)了。

七、下载安装数据库内容,前文说过,mangos只是游戏引擎,游戏的内容,建立的数据库由独立小组开发,我们这里使用著名的Silver’s Database Site的库。版本号为SDB156.

  1. 将下载的CompleteSDB156-Mangos3462.rar文件解压,里面有几个数据库文件。找到其中“realm-database-full.sql”和“mangos-full-for-rev3462.sql”文件。
  2. 使用SQLyog连接到你的数据库。
  3. 在左栏找到realmd,点击右键选择”Restore from SQL Dump”,找到“realm-database-full.sql”,导入数据库文件。
  4. 同样找到mangos,点击右键选择”Restore from SQL Dump”,找到“mangos-full-for-rev3462.sql”,导入数据库文件。

八、配置数据库,配置文件,运行开源魔兽服务器!本文只为测试游戏,在局域网中玩。

  1. 使用SQLyog连接到你的数据库。
  2. 找到realmd,展开找到”realmlist”选项。
  3. 在右下找到”Table Data”。
  4. 在name一项填入你的服务器名字,例如叫faif
  5. 在地址address一项可以使用”localhost” 或者你的IP地址。
  6. 在realmd数据库下找到account表,点击”Table Data”,建立你的游戏帐号,里面已经由几个内置的帐号,你可以参照gm,普通玩家的帐号建立你的用户名,密码。
  7. 在mangos目录下打开realmd.conf文件,找到
    “LoginDatabaseInfo =”
    改为:”LoginDatabaseInfo = “127.0.0.1;3306;root;[password];realmd”
    这里的IP地址可以是你的IP,[password]是你的密码。存盘。
  8. 在mangos目录下打开mangosd.conf文件,找到如下的内容,并设置如下:
    • DataDir = “.”
    • WorldDatabaseInfo = “127.0.0.1;3306;root;[password];mangos”
    • LoginDatabaseInfo = “127.0.0.1;3306;root;[password];realmd”

    同样,这里的IP地址可以是你的IP,[password]是你的密码。存盘。

  9. 到这里基本就大功告成了。先运行realmd.exe文件,稍等,运行mangosd.exe,魔兽服务器就已经运行了。
  10. 连接到你的魔兽服务器,修改你的魔兽世界客户端的realmlist.wtf的文件,将127.0.0.1,或者你的IP地址填在里面,例如:“set realmlist 127.0.0.1”

到此为止,开源的魔兽服务器就架设成功了,我给朋友建立了几个gm帐号,他们测试了一下,觉得这个版本的做的很不错了,开源的宗旨是知识的分享,开源社区黑客们的探索精神不得不让人佩服。

英文Mangos编译完全手册for wow2.43-3.13-入门篇2009-07-21 23:03

作者也是从小白过来,知道很多mangoser都想学mangos的vc技术,可是国内资料太少,真理总是掌握在少数人手里。。。我希望大家能本着互相交流学习的目的提高中国mangos技术,和vc开发网游技术。我公开我所掌握的技术资料,陆续会推出内核各种代码机制及数据库交互面的讲解,今天先来个入门教程。


先是关于mangosf的一些介绍:

mangos不是一个魔兽世界 SF 模拟器,它是一个开源的自由软件项目,是用c++和C#编程语言,实现的一个支持大型多人在线角色扮演游戏服务器的程序框架,在这个框架下,它理论上应该支持任何客户端的网络游戏,由于现在很多人使用魔兽世界来对它进行测试,所以针对魔兽世界的脚本和数据库文件比较完善,很多人就利用这个开源项目来实现魔兽SF。 最近对这一块比较感兴趣,所以下载mangos的代码尝试着自己编译。 mangos源码下载地址: GIT方式更新 [url]http://github.com/mangos/mangos/tree/master[/url] (负责Server端的主要控制工作,接收客户端发送的封包信息以及回复给客户端相应的封包) ScriptDev2源码下载地址: SVN方式更新[url]http://scriptdev2.svn.sourceforge.net/svnroot/scriptdev2/[/url] (这部分代码主要控制玩家交互方面的一些东西,游戏里生物的AI,等等。常见的WOWSF里的 传送宝石就是通过扩展这部分代码做出来的) udb下载地址: SVN方式更新[url]https://unifieddb.svn.sourceforge.net/svnroot/unifieddb/trunk/[/url] (游戏的所有数据,生物,物品,怪物,NPC等等都是通过读取MYSQL数据库而实现的,而mangos这个项目只负责做框架,并没有把数据库的研发工作也包含进去,所以有另外一个小组专门做udb这方面的工作,每天都会更新很多新的数据,时时保持更新,可以让游戏里的内容更加丰富) 在编译之前将以上三个资源都下载到最新版本,然后先编译mangos,提供三个工程 vc71,vc8,vc9。我用的Visual Stdio2008,所以打开vc91的工程项目进行编译.整个编译过程没什么好说的。 1:中文问题: 需要说明的一点是,如果要在游戏里加入中文需要特别的处理一下,因为mangos这个项目最初并没有考虑到国际化的问题,所以如果需要正确的显示中文,最好在文字的开头和末尾都额外地加上一个空格,然后按照UTF-8的编码方式保存源文件。再进行编译。至于VS2003和VS2005我没有尝试过,也可以考虑添加一个通用类来进行UTF-8的转换工作。 2:编译ScriptDev2有可能遇到的问题 首先将ScriptDev2复制到mangos项目下的srcbindings目录里进行编译。 我在编译时确实遇到了一下这个问题,如果之前通过的SVN下载的mangos源码,有可能出现这样的问题。需要将mangos用git 更新一下,不过目前的资料貌似没有出现这个问题。 mangossrcbindingsScriptDev2>”……winVC71genrevision__Win32_Releasegenrevision.exe”
1>系统找不到指定的路径。
1>mangossrcbindingsScriptDev2>if errorlevel 1 goto VCReportError 3:mangos版本对应wow版本的说明

2009-02-19发布v0.13-dev2 ,仅支持魔兽世界版本为3.0.8,3.0.9
2009-01-30发布v0.13-dev1 ,仅支持魔兽世界版本为3.0.3,3.0.5
2009-12-22发布v0.12,    仅支持魔兽世界版本为2.4.3


关于Mangos框架 的编译和搭建分为两部分工作.

一、先决条件:
1. Visual Studio 2008(下Express Edition就行,免费的专门为学生和非专业开发者用,我也用这个http://www.microsoft.com/express/download/default.aspx

  1. MySQL6.0(dev.mysql.com/downloads/mysql/6.0.html

  2. SQLyog Community数据库管理器,用于管理MYSQL数据库(http://www.webyog.com/en/downloads.php

  3. 新建好mangos源代码和ScriptDev2脚本源代码文件夹

5.wow客户端,目前推荐美f3.13,台f3.12/3.13,cwow3.05/3.09(sina游戏有下载)

6.SVN软件,或者GIT软件(中国人喜欢SVN外国人喜欢GIT+SVN,我喜欢俩个都用。。。)

如何下载mangos和ScriptDev前面已经说得很清楚了,下载完两个项目之后,将ScriptDev 放到 mangos的项目 /src/bindings目录下. 二:编译 首先release编译mangos,根据vs版本的不同打开不同的sln文件。生成解决方案(去喝杯茶,抽支烟吧)目前为止整个项目450万以上的代码对我的硬件设备是一个很大的挑战。 正常情况下编译是成功的,如果不成功,请检查是否是最新版本的源码。 继续编译ScriptDev2 编译完毕,进到mangos 目录下的 /src/bin/release里面,你会找到:libeay32.dll,dbghelp.dll,libeaysql.dll,mamgosd.exe,MaNGOSScript.dll,realmd.exe 这6个文件。 三:配置 现在新建一个目录,用来存放WOW服务开启所需要的东西。 将编译好的文件copy进来. 现在,到./src/mangosd文件夹下找到mangosd.conf.dist.in,再到./src/realmd下找到realmd.conf.dist.in文件,
把这两个文件拷贝到mangos文件夹下,并把.in去掉。
用记事本或者UltarEdit32打开这两个conf文件,修改几处:
<1>mangosd.conf文件中找到:
LoginDatabaseInfo = “127.0.0.1;3306;xxxx;xxxx;realmd”
WorldDatabaseInfo = “127.0.0.1;3306;xxxx;xxxx;mangos”
把第一个xxxx设置为你的MySQL用户名,第二个是你的MySQL的密码。
找到:DataDir=”@MangosDATA@” 把””里面改为:”./”即DataDir=”./”
然后保存完事儿
<2>realmd.conf文件中找到:
LoginDatabaseInfo = “127.0.0.1;3306;xxxx;xxxx;realmd”
修改方法同mangosd.conf 在mysql中新建四个数据库mangos,realmd,characters,scriptdev2 然后导入一些初始数据,sql目录下有三个主要的sql mangos.sql realmd.sql characters.sql srcbindingsScriptDev2下有scriptdev2所需要的数据信息 五、释放DBC,MAPS文件:
需要两个软件:ad.exe这个是解压缩地图的. mpq.exe这个是解压缩mpq文件的。
1.把ad.exe放入游戏客户端的目录下即./World of warcraft,然后新建一个名为”maps”的文件夹
然后,美服客户端就可以直接运行了。如果是台服,就要把Data文件夹下的enTW文件夹改名为enGB
然后把locale-enTW.MPQ,patch-enTW-2.MPQ,patch-enTW.MPQ这3个文件的TW都改为GB。
运行就可以解压出地图来了,弄完以后的maps文件夹大小是1.06G
2.把mpq.exe和zlib.dll放进zhTW里面,然后:
开始菜单—>运行—>输入cmd—>到你放mpq.exe的路径下,例:F:/WOW/DATA/zhTW—>
输命令:mpq e locale-zhTW.MPQ *.dbc
然后会生成一个目录Ex….locale-zhTW的,里面有一个DB开头的文件夹,把文件夹名字改为dbc,
完工
3.现在把maps,dbc两个文件夹拷到mangos下面就可以了。     启动realmd.exe和mangosd.exe OK,现在将以下文本保存为bat复制到你的wow客户端目录下 @echo off
rd cache /s /q
echo set realmlist 127.0.0.1>realmlist.wtf
start wow.exe -opengl -windows
exit 点击这个bat开始登陆吧

有一个初始的游戏管理员账号ADMINISTRATOR,密码同


现在本人补充:为了完成3。09以上sf工作

1。GIT下载:http://msysgit.googlecode.com/files/Git-1.6.3.2-preview20090608.exe

这个冬冬作为svn的补充。建议先编译,如果不成在用git更新,

更新地址:git://github.com/mangos/mangos.git

2。svn现在官方最新的更新地址:http://svn2.assembla.com/svn/mangos-svn-mirror/

原来的地址已经不能下载mangos源程序了。

3。用户密码现在已经不是明码,而是hash码,建议用administrator建立帐户,后在数据库提高其权限为3

 

转载来自:http://hi.baidu.com/sunsee/item/ead77482d42173ebe596e041