中软面试题-最新
by jiang zhuo on 七.29, 2010, under 笔试面试题目
中软的面试比较经典,也比较严格,一般有四轮,类似于微软的面试。中软面过以后,根据项目组,会推到美国微软那边运用live meeting & con-call 再面一次。以下是我的面试题及个人的小分析,拿出来和大家share一下。希望更多的人能过这个坎。如有什么问题,可以一起交流。直接进入主题:
1. English communication. (sale yourself, project information, your interesting,and how to deal with problem you encounter etc.)
2. the using of key words "new".
Regarding this problem, you can refer to my other blog with this path: new . or you can get more information from internet.
3.Write a method which can remove the same unit from a Array which has been sorted.
//在排序好的数组中移除相同的元素
public int [] RemoveCommon(int [] a)
{
if (a.Length==0)
{
return a;
}
List
int i , j;
i = j = 0;
int temp = a[0];
while (i < a.Length)
{
if (a[i] != temp)
{
j++;
temp = a[i];
Result.Add(temp);
}
i++;
}
// convert List to Array
//……
return Result;
}
4. Judge Triangle and write test case for your code.
判断一个三角形,如果是等边的返回1,等腰返回2,其他的返回3,不能构成三角形的返回 4。 再编写test case 测试
判断一个三角形,如果是等边的返回1,等腰返回2,其他的返回3,不能构成三角形的返回 4。 再编写test case 测试
public int Triangle(int a, int b, int c)
{
if (a <= 0 || b <= 0 || c <= 0)
{
return 4;
}
int [] arry = new int [3] { a, b, c };
Array.Sort(arry);
int min, mid, max;
min = arry[0];
mid = arry[1];
max = arry[2];
if (max-mid
return 4; //不能构成三角形
}
if (min == max)
{
return 1; //等边
}
else if ( mid==min || mid == max)
{
return 2; // 等腰
}
else
return 3; // 其他
}
在这里,我最想提的就是这一题了,因为,我们知道,判断三角形,我们常用 两边之和大于第三边。但是,在这里,就不能用了。例如: a= int.MaxValue, b=int.MaxValue, c=int.MaxValue, 这时候三边肯定能构成三角形,但是,a+b 的和已经超过了int 型的最大值。造成内存溢出。 有人说,内存溢出 就是 0 或者负数,用这个不就可以判断吗?这个还真不行。你可以多写几个test case 试试。
Test case:
其他的普通的case,我就不写了,在这里就强调一下,边界值的问题(也就是常说的 临界值)。int.maxvalue, 0 etc.
5.Reverse string.
字符串反转,例如: string str="I love china", 反转后就是 str="china love I".
private char [] Convent(char [] str,int start, int end)
{
char temp;
int len = end – start;
int i=0;
while(i
temp = str[start+i];
str[start +i] = str[end -i - 1];
str[end-i-1] = temp;
i++;
}
return str;
}
public string Reverse(string str)
{
if (String.IsNullOrEmpty(str))
{
return null;
}
char [] objstr = str.ToCharArray(); ;
int length=objstr.Length;
objstr = Convent(objstr,0,length);
int i = 0;
int start=0,end=0;
while (i < length)
{
if (objstr[i] == ‘ ‘||i==length-1)
{
if (i == length – 1)
{
end = i + 1;
}
else
{
end = i;
}
objstr = Convent(objstr, start, end);
start = end+1;
}
i++;
}
return new string(objstr);
}
6. Find the most width level in a tree and return the count of level, if there are many one, just return the nearest level. (it can be found in the internet)
寻找树的最宽层,并返回那层的层数(如有多个最宽层,返回离根节点最近的层数)
static int M 10 //假设二叉树最多的层数
int Width(BinTree T)
{
int static n[M];//向量存放各层结点数
int static i=1;
int static max=0;//最大宽度
if(T)
{
if(i==1) //若是访问根结点
{
n[i]++; //第1层加1
i++; //到第2层
if(T->lchild)//若有左孩子则该层加1
n[i]++;
if(T->rchild)//若有右孩子则该层加1
n[i]++;
}
else
{ //访问子树结点
i++; //下一层结点数
if(T->lchild)
n[i]++;
if(T->rchild)
n[i]++;
}
if(max
i–; //往上退一层
Width(T->rchild);//遍历右子树
}
return max;
}//算法结束
7. Implement the function: Int ConvertToInt(string num)
实现 Int ConvertToInt(string num)
public int ConvertToInt(string num)
{
int result=0;
int temp=0;
if (!string.IsNullOrEmpty(num))
{
if (IsInteger(num))
{
for (int i = 0; i < num.Length; i++)
{
temp = result;
result = result * 10 + ((int)num[i] – 48); //0 的Asscall码 是48
if (temp == result)
continue;
}
if (temp != result)
{
throw new Exception("overflow");
}
}
}
return result;
}
// 判断字符串是否是整数。
public bool IsInteger(string strIn)
{
bool bolResult = true;
if (strIn == "")
{
bolResult = false;
}
else
{
foreach (char Char in strIn)
{
if (char.IsNumber(Char))
continue;
else
{
bolResult = false;
break;
}
}
}
return bolResult;
}
关于上面的判断字符串里转换后是否是整数,还有其他的方法:
判断是否是数字
public bool isnumeric(string str)
{
char[] ch = new char[str.Length];
ch = str.ToCharArray();
for (int i = 0; i < ch.Length; i++)
{
if (ch[i] < 48 || ch[i] > 57)
return false;
}
return true;
}
8. Quick sort. (you can get it from internet)
快速排序
static public void Quicksort(int[] array, int begin, int end)
{
if (begin < 0 || end < 0 || begin > end)
return;
int left = begin, right = end, temp;
temp = array[left];
while (right != left)
{
while (temp < array[right] && right > left)
right–;
if (right > left)
{
array[left] = array[right];
left++;
}
while (temp > array[left] && right > left)
left++;
if (right > left)
{
array[right] = array[left];
right–;
}
}
array[right] = temp;
Quicksort(array, right + 1, end);
Quicksort(array, begin, right – 1);
}
Ok, that is all.
那次面试比较久,接近四个小时。后面被推到微软那边面试,面试题,详见后面解析。
by jiang zhuo on 七.29, 2010, under 源码分析
Announce的参数比较多 常用于发布种子向Tracker提交信息 并且一次是一个种子。
Scrape的参数比较少 从Tracker一次获得多个Torrent的信息。
Announce的参数处理 public AnnounceParameters(NameValueCollection collection, IPAddress address) : base(collection, address) { CheckMandatoryFields(); if (!isValid) return; /* If the user has supplied an IP address, we use that instead of * the IP address we read from the announce request connection. */ IPAddress supplied; if (IPAddress.TryParse(Parameters["ip"] ?? "", out supplied) && !supplied.Equals(IPAddress.Any)) clientAddress = new IPEndPoint(supplied, Port); else clientAddress = new IPEndPoint(address, Port); } private void CheckMandatoryFields() { isValid = false; List<string> keys = new List<string>(Parameters.AllKeys); foreach (string field in mandatoryFields) { if (keys.Contains(field)) continue; Response.Add(FailureKey, (BEncodedString)("mandatory announce parameter " + field + " in query missing")); return; } byte[] hash = HttpUtility.UrlDecodeToBytes(Parameters["info_hash"]); if (hash.Length != 20) { Response.Add(FailureKey, (BEncodedString)(string.Format("infohash was {0} bytes long, it must be 20 bytes long.", hash.Length))); return; } infoHash = new InfoHash(hash); isValid = true; }
Scrape的参数处理 public ScrapeParameters(NameValueCollection collection, IPAddress address) : base(collection, address) { hashs = new List(); ParseHashes(Parameters["info_hash"]); } private void ParseHashes(string infoHash) { if (string.IsNullOrEmpty(infoHash)) return; if (infoHash.IndexOf(',') > 0) { string[] stringHashs = infoHash.Split(','); for (int i = 0; i < stringHashs.Length; i++) hashs.Add(InfoHash.UrlDecode(stringHashs[i])); } else { hashs.Add(InfoHash.UrlDecode(infoHash)); } }
by jiang zhuo on 七.27, 2010, under 源码分析
connectionIDs是个Dictionary 用来维护现在Listener处理的所有传入连接
curConnectionID是当前处理的传入连接
Running标识是否正在运行
CreatConnectionID没创建一个curConnectionID进行加一,加入到Dictionary 中是传入来连接的IP与curConnectionID的集合
GetConnectionID根据IP得到connectionID
Start与Stop方法 ////// Starts listening for incoming connections /// public override void Start() { if (Running) return; //TODO test if it is better to use socket directly //Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); listener = new System.Net.Sockets.UdpClient(endpoint.Port); listener.BeginReceive(new AsyncCallback(ReceiveData), null); } /// /// Stops listening for incoming connections /// public override void Stop() { if (!Running) return; System.Net.Sockets.UdpClient listener = this.listener; this.listener = null; listener.Close(); }
Start方法实例化一个UdpClient然后开始惊醒ReceiveData的回调。
Stop方法释放UdpClient
ReceiveData方法 private void ReceiveData(IAsyncResult ar) { try { byte[] data = listener.EndReceive(ar, ref endpoint); if (data.Length <16) return;//bad request UdpTrackerMessage request = UdpTrackerMessage.DecodeMessage(data, 0, data.Length, MessageType.Request); switch (request.Action) { case 0: ReceiveConnect((ConnectMessage)request); break; case 1: ReceiveAnnounce((AnnounceMessage)request); break; case 2: ReceiveScrape((ScrapeMessage)request); break; case 3: ReceiveError((ErrorMessage)request); break; default: throw new ProtocolException(string.Format("Invalid udp message received: {0}", request.Action)); } } catch (Exception e) { Logger.Log(null, e.ToString()); } finally { if (Running) listener.BeginReceive(new AsyncCallback(ReceiveData), null); } }
如果小于16字节根据规定肯定是不对的不是发的错了就是传输问题之类的。如果是个合法的数据,就实例化一个UdpTrackerMessage(这个类大多数用在客户端之间的数据传输,所以在MonoTorrent.Client名字空间下。然后判断Udp请求是什么样的请求。有建立连接,Announce,Scrape,Error。不过这几个Message类都放在MonoTorrent.Client下。
Receive到Connection Announce Scrape的方法 private void ReceiveConnect(ConnectMessage connectMessage) { UdpTrackerMessage m = new ConnectResponseMessage(connectMessage.TransactionId, CreateConnectionID()); byte[] data = m.Encode(); listener.Send(data, data.Length, endpoint); } //QUICKHACK: format bencoded val and get it back wereas must refactor tracker system to have more generic object... private void ReceiveAnnounce(AnnounceMessage announceMessage) { UdpTrackerMessage m; BEncodedDictionary dict = Handle(getCollection(announceMessage), endpoint.Address, false); if (dict.ContainsKey(RequestParameters.FailureKey)) { m = new ErrorMessage(announceMessage.TransactionId, dict[RequestParameters.FailureKey].ToString()); } else { TimeSpan interval = TimeSpan.Zero; int leechers = 0; int seeders = 0; Listpeers = new List (); foreach (KeyValuePair keypair in dict) { switch (keypair.Key.Text) { case ("complete"): seeders = Convert.ToInt32(keypair.Value.ToString());//same as seeder? break; case ("incomplete"): leechers = Convert.ToInt32(keypair.Value.ToString());//same as leecher? break; case ("interval"): interval = TimeSpan.FromSeconds(int.Parse(keypair.Value.ToString())); break; case ("peers"): if (keypair.Value is BEncodedList) // Non-compact response peers.AddRange(MonoTorrent.Client.Peer.Decode((BEncodedList)keypair.Value)); else if (keypair.Value is BEncodedString) // Compact response peers.AddRange(MonoTorrent.Client.Peer.Decode((BEncodedString)keypair.Value)); break; default: break; } } m = new AnnounceResponseMessage(announceMessage.TransactionId, interval, leechers, seeders, peers); } byte[] data = m.Encode(); listener.Send(data, data.Length, endpoint); } private NameValueCollection getCollection(AnnounceMessage announceMessage) { NameValueCollection res = new NameValueCollection(); res.Add("info_hash", announceMessage.Infohash.UrlEncode()); res.Add("peer_id", announceMessage.PeerId); res.Add("port", announceMessage.Port.ToString()); res.Add("uploaded", announceMessage.Uploaded.ToString()); res.Add("downloaded", announceMessage.Downloaded.ToString()); res.Add("left", announceMessage.Left.ToString()); res.Add("compact", "1");//hardcode res.Add("numwant", announceMessage.NumWanted.ToString()); res.Add("ip", announceMessage.Ip.ToString()); res.Add("key", announceMessage.Key.ToString()); res.Add("event", announceMessage.TorrentEvent.ToString().ToLower()); return res; } private void ReceiveScrape(ScrapeMessage scrapeMessage) { BEncodedDictionary val = Handle(getCollection(scrapeMessage), endpoint.Address, true); UdpTrackerMessage m; byte[] data; if (val.ContainsKey(RequestParameters.FailureKey)) { m = new ErrorMessage(scrapeMessage.TransactionId, val[RequestParameters.FailureKey].ToString()); } else { List scrapes = new List (); BEncodedDictionary files = (BEncodedDictionary)val["files"]; foreach (KeyValuePair keypair in val) { BEncodedDictionary dict = (BEncodedDictionary)keypair.Value; int seeds = 0; int leeches = 0; int complete = 0; foreach (KeyValuePair keypair2 in dict) { switch (keypair2.Key.Text) { case "complete"://The current number of connected seeds seeds = Convert.ToInt32(keypair2.Value.ToString()); break; case "downloaded"://The total number of completed downloads complete = Convert.ToInt32(keypair2.Value.ToString()); break; case "incomplete": leeches = Convert.ToInt32(keypair2.Value.ToString()); break; } } ScrapeDetails sd = new ScrapeDetails(seeds, leeches, complete); scrapes.Add(sd); if (scrapes.Count == 74)//protocole do not support to send more than 74 scrape at once... { m = new ScrapeResponseMessage(scrapeMessage.TransactionId, scrapes); data = m.Encode(); listener.Send(data, data.Length, endpoint); scrapes.Clear(); } } m = new ScrapeResponseMessage(scrapeMessage.TransactionId, scrapes); } data = m.Encode(); listener.Send(data, data.Length, endpoint); } private NameValueCollection getCollection(ScrapeMessage scrapeMessage) { NameValueCollection res = new NameValueCollection(); if (scrapeMessage.InfoHashes.Count == 0) return res;//no infohash???? //TODO more than one infohash : paid attention to order in response!!! InfoHash hash = new InfoHash(scrapeMessage.InfoHashes[0]); res.Add("info_hash", hash.UrlEncode()); return res; } private void ReceiveError(ErrorMessage errorMessage) { throw new ProtocolException(String.Format("ErrorMessage from :{0}",endpoint.Address)); }
比较明确 就是按照协议组装出我们要发回给另一端的信息。从这也能看出Scrape与Announce的不同。
by jiang zhuo on 七.27, 2010, under 源码分析
ListenerBase是所有Listener的基类,平时使用的大多是HttpListener,我们见到的大多数Tracker发布都是通过Http协议的POST、GET实现的。
并且用户向Tracker服务器进行请求的时候形式是有两种的Announce和Scrape 至于他们的不同可以参见《BitTorrent Protocol Specification》其中有详细的介绍。它可向Tracker服务器查询特定的某个torrent或者Tracker服务器全部torrent的信息。这杯称作”scrape page” ,因为它是自动地实现这个繁琐的过程。
ListenerBase的两个事件就是对应的收到Annouce请求和Scrape请求。是EventHandler
基类中的Handle也是一些子类共同的对请求的行为。包括一些抛出异常,一些判断是否为Scrape。等下再介绍ScrapeParameters与AnnounceParameters之间的不同,它们都继承自一个以EventArgs为基类的RequestParameters的处理请求参数的类。
ParseQuery是URL中POST来的各个参数进行分析,得到一个名字与值的集合,选用System.Collections.Specialized中的NameValueCollection实在是再合适不过了。
Start与Stop方法是虚方法,在其派生类中有不同的实现。
Methods #region Methods public virtual BEncodedDictionary Handle(string queryString, IPAddress remoteAddress, bool isScrape) { if (queryString == null) throw new ArgumentNullException("queryString"); return Handle(ParseQuery(queryString), remoteAddress, isScrape); } public virtual BEncodedDictionary Handle(NameValueCollection collection, IPAddress remoteAddress, bool isScrape) { if (collection == null) throw new ArgumentNullException("collection"); if (remoteAddress == null) throw new ArgumentNullException("remoteAddress"); RequestParameters parameters; if (isScrape) parameters = new ScrapeParameters(collection, remoteAddress); else parameters = new AnnounceParameters(collection, remoteAddress); // If the parameters are invalid, the failure reason will be added to the response dictionary if (!parameters.IsValid) return parameters.Response; // Fire the necessary event so the request will be handled and response filled in if (isScrape) RaiseScrapeReceived((ScrapeParameters)parameters); else RaiseAnnounceReceived((AnnounceParameters)parameters); // Return the response now that the connection has been handled correctly. return parameters.Response; } private NameValueCollection ParseQuery(string url) { // The '?' symbol will be there if we received the entire URL as opposed to // just the query string - we accept both therfore trim out the excess if we have the entire URL if (url.IndexOf('?') != -1) url = url.Substring(url.IndexOf('?') + 1); string[] parts = url.Split('&', '='); NameValueCollection c = new NameValueCollection(1 + parts.Length / 2); for (int i = 0; i < parts.Length; i += 2) if (parts.Length > i + 1) c.Add(parts[i], parts[i + 1]); return c; } private void RaiseAnnounceReceived(AnnounceParameters e) { EventHandlerh = AnnounceReceived; if (h != null) h(this, e); } private void RaiseScrapeReceived(ScrapeParameters e) { EventHandler h = ScrapeReceived; if (h != null) h(this, e); } public abstract void Start(); public abstract void Stop(); #endregion Methods
接下来以HttpListener为例进行分析,同时会讲一下Udp的实现。
HttpListener using System; using System.Net; using MonoTorrent.BEncoding; namespace MonoTorrent.Tracker.Listeners { public class HttpListener : ListenerBase { #region Fields private string prefix; private System.Net.HttpListener listener; #endregion Fields #region Properties ////// 如果Listener正在监听则为True /// public override bool Running { get { return listener != null; } } #endregion Properties #region Constructors public HttpListener(IPAddress address, int port) : this(string.Format("http://{0}:{1}/announce/", address, port)) { } public HttpListener(IPEndPoint endpoint) : this(endpoint.Address, endpoint.Port) { } public HttpListener(string httpPrefix) { if (string.IsNullOrEmpty(httpPrefix)) throw new ArgumentNullException("httpPrefix"); this.prefix = httpPrefix; } #endregion Constructors #region Methods /// /// 开始监听 /// public override void Start() { if (Running) return; listener = new System.Net.HttpListener(); listener.Prefixes.Add(prefix); listener.Start(); listener.BeginGetContext(EndGetRequest, listener); } /// /// 停止监听 /// public override void Stop() { if (!Running) return; listener.Close(); listener = null; } private void EndGetRequest(IAsyncResult result) { HttpListenerContext context = null; System.Net.HttpListener listener = (System.Net.HttpListener) result.AsyncState; try { context = listener.EndGetContext(result); HandleRequest(context); } catch(Exception ex) { Console.Write("Exception in listener: {0}{1}", Environment.NewLine, ex); } finally { if (context != null) context.Response.Close(); if (listener.IsListening) listener.BeginGetContext(EndGetRequest, listener); } } private void HandleRequest(HttpListenerContext context) { bool isScrape = context.Request.RawUrl.StartsWith("/scrape", StringComparison.OrdinalIgnoreCase); BEncodedValue responseData = Handle(context.Request.RawUrl, context.Request.RemoteEndPoint.Address, isScrape); byte[] response = responseData.Encode(); context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.ContentLength64 = response.LongLength; context.Response.OutputStream.Write(response, 0, response.Length); } #endregion Methods } }
有.NET类库就是好啊,其这个HttpListener的实现主要依靠的是一个System.Net.HttpListener。
三种形式的构造函数可以传入(IPAddress address, int port)(IPEndPoint endpoint)(string httpPrefix)其目的都是将私有成员prefix设置成传入的传入的Ip与端口的组合即http://{0}:{1}/announce/的形式。
Start方法:首先new一个System.Net.HttpListener对象然后添加上URI然后调用System.Net.HttpListener对象的Start方法,再调用BeginGetContext开始异步检索传入的请求 listener.BeginGetContext(EndGetRequest, listener); 。异步操作通过EndGetRequest来完成。
这里的EndGetRequest函数不光包含了类库中原有的EndGetRequest操作,并且还有最主要的对Request进行的处理(调用HandleRequest函数)。并且是进行Try遇到异常会抛出并且最后总是进行context.Response.Close,保证链接断开,资源释放。
HandleRequest是组织我们发给客户端的Response信息。内容是基类的Handle方法返回的一个BEncode编码的Dictionary转换成的字节数组。
Start具体调用层次如图
Stop方法:处理完当前请求队列后关闭Listener,将Listener置位null
下一篇写些UdpListener
by jiang zhuo on 七.27, 2010, under BT协议
BitTorrent Protocol Specification
比较全面系统的一个英文文档
by jiang zhuo on 七.25, 2010, under BT协议
BT是如何采用激励机制来达到健壮性的
Bram Cohen
[email protected]
2003年5月22日
翻译:小马哥
日期:2004-6-1
修改:扬帆
日期:2005-5-9
概要
BitTorrent 文件发布系统采用针锋相对(tit_for_tat)的方法来达到帕累托有效,与当前已知的协作技术相比,它具有更高的活力。本文将解释BitTorrent 的用途,以及是怎样用经济学的方法来达到这个目标的。
1、BitTorrent 用来做什么?
当通过HTTP协议来下载一个文件的时候,所有的上载开销都在主机上。而使用 BitTorrent,当多个人同时下载同一个文件的时候,他们之间也相互为对方提供文件的部分片断的下载。这样,就把上载的开销分摊到每个下载者那里,也就可以在理论上支持无限多个下载者来下载同一个文件。
研究人员以前也在寻找一种达到这种效果的可实用的技术[3]。这种技术原来并没有在大的范围内运用过,因为逻辑和的问题非常棘手。如果仅仅计算哪些 peers 拥有文件的哪些片断以及这些片断应该被发送给谁,那么很难只产生比较小的系统开销。Peers之间的连接很少会超过几个小时,通常是几分钟而已。最后,有一个普遍的问题,就是公平性。
我们将解释BitTorrent 是如何很好的解决这些问题的。
1.1、BitTorrent接口
BitTorrent 的接口可能是最简单的。用户点击希望下载的文件的超级链接,然后会弹出一个标准的“保存到”对话框。此后,出现一个下载进度的窗口,在这个窗口中,除了显示下载速率外,还显示一个上载速率。BT在使用上非常简单,使得BT能广泛的被运用。
1.2、部署
决定采用BitTorrent的原因是因为有一些文件需要发布。而下载者使用 BitTorrent,是因为这是他们获取所需要的文件的唯一途径。下载者经常一完成下载,就停止为别人上载,虽然说,在BT 客户端完成下载之后,继续为别人提供一段时间的上载是一种礼貌的行为。标准的实现是让客户端一直保持上载,除非窗口被关闭。
在一个典型的部署中,未完成的下载者
一台主机负责提供原始的文件,下载者通过BT来下载这个文件。下载者在下载的同时,为其它人提供上载,最后,离开这个系统。
2、技术框架
2.1发布内容
为了部署 BT,首先将一个扩展名为 .torrent 的文件放在一个普通的web服务器上。.torrent文件包含了要共享的文件的信息,包括文件名、大小、文件的散列信息和一个指向tracker的url。Tracker负责帮助下载者能够获取其它下载者的信息。Tracker和下载者之间使用一种很简单的基于HTTP的协议进行交互,下载者告诉tracker自己要下载的文件、自己使用的端口以及类似的信息,tracker告诉下载者其它下载同样文件的下载者的联系信息。下载者利用这些信息相互之间建立连接。一个被成为“种子”的下载者,必须首先被启动,它知道完整的文件信息。对tracker和web服务器的带宽需求很低,而种子必须至少发送原始文件的一份完整拷贝。
译注:
P2P的核心思想就是没有服务器的概念,任何一个下载者既是client,又是server。
下载者从别人那里取文件的时候,称为下载,而为别人提供文件的时候,称为上载(传)。
为了完成一次部署,至少需要一个 tracker和一个seed。所谓tracker,是一个服务器,负责帮助peers之间相互建立连接。而seed,通常是第一个向tracker注册,然后它就开始进入循环,等待为别人提供文件,也就是说,第一个seed只负责上传文件。一旦有一个peer向tracker注册后,就可以取得seed的信息,从而与seed建立连接。从seed处读取文件。由于原始的文件,只有seed拥有,所有说,seed至少要上传原始文件的一份完整拷贝。如果又有一个peer加入进来,那么它可以同时和seed和前一个peer建立连接,然后从这两者处获取文件。
2.2对等发布
所有和文件下载相关的逻辑问题,通过 peers之间的交互来解决。一些关于下载和上传的速率的信息被发送给tracker,tracker搜集这些信息用于统计。Tracker的职责被严格限定为“帮助peers相互发现对方”。
尽管tracker是peers之间相互发现的唯一途径,也是peers之间相互协作的唯一地点,标准的tracker算法返回一个随机的 peers的列表。随机图具有非常强大的特性,许多的peer选择算法最终产生了一个幂律图,幂律图能以少量的搅拌来获得分片。注意,peers之间的连接都是双向传输的。
为了跟踪每个peers都拥有什么,BT将文件切割为固定大小的片(典型的大小是256k)。每个下载者必须通知其它peers,它拥有哪些片。为了验证文件的完整性,对每个片断都通过SHA1算法计算出它的hash信息,并保存在torrent文件中。Peers只有在检查了片断的完整性之后,才会通知其它peers它拥有这个片断。删除代码是一种被建议使用的帮助文件分布的技术,但是这种更简单的方法(既分片)也是可用的。
Peers不断的从它能连接到的peers那里下载文件片断。当然,它不能从没有跟它建立连接的peers那里下载任何东西。即使是建立了连接的peers,有的也并不包含它想要的片断,或者还不允许它去下载。关于不允许其它peers从它那里下载文件片断的策略,被称为 阻塞choking,后文将进行讨论。其它关于文件分布的方法通常都要用到麻烦的树结构,而且树叶的上载能力并没有被利用起来。简单的让 peers 宣布它拥有什么会导致不到 10 % 的带宽开支,却可以可靠的使用所有的上载能力。
2.3流水作业
构架在TCP之上的应用层协议,例如BT,很重要的一点是应该同时发送多个请求,以避免在两个片断发送之间的延迟,因为那样会严重影响传输速率。为了达到这种目的,BT将每个片断又进一步分为子片断,每个子片断的大小一般是16k,同时,它一直保持几个请求(通常是5个)被流水的同时发送。流水作业所选择的data(应该是指的同时发送的请求数目,也就是5个request)的依据是能使得大多数连接变得饱和。
译注:也就是说,每次发送5个请求,然后过一段时间,又发送5个请求。流水作业在HTTP 协议1.1版本中被广泛运用。
2.4片断选择
选择一个好的顺序来下载片断,对提高性能非常重要。一个差的片断选择算法可能导致所有的片断都处于下载中,或者另一种情况,没有任何片断被上载给其它 peers。
2.4.1严格的优先级
片断选择的第一个策略是:一旦请求了某个片断的子片断,那么该片断剩下的子片断优先被请求。这样,可以尽可能快的获得一个完整的片断
2.4.2最少的优先
对一个下载者来说,在选择下一个被下载的片断时,通常选择的是它的peers们所拥有的最少的那个片断,也就是所谓的“最少优先”。这种技术,确保了每个下载者都拥有它的peers们最希望得到的那些片断,从而一旦有需要,上载就可以开始。这也确保了那些越普通的片断越放在最后下载,从而减少了这样一种可能性,即某个peer当前正提供上载,而随后却没有任何的被别人感兴趣的片断了。
译注:
也就说说,每个peer都优先选择整个系统中最少的那些片断去下载,而那些在系统中相对较多的片断,放在后面下载,这样,整个系统就趋向于一种更优的状态。如果不用这种算法,大家都去下载最多的那些片断,那么这些片断就会在系统中分布的越来越多,而那些在系统中相对较少的片断仍然很少,最后,某些 peer 就不再拥有其它 peer 感兴趣的片断了,那么系统的参与者越来越少,整个系统的性能就下降。
在BT系统中,充分考虑了经济学的概念,处处从整个系统的性能出发,参与者越多,系统越优化。
信息理论显示除非种子上传了文件的所有片断,否则没有任何下载者可以完成所有文件的下载。如果在一个部署中,只有一个种子,而且种子的上载能力比它的大多数下载者都要差,那么,不同的下载者从种子那里下载不同的片断,性能就会变得比较好,因为,重复的下载浪费了种子获取更多信息的机会。“最少优先”使得下载者只从种子处下载新的片断(也就是整个系统中其它peer都没有的片断),因为,下载者能够看到其它peers那里已经有了种子已经上传的片断。
在某些部署中,原始的种子由于某些原因最终关闭,只好由剩下的这些下载者们来负责上传。这样显然会带来一个风险:某些片断任何一个下载者都不拥有。“最少优先”也很好的处理了这种情况。通过尽快的复制最少的片断,减少了这种由于当前的peers停止上载后带来的风险。
2.4.3随机的第一个片断
“最少优先”的一个例外是在下载刚开始的时候。此时,下载者没有任何片断可供上传,所以,需要尽快的获取一个完整的片断。而最少的片断,通常只有某一个peer拥有,所以,它可能比多个peers都拥有的那些片断下载的要慢。因此,第一个片断是随机选择的,直到第一个片断下载完成,才切换到“最少优先”的策略。
2.4.4最后阶段模式
有时候,从一个速率很慢的peer那里请求一个片断。在下载的中间阶段,这不是什么问题,但是却可能潜在的延迟下载的完成。为了防止这种情况,在最后阶段,peer向它的所有的peers们都发送某片断的子片断的请求,一旦某些子片断到了,那么就会向其它peer发送cancel 消息,取消对这些子片断的请求,以避免带宽的浪费。实际上,用这种方法并没有浪费多少带宽,而文件的结束部分也一直下载的非常快。
3 阻塞(choking)算法
BT并不集中分配资源。每个peer自己有责任来尽可能的提高它的下载速率。Peers从它可以连接的peers处下载文件,并根据对方提供的下载速率给予同等的上传回报(你敬我一尺,我敬你一丈)。对于合作者,提供上传服务,对于不合作的,就阻塞对方。所以说,阻塞是一种临时的拒绝上传策略,虽然上传停止了,但是下载仍然继续。在阻塞停止的时候,连接并不需要重新建立。
阻塞算法并不属于BT对等协议(指peers 之间交互的协议)的技术部分,但是对提高性能是必要的。一个好的阻塞算法应该利用所有可用的资源,为所有下载者提供一致可靠的下载速率,并适当惩罚那些只下载而不上传的peers。
3.1帕累托有效
帕累托有效是指资源配置已达到这样一种境地,即任何重新改变资源配置的方式,都不可能使一部分人在没有其他人受损的情况下受益。这一资源配置的状态,被称为“帕累托最优”(Pareto optimum)状态,或称为“帕累托有效”(Pareto efficient)
在计算机领域,寻求帕累托有效是一种本地优化算法
BitTorrent的阻塞算法,用一种针锋相对的方式来试图达到帕累托最优。(原文不太好翻译,我简化了)。Peers对那些向他提供上传服务的peers给予同样的回报,目的是希望在任何时候都有若干个连接正在进行着双向传输。
3.2 BitTorrent的阻塞算法
从技术层面上说,BT的每个peer一直与固定数量的其它 peers 保持疏通(通常是4个),所以问题就变成了哪些peers应该保持疏通?这种方法使得TCP的拥塞控制性能能够可靠的饱和上传容量。(也就是说,尽量让整个系统的上传能力达到最大)。
严格的根据当前的下载速率来决定哪些peers应该保持疏通。令人惊讶的是,计算当前下载速率是个大难题。当前的实现实质上是一个每隔20秒的轮询。而原来的算法是对一个长时间的网络传输进行总计,但这种方法很差劲,因为由于资源可用或者不可用,带宽会变化的很快。
为了避免因为频繁的阻塞和疏通 peers造成的资源浪费,BT每隔10秒计算一次哪个peer需要被阻塞,然后将这种状态保持到下一个10秒。10秒已经足够使得TCP来调整它的传输性能到最大。
3.3、optimistic unchoking
如果只是简单的为提供最好的下载速率的peers们提供上载,那么就没有办法来发现那些空闲的连接是否比当前正使用的连接更好。为了解决这个问题,在任何时候,每个peer都拥有一个称为“optimistic unchoking”的连接,这个连接总是保持疏通状态,而不管它的下载速率是怎样。每隔30秒,重新计算一次哪个连接应该是“optimistic unchoking”。30秒足以让上载能力达到最大,下载能力也相应的达到最大。这种和针锋相对类似的思想非常的伟大。“optimistic unchoking”非常和谐的与“囚徒困境”合作。
3.4、反对歧视
某些情况下,一个peer可能被它所有的peers都阻塞了,这种情况下,它将会保持较低的下载速率直到通过“optimistic unchoking”找到更好peers。为了减轻这种问题,如果一段时间过后,从某个peer那里一个片断也没有得到,那么这个peer认为自己被对方“怠慢”了,于是不再为对方提供上传,除非对方是“optimistic unchoking”。这种情况频繁发生,会导致多于一个的并发的“optimistic unchoking”。
3.5仅仅上传
一旦某个peer完成了下载,它不能再通过下载速率(因为下载速率已经为0了)来决定为哪些 peers 提供上载了。目前采用的解决办法是,优先选择那些从它这里得到更好的上载速率的peers。这样的理由是可以尽可能的利用上载带宽。
4、真实世界的体验
BitTorrent不仅仅早已经实现,而且早已经被广泛的使用,它为许多并发的下载者提供成百兆的文件下载。已知的最大的部署中,同时有超过1000个的下载者。当前的瓶颈(实际还没有达到)看来是tracker的带宽。它(trakcer的带宽)大概占用了带宽总量的千分之一,一些小的协议扩展可能会使它降到万分之一。
参考资料:
[1] E. Adar and B. A. Huberman. Free riding on gnutella. First Monday, 5(10), 2000.
[2] A.-L. Barab´asi. Linked: The New Science of Networks.Perseus Publishing, 2002.
[3] M. Castro, P. Druschel, A.-M. Kermarrec, A. Nandi, A. Rowstron, and A. Singh. Splitstream: High-bandwidth content distribution in cooperative environments. In Proceedings of IPTPS03, Berkeley, USA, Feb. 2003.
[4] P. Maymounkov and D. Mazieres. Kademlia: A peer-to-peer information system based on the xormetric. In Proceedings of IPTPS02, Cambridge, USA, Mar. 2002.
by jiang zhuo on 七.25, 2010, under BT协议
通常BT客户端每几分钟就要向tracker发送一次请求.对于一些比较大的BT站点,其tracker的压力是可想而知的.降低tracker的压力首先考虑到的当然是采用更低网络开销的udp协议.于是Bittorrent udp-tracker protocol应运而生.
这个协议很简单.
下面是实现它的封装类:
// UDPTrackerClient.h: interface for the CUDPTrackerClient class.
//
//
#if !defined(AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_)
#define AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <WINSOCK2.H>
#pragma comment(lib, "ws2_32.lib")
#ifndef _DISABLEWARNING4786_4355
#define _DISABLEWARNING4786_4355
#pragma warning( disable : 4786 )
#pragma warning( disable : 4355 )
#endif
#ifndef _ENABLEUSESTL
#define _ENABLEUSESTL
#include <LIST>
#include <SET>
#include <VECTOR>
#include <QUEUE>
#include <STRING>
#include <MAP>
using namespace std;
#endif
class CPeerHostInfo
{
public:
DWORD IP;//节点IP
WORD Port;//节点端口
};
class CUDPTrackerClient
{
public:
CUDPTrackerClient();
virtual ~CUDPTrackerClient();
void CancelSocketOperate();
BOOL Connect(const char * szServer,WORD wPort = 0);
DWORD Announcing(BYTE* pInfoHash,BYTE * pPeerID,
__int64 idownloaded,__int64 ileft,__int64 iuploaded,
int ievent,
DWORD dwIP,WORD wPort);
BOOL Disconnect();
public:
SOCKET m_socket;
DWORD m_dwIP;
WORD m_wPort;
__int64 m_iConnection_id;
DWORD m_dwConnectTick;
string m_strError; //如果请求失败,此变量保存错误信息
DWORD m_dwDonePeers; //种子数
DWORD m_dwNumPeers; //当前下载者个数
DWORD m_dwInterval; //查询间隔时间
list m_listPeers;
};
#endif // !defined(AFX_UDPTRACKERCLIENT_H__69B6ACC8_8193_4680_81D8_925B1550E92C__INCLUDED_)
// UDPTrackerClient.cpp: implementation of the CUDPTrackerClient class.
//
//
#include "stdafx.h"
#include "UDPTrackerClient.h"
#include "DataStream.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//
// Construction/Destruction
//
#define RECVBUFSIZE 2048
CUDPTrackerClient::CUDPTrackerClient()
{
m_socket = INVALID_SOCKET;
m_iConnection_id = 0;
m_dwConnectTick = 0;
m_dwIP = 0;
m_wPort = 0;
m_dwDonePeers = 0; //种子数
m_dwNumPeers = 0; //当前下载者个数
m_dwInterval = 0; //查询间隔时间
}
CUDPTrackerClient::~CUDPTrackerClient()
{
Disconnect();
}
void CUDPTrackerClient::CancelSocketOperate()
{
if(m_socket != INVALID_SOCKET)
{
LINGER lingerStruct;
// If we’re supposed to abort the connection, set the linger value
// on the socket to 0.
lingerStruct.l_onoff = 1;
lingerStruct.l_linger = 0;
setsockopt(m_socket, SOL_SOCKET, SO_LINGER,
(char *)&lingerStruct, sizeof(lingerStruct) );
}
}
BOOL CUDPTrackerClient::Disconnect()
{
m_iConnection_id = 0;
m_dwDonePeers = 0; //种子数
m_dwNumPeers = 0; //当前下载者个数
m_dwInterval = 0; //查询间隔时间
if ( m_socket != INVALID_SOCKET )
{
m_dwIP = 0;
m_wPort = 0;
// Now close the socket handle. This will do an abortive or
// graceful close, as requested.
shutdown(m_socket,SD_BOTH);
closesocket(m_socket);
m_socket = INVALID_SOCKET;
return TRUE;
}
return FALSE;
}
//szServer连接的主机,可以是下列形式的字符串:
//easeso.com:1000
//easeso.com
//如果wPort不为0,则szServer不应该包含端口信息
BOOL CUDPTrackerClient::Connect(const char * szServer,WORD wPort)
{
m_strError = "";
BOOL bRes = FALSE;
if ( m_socket == INVALID_SOCKET )
{
//用UDP初始化套接字
BOOL optval = TRUE;
m_socket =socket(AF_INET,SOCK_DGRAM,0);
if(m_socket == INVALID_SOCKET)
return FALSE;
//设置超时时间
int TimeOut=10000;
int err = setsockopt (m_socket, SOL_SOCKET,SO_RCVTIMEO,(CHAR *) &TimeOut,sizeof (TimeOut));
}
if(m_dwIP == 0)
{
CString strServer = szServer;
CString strHost;
if(wPort == 0)
{
int iNext = strServer.Find(‘:’);
if(iNext>0)
{
strHost = strServer.Mid(0,iNext);
CString strPort = strServer.Mid(iNext+1);
m_wPort = (WORD)atoi(strPort);
}
else
strHost = strServer;
}
else
{
strHost = strServer;
m_wPort = wPort;
}
if(m_wPort == 0)
m_wPort = 80;
//Check if address is an IP or a Domain Name
int a = strHost[0];
if (a > 47 && a < 58)
m_dwIP = inet_addr(strHost);
else
{
struct hostent *pHost;
pHost = gethostbyname(strHost);
if(pHost != NULL)
m_dwIP = *((ULONG*)pHost->h_addr);
else
m_dwIP = 0;
}
}
if((GetTickCount()-m_dwConnectTick)>30000)
{
m_dwConnectTick = 0;
m_iConnection_id = 0;
}
if(m_socket != INVALID_SOCKET && m_dwIP && m_wPort && m_iConnection_id ==0)
{
DWORD dwTransaction_id = GetTickCount();
SOCKADDR_IN from;
int fromlength=sizeof(SOCKADDR);
char buf[RECVBUFSIZE];
from.sin_family=AF_INET;
from.sin_addr.s_addr=m_dwIP;
from.sin_port=htons(m_wPort);
CDataStream sendstream(buf,2047);
sendstream.clear();
__int64 iCID = 0×41727101980;
sendstream.writeint64(CNetworkByteOrder::convert(iCID));
sendstream.writedword(CNetworkByteOrder::convert((int)0));
sendstream.writedword(dwTransaction_id);
int iRes = 0;
int iTimes = 6;
while(iTimes>0&&m_dwIP)
{
sendto(m_socket,sendstream.getbuffer(),sendstream.size(),0,(struct sockaddr FAR *)&from,sizeof(from));
iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength);
if(iRes >=0)
break;
iTimes–;
}
if(iRes>=16)
{
CDataStream recvstream(buf,RECVBUFSIZE-1);
DWORD dwAction = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword());
DWORD dwTIDResp= recvstream.readdword();
if(dwTIDResp == dwTransaction_id)
{
if(dwAction == 0)
{
m_iConnection_id = recvstream.readint64();
//BitComet将回复0×16字节数据,最后6字节是服务器查看到的本地IP和UDP端口
}
else if(dwAction == 3)//得到一个错误信息包
{
buf[iRes]=0;
m_strError = recvstream.readstring();
}
}
}
}
if(m_iConnection_id)
bRes = TRUE;
return bRes;
}
//提交请求
//pInfoHash 20字节的数据缓冲区指针
//pPeerID 20字节的数据缓冲区指针
//ievent参数值:
//none = 0
//completed = 1
//started = 2
//stopped = 3
DWORD CUDPTrackerClient::Announcing(BYTE* pInfoHash,BYTE * pPeerID,
__int64 idownloaded,__int64 ileft,__int64 iuploaded,
int ievent,
DWORD dwIP,WORD wPort)
{
m_listPeers.clear();
m_dwNumPeers = 0;
m_dwDonePeers = 0;
m_strError = "";
DWORD dwReturnCode = 0;
if(m_iConnection_id && m_socket != INVALID_SOCKET && m_dwIP & m_wPort)
{
DWORD dwTransaction_id = GetTickCount();
//srand(dwTransaction_id);
//DWORD dwKey = rand();
DWORD dwKey = 0×3753;
SOCKADDR_IN from;
int fromlength=sizeof(SOCKADDR);
char buf[RECVBUFSIZE];
from.sin_family=AF_INET;
from.sin_addr.s_addr=m_dwIP;
from.sin_port=htons(m_wPort);
CDataStream sendstream(buf,RECVBUFSIZE-1);
sendstream.clear();
sendstream.writeint64(m_iConnection_id);
sendstream.writedword(CNetworkByteOrder::convert((int)1));
sendstream.writedword(dwTransaction_id);
sendstream.writedata(pInfoHash,20);
sendstream.writedata(pPeerID,20);
sendstream.writeint64(CNetworkByteOrder::convert(idownloaded));
sendstream.writeint64(CNetworkByteOrder::convert(ileft));
sendstream.writeint64(CNetworkByteOrder::convert(iuploaded));
sendstream.writedword(CNetworkByteOrder::convert(ievent));
sendstream.writedword(dwIP);
sendstream.writedword(CNetworkByteOrder::convert((int)dwKey));
sendstream.writedword(CNetworkByteOrder::convert((int)200));
sendstream.writedword(CNetworkByteOrder::convert(wPort));
int iRes = 0;
int iTimes = 2;
while(iTimes>0&&m_dwIP)
{
sendto(m_socket,sendstream.getbuffer(),sendstream.size(),0,(struct sockaddr FAR *)&from,sizeof(from));
iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength);
if(iRes >=0)
break;
iTimes–;
}
if(iRes>=20)
{
CDataStream recvstream(buf,RECVBUFSIZE-1);
DWORD dwAction = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword());
DWORD dwTIDResp= recvstream.readdword();
if(dwTIDResp == dwTransaction_id)
{
if(dwAction == 1)
{
m_dwInterval = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword());
m_dwNumPeers = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword());
m_dwDonePeers = (DWORD)CNetworkByteOrder::convert((int)recvstream.readdword());
CPeerHostInfo hi;
for(int iCurPos = 20;iCurPos+6<=iRes;iCurPos+=6)
{
hi.IP= recvstream.readdword();
hi.Port = (WORD)CNetworkByteOrder::convert((unsigned short)recvstream.readword());
m_listPeers.push_back(hi);
}
if(m_dwNumPeers>m_listPeers.size())
{
iRes = 0;
iTimes = 6;
while(iTimes>0&&m_dwIP)
{
iRes = recvfrom(m_socket,buf,RECVBUFSIZE-1,0,(struct sockaddr FAR *)&from,(int FAR *)&fromlength);
if(iRes >=0)
break;
iTimes–;
}
if(iRes>=6)
{
for(iCurPos = 0;iCurPos+6<=iRes;iCurPos+=6)
{
hi.IP= recvstream.readdword();
hi.Port = (DWORD)CNetworkByteOrder::convert((int)recvstream.readword());
m_listPeers.push_back(hi);
}
}
}
m_dwNumPeers = m_listPeers.size();
dwReturnCode = 200;
}
else if(dwAction == 3)//得到一个错误信息包
{
buf[iRes]=0;
m_strError = recvstream.readstring();
dwReturnCode = 400;
}
}
}
}
//每次都要求重新连接
m_iConnection_id = 0;
return dwReturnCode;
}
// DataStream.h: interface for the CDataStream class.
//
//
#if !defined(AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_)
#define AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include
//数据流操作函数
class CDataStream
{
public :
CDataStream(char * szBuf,int isize)
{
m_isize = isize;
buffer = szBuf;
current = buffer;
}
~CDataStream()
{
}
void clear()
{
current = buffer;
current[0]=0;
}
//此函数不动态增加内存,一次打印的数据长度不应该超过缓冲区的三分之一,否则可能导致失败
bool printf(const char * format,…)
{
if(current)
{
if(current – buffer > (m_isize*2)/3)
return false;
va_list argPtr ;
va_start( argPtr, format ) ;
int count = vsprintf( current, format, argPtr ) ;
va_end( argPtr );
current += count ;
return true;
}
return false;
}
//此函数拷贝字符串
bool strcpy(const char * szStr)
{
if(current&&szStr)
{
int ilen = lstrlen(szStr);
if((m_isize-(current – buffer)) < (ilen +2))
return false;
memcpy(current,szStr,ilen+1);
current += ilen;
return true;
}
return false;
}
char * getcurrentpos()
{
return current;
}
void move(int ilen)//当前指针向后移动ilen
{
current += ilen;
}
void reset()
{
current = buffer;
}
BYTE readbyte()
{
current ++;
return *(current-1);
}
void writebyte(BYTE btValue)
{
*current = btValue;
current ++;
}
WORD readword()
{
current +=2;
return *((WORD*)(current-2));
}
void writeword(WORD wValue)
{
*((WORD*)current) = wValue;
current +=2;
}
DWORD readdword()
{
current +=4;
return *((DWORD*)(current-4));
}
void writedword(DWORD dwValue)
{
*((DWORD*)current) = dwValue;
current +=4;
}
__int64 readint64()
{
current +=8;
return *((__int64*)(current-8));
}
void writeint64(__int64 iValue)
{
*((__int64*)current) = iValue;
current +=8;
}
BYTE * readdata(DWORD dwLen)
{
current +=dwLen;
return (BYTE*)(current-dwLen);
}
void writedata(BYTE * pData,DWORD dwLen)
{
memcpy(current,pData,dwLen);
current +=dwLen;
}
char * readstring()
{
char * szRes = current;
int ilen = lstrlen(current);
current +=(ilen+1);
return szRes;
}
int size()
{
return (int)(current-buffer);
}
const char * getbuffer(){return buffer;}
private :
char* buffer;
char* current;
int m_isize;
};
class CNetworkByteOrder
{
public:
static unsigned short int convert(unsigned short int iValue)
{
unsigned short int iData;
((BYTE*)&iData)[0] = ((BYTE*)&iValue)[1];
((BYTE*)&iData)[1] = ((BYTE*)&iValue)[0];
return iData;
}
static int convert(int iValue)
{
int iData;
((BYTE*)&iData)[0] = ((BYTE*)&iValue)[3];
((BYTE*)&iData)[1] = ((BYTE*)&iValue)[2];
((BYTE*)&iData)[2] = ((BYTE*)&iValue)[1];
((BYTE*)&iData)[3] = ((BYTE*)&iValue)[0];
return iData;
}
static __int64 convert(__int64 iValue)
{
__int64 iData;
((BYTE*)&iData)[0] = ((BYTE*)&iValue)[7];
((BYTE*)&iData)[1] = ((BYTE*)&iValue)[6];
((BYTE*)&iData)[2] = ((BYTE*)&iValue)[5];
((BYTE*)&iData)[3] = ((BYTE*)&iValue)[4];
((BYTE*)&iData)[4] = ((BYTE*)&iValue)[3];
((BYTE*)&iData)[5] = ((BYTE*)&iValue)[2];
((BYTE*)&iData)[6] = ((BYTE*)&iValue)[1];
((BYTE*)&iData)[7] = ((BYTE*)&iValue)[0];
return iData;
}
};
#endif // !defined(AFX_DATASTREAM_H__D90A2534_EA73_4BEA_8B7E_87E59A3D1D26__INCLUDED_)
by jiang zhuo on 七.25, 2010, under BT协议
Bittorrent udp-tracker protocol extension
Contents
introduction
connecting
Client sends packet:
Server replies with packet:
announcing
Client sends packet:
Server replies with packet:
scraping
Client sends packet:
Server replies with packet:
errors
server replies packet:
actions
extensions
authentication
credits
introduction
A tracker with the protocol "udp://" in its URI is supposed to be contacted using this protocol.
This protocol is supported by xbt-tracker.
For additional information and descritptions of the terminology used in this document, see the protocol specification
All values are sent in network byte order (big endian). The sizes are specified with ANSI-C standard types.
If no response to a request is received within 15 seconds, resend the request. If no reply has been received after 60 seconds, stop retrying.
connecting
Client sends packet:size name description
int64_t connection_id Must be initialized to 0×41727101980 in network byte order. This will identify the protocol.
int32_t action 0 for a connection request
int32_t transaction_id Randomized by client.
Server replies with packet:size name description
int32_t action Describes the type of packet, in this case it should be 0, for connect. If 3 (for error) see errors.
int32_t transaction_id Must match the transaction_id sent from the client.
int64_t connection_id A connection id, this is used when further information is exchanged with the tracker, to identify you. This connection id can be reused for multiple requests, but if it’s cached for too long, it will not be valid anymore.
announcing
Client sends packet:size name description
int64_t connection_id The connection id acquired from establishing the connection.
int32_t action Action. in this case, 1 for announce. See actions.
int32_t transaction_id Randomized by client.
int8_t[20] info_hash The info-hash of the torrent you want announce yourself in.
int8_t[20] peer_id Your peer id.
int64_t downloaded The number of byte you’ve downloaded in this session.
int64_t left The number of bytes you have left to download until you’re finished.
int64_t uploaded The number of bytes you have uploaded in this session.
int32_t event The event, one of
none = 0
completed = 1
started = 2
stopped = 3
uint32_t ip Your ip address. Set to 0 if you want the tracker to use the sender of this udp packet.
uint32_t key A unique key that is randomized by the client.
int32_t num_want The maximum number of peers you want in the reply. Use -1 for default.
uint16_t port The port you’re listening on.
uint16_t extensions See extensions
Server replies with packet:size name description
int32_t action The action this is a reply to. Should in this case be 1 for announce. If 3 (for error) see errors. See actions.
int32_t transaction_id Must match the transaction_id sent in the announce request.
int32_t interval the number of seconds you should wait until reannouncing yourself.
int32_t leechers The number of peers in the swarm that has not finished downloading.
int32_t seeders The number of peers in the swarm that has finished downloading and are seeding.
The rest of the server reply is a variable number of the following structure:
size name description
int32_t ip The ip of a peer in the swarm.
uint16_t port The peer’s listen port.
scraping
Client sends packet:size name description
int64_t connection_id The connection id retreived from the establishing of the connection.
int32_t action The action, in this case, 2 for scrape. See actions.
int32_t transaction_id Randomized by client.
int16_t num_info_hashes The number of info-hashes that will follow.
uint16_t extensions See extensions.
The following structure is repeated num_info_hashes times:
size name description
int8_t[20] info_hash The info hash that is to be scraped.
Server replies with packet:size name description
int32_t action The action, should in this case be 2 for scrape. If 3 (for error) see errors.
int32_t transaction_id Must match the sent transaction id.
The rest of the packet contains the following structures once for each info-hash you asked in the scrape request.
size name description
int32_t complete The total number of completed downloads.
int32_t downloaded The current number of connected seeds.
int32_t incomplete The current number of connected leechers.
errors
In case of a tracker error,
server replies packet:size name description
int32_t action The action, in this case 3, for error. See actions.
int32_t transaction_id Must match the transaction_id sent from the client.
int8_t[] error_string The rest of the packet is a string describing the error.
actions
The action fields has the following encoding:
connect = 0
announce = 1
scrape = 2
error = 3 (only in server replies)
extensions
The extensions field is a bitmask. The following bits are assigned:
1 = authentication.
authenticationThe packet will have an authentication part appended to it. It has the following format:
size name description
int8_t username_length The number of characters in the username.
int8_t[] username The username, the number of characters as specified in the previous field.
uint8_t[8] passwd_hash sha1(packet + sha1(password)) The packet in this case means the entire packet except these 8 bytes that are the password hash. These are the 8 first bytes (most significant) from the 20 bytes hash calculated.
credits
Protocol designed by Olaf van der Spek
by jiang zhuo on 七.25, 2010, under 转载
这个翻译版本由孤波独立完成
原文见http://bitconjurer.org/BitTorrent/protocol.html
作者Bram Cohen
孤波享有对该翻译版本解释权修改权
非商业引用请注明译者
BitTorrent协议详解
BitTrrent(简称BT,比特洪流)是一个文件分发协议,它通过URL识别内容并且和网络无缝结合。它在HTTP平台上的优势在于,同时下在一个文件的下载者在下载的同时不断互相上传数据,使文件源可以在很有限的负载增加的情况下支持大量下载者同时下载。
一个BT式文件分发需要以下实体:
·一个普通网络服务器
·一个静态元信息文件
·一个BT Tracker
·一个“原始”下载者
·网络终端浏览者
·网络终端下载者
这里假设理想情况下一个文件有多个下载者。
架设一个BT服务器步骤如下:
1.开始运行Tracker(已运行的跳过这一步);
2.开始运行普通网络服务器端程序,如Apache,已运行的跳过这一步;
3.在网络服务器上将.torrent文件关联到Mimetype类型application/x-bittorrent(已关联的跳过这一步);
4.用要发布的完整文件和Tracker的URL创建一个元信息文件(.torrent文件);
5.将元信息文件放置在网络服务器上;
6.在网页上发布元信息文件(.torrent文件)链接;
7.原始下载者提供完整的文件(原本)。
通过BT下载步骤如下:
1.安装BT客户端程序(已安装的跳过这一步);
2.上网;
3.点击一个链到.torrent文件的链接;
4.选择本地存储路径,选定需要下载的文件(对有选择下载功能的BT客户端用户);
5.等待下载完成;
6.用户退出下载(之前下载者不停止上传)。
连接状况如下:
·网站正常提供静态文件连接,并且启动客户端上的BT程序;
·Tracker即时接收所有下载者信息,并且给每个下载者一份随机的peer列表。通过HTTP或HTTPS协议实现;
·下载者每隔一段时间连一次Tracher,告知自己的进度,并和那些已经直接连接上的peer进行数据的上传下载。这些连接遵循BitTorrent peer协议,通过TCP协议进行通信。
·原始下载者只上传不下载,他拥有整个文件,所以很必要向网络中传输完文件的所有部分。在一些人气很旺的下载中,原始下载者经常可以在较短的时间内退出上传,由其它已经下载到整个文件的下载者继续提供上传。
元信息文件和Tracker的回应信息都以一种简单高效可扩展的格式(Bencoding,B编码)传送。B编码过的信息就是以包含字符串和整型数据的字典和列表的嵌套(像在Python中一样),可扩展性是指可以通过减少字典忽略的关键值来添加新的特性。
B编码规则如下:
·字符串表示为十进制数的既定字符串长度加冒号再跟原字符串。
如4:spam就相当于’spam’。
·整型数据表示成前面加’i'后面加’e'中间是十进制数,如i3e就相当于3,i-3e就是-3。整型数据没有长度限制。i-0e无效,所有以’i0′开头的除了代表0的i0e,其它都无效。
·列表编码为一个’l'开头后面跟它所包含的项目(已经编码过)最后加一个’e',比如l4:spam4:eggse就等于['spam', 'eggs']。
·字典编码为一个’d'开头后面跟一个交替关键值(key)及其对应值的列表最后加一个’e'。
如:d3:cow3:moo4:spam4:eggse相当于{‘cow’: ‘moo’, ‘spam’: ‘eggs’}
d4:spaml1:a1:bee相当于{‘spam’: ['a', 'b']}
关键值必须是处理过的字符串(用原始字符串编码的,而且不是数字字母混合编码的)。
元信息文件就是B编码的有以下关键值的字典:
announce(声明)
Tracker的URL。
info(信息)
此关键值对应一个字典包含以下描述的关键值:
关键值name对应一个字符串,代表默认的下载文件或存成目录的名字。它是纯粹建议性的。
关键值piece length(块长)对应文件分割成的块的字节数。出于传输需要,文件被分割成大小相等的块,除了最后一块通常会小一些。块长一般来说是2的权值,大部分设块长为256K(2的18次幂)。
关键值pieces(块)对应一个字符串,此字符串长度是20的倍数。它可以再分成每20字节一段的多个字符串,分别对应块在索引中的SHA1校验码(hash)。
还有关键值length(长度)和files(文件),它们不能同时出现也不能都不出现。当length出现说明这个元信息文件只是单文件下载,否则说明是多文件的目录结构下载。
单文件情况下,length对应文件长度的字节数。
多文件情况被看作是把许多单文件按文件列表中的顺序连成一个大文件下载,而关键值files就对应文件列表,是一个字典的列表,其中每个字典又包含以下关键值:
length(长度)
文件长度的字节数。
path(路径)
一个包含字符串的列表,字符串就是子目录名,最后一项的字符串是文件名。
(一个长度为零的length表单是错误的。)
在单文件情况下,关键值name是文件名;多文件情况下,它就成了目录名。
Tracker质询是双向的。Tracker通过HTTP GET参数获得信息,然后返回一个B编码后的信息。尽管Tracker需要在服务器端执行,但它运行流畅像Apache的一个模块。
Tracker的GET请求有如下关键值:
info_hash
20字节长的SHA1验证码,来自B编码过的元信息文件中的info值下,是元信息文件的一个支链。这个值是自动转换的。
peer_id
一个20字节长的字符串,是每个用户开始下载时随机生成的ID。这个值也是是自动转换的。
ip
一个可选择的参数给出peer所在的IP(或DNS主机名),一般是和Tracker同机器的原始下载者得到后以便散发文件。
port
监听端口,官方默认的是从6881端口开始试,如果端口被占用则依次向后推一个端口找空闲端口,到6889端口为止。
uploaded
目前总上传量,编码为十进制ASCII码。
downloaded
目前总下载量,编码为十进制ASCII码。
left
未下载的字节数,编码为十进制ASCII码。这个数不是通过文件长度和已下载数算出来的,因为文件可能在被续传,还有一些已经下载的数据不能通过完整性检查必须重新下载。
event
这是个选择性的关键值,选项有started,completed或stopped(或empty,等同于没有运行)。如果没有运行,这个声明会定期间隔一定时间发出。开始下载时发出started值,完成下载时发出completed。当文件完整后再开始,没有completed发出,下载者中止下载时发出stopped。
Tracker的回应也是B编码字典。如果Tracker回应中有关键值failure reason(失败原因),就会对应一个人可以读懂的字符串信息解释质询失败的原因,不需要其它关键值。否则,回应必须有两个关键值:interval(间隔)对应下载者定期发出请求的间隔秒数;peers,peer自选ID,IP地址或DNS主机名的字符串和端口号。记住peers不会完全按照计划的间隔发送请求,假如他们发生一个事件或者想要更多的peers。
如果你想对元信息文件或者Tracker质询进行扩展,请与Bram Cohen进行协调,确保所有扩展都兼容。
BitTorrent peer协议通过TCP协议进行操作。它不用调节任何socket选项就可以流畅运行。
peer之间的连接是对称的。两个方向送出的信息要协调一致,数据可以流入任一方。
peer协议指一个peer从零开始下载,每得到元信息文件索引中所描述的一个块且验证码一致,就向所有peer声明已得到此块。
连接的两个终端有2个状态指标,被阻塞与否,被关注与否,被阻塞(choking)是表明在恢复通畅之前数据不再发出的通知。发生阻塞的原因和技术问题稍后会提到。
数据传输发生在一方关注对方且对方没有阻塞的情况下。关注状态必须一致保持-如果一个没阻塞的peer没有别人需要的数据,别人对他就会失去关注,转而关注那些正在阻塞的peer。完全执行这种条件需要非常慎重,但这样的确可以让下载者知道哪些peer在阻塞消失后可以马上开始下载。
连接会逐渐断开不感兴趣和阻塞的peer。
当数据传输时,下载者要备好多份请求排成队列,以获得较高的TCP传输效率(这叫“管运请求”)。另一方面,不能被写入TCP缓冲区的请求要被立即排入内存,而不是一个应用程序级的网络缓冲,一旦阻塞出现,这些请求全部丢弃。
peer连线协议包括一次握手跟着不断的大小一致且确定的信息流。握手的开始是字符十九(十进制),跟着是字符串’BitTorrentprotocol’。开头的字符是长度固定的,希望其它新协议也能这样以便区分。
此后所有送入协议的整数都编码为4字节大中止端。
在现有的应用中头部数据之后是8个全部预留为0的字节,若果你想通过改变这8个预留字节以扩展协议,请与Bram Cohen协调以保证所有扩展兼容。
然后是来自元信息文件中B编码的info值中长20字节的SHA1验证码(和info_hash向Tracker声明的值相同,但这里是原始值那里是引用)。如果双方的值不同,连接断开。一个例外是下载者想只用一个端口进行多个连接下载,它们会先从接入连接得到一个验证码,然后和列表里面的对照,有相同的就答复。
验证码之后是20字节的peer id,它包含在Tracker回应的peer列表中,在向Tracker的请求中被报告。如果接受方peer id不符合发送方希望,连接断开。
握手完毕。之后是长度固定的交互信息流。零长度信息用来保持连接,被忽略。这种信息一般2分钟发出一次,但是在等待数据期间很容易超时。
所有非保持连接用信息开头的字节给出类型,可能值如下:
·0-阻塞
·1-通畅
·2-关注
·3-不关注
·4-有
·5-比特组
·6-请求
·7-块
·8-取消
“阻塞”、“通畅”、“关注”和“不关注”类信息没有荷载。
“比特组”类信息仅作为首信息发出。它负载一个比特组,下载者有索引的设为1,其它为0。开始下载时没有任何数据的下载者跳过“比特组”信息。首字节高位到低位对应索引0-7,依次类推,第二字节对应8-15,等等。尾部的剩余的比特位设为0。
“已有”类信息负载一个数,即刚下载并核对完验证码的索引数。
“请求”类信息包括包含一个索引,开始和长度。后两者是字节偏移。长度一般是2的权值除非被文件尾截断。现行一般是2的15次幂,并且关闭大于2的17次幂长度的连接。
“取消”类信息负载和“请求”类信息有一样的负载。它通常在下载接近完成即“最后阶段”发出。当下载快要完成时,剩下几个块有都从同一个线程下载的趋向,这样会很慢。为了确保剩余块下载迅速,一旦还没有决定剩余块的下载请求向谁发出,先向所有他正在从对方下载数据的连接者发送要求所有剩余块的请求。为避免低效,每当一个块开始下载就向其他peer发出取消信息。
“块”类信息包含一个索引,开始和块。记住它和“请求”类信息是相关的。当传输速度很慢或“阻塞”“通畅”类信息高频率交替发出或两者同时发生,可能会载到一个不需要的块。
下载者下载块的顺序是随机的,这样适当防止下载者与其他Peers仅有相同的块子集或超集。
阻塞的发生有很多原因。TCP协议的信息拥挤控制在即时向多连接发送信息的过程中表现极差。同时,阻塞的存在使下载者们能够用以牙还牙式的算法来确保稳定的下载速率。
下面描述的阻塞算法是目前基础的配置。重要的是所有新算法不光要在包含全部扩展算法的网络中运行良好,也要在主要包含这个基础算法的网络中运行良好。
一个优秀的阻塞算法有许多标准。它必须封锁一定同时上传的数量以获得良好的TCP表现,还要避免频繁的堵塞和通畅交替,即所谓“纤维化”。它应该用数据交换报答给自己数据的peer。最后,它还应该偶尔尝试一下与未使用过的peer端连接,找出比现有连接好的连接,这叫做尝试性疏通。
现行的阻塞算法避免纤维化的手段是每10秒转换被阻塞的名单。疏通4个自己关注且能从他们身上得到最高下载速率的peer,进行上传和数据交换。有较高上传速率但是不被关注下载者的peer被疏通,一旦这些peer开始被关注,那些上传率最低的peer的就被阻塞。如果下载者有了完整的文件,他用自己的上传率而不是下载率来决定疏通谁的连接。
在尝试性疏通中,任何一次中都有一个peer被疏通不管他的上传率如何(如果被关注,他会成为4个提供下载的peer之一)。被尝试性疏通的这种peer每30秒轮换一次。为了给它们一个上传整一个块的机会,新连接会以轮换中尝试性疏通次数的3倍开始连接。
by jiang zhuo on 七.25, 2010, under BT协议
BitTorrent 是一种分发文件的协议。它通过URL来识别内容,并且可以无缝的和web进行交互。它基于HTTP协议,它的优势是:如果有多个下载者并发的下载同一个文件,那么,每个下载者也同时为其它下载者上传文件,这样,文件源可以支持大量的用户进行下载,而只带来适当的负载的增长。(译注:因为大量的负载被均衡到整个系统中,所以提供源文件的机器的负载只有少量增长)
一个BT文件分布系统由下列实体组成:
一个普通的web服务器
一个静态的“元信息”文件
一个跟踪(tracker)服务器
终端用户的web浏览器
终端下载者
理想的情况是多个终端用户在下载同一个文件。
要提供文件共享,那么一台主机需要执行以下步骤:
Ø运行一个 tracker服务器(或者,已经有一个tracker服务器在运行了也可以)
Ø运行一个web服务器,例如apache,或者已经有一个web服务器在运行了。
Ø在web服务器上,将文件扩展名.torrent 和MIME类型 application/x-bittorrent关联起来(或者已经关联了)
Ø根据 tracker服务器的 URL 和要共享的文件来创建一个“元信息”文件(.torrent)。
Ø将“元信息”文件发布到web服务器上
Ø在某个web页面上,添加一个到“元信息”文件的链接。
Ø运行一个已经拥有完整文件的下载者(被成为’origin’,或者’seed’,种子)
要开始下载文件,那么终端用户执行以下步骤:
Ø安装 BT(或者已经安装)
Ø访问提供 .torrent 文件的web服务器
Ø点击到 .torrent 文件的链接(译注:这时候,bt会弹出一个对话框)
Ø选择要把下载的文件保存到哪里?或者是一次断点续传
Ø等待下载的完成。
Ø结束bt程序的运行(如果不主动结束,那么bt会一直为其它人提供文件上传)
各个部分之间的连通性如下:
网站负责提供一个静态的文件,而把BT辅助程序(客户端)放在客户端机器上。
Trackers从所有下载者处接收信息,并返回给它们一个随机的peers的列表。这种交互是通过HTTP或HTTPS协议来完成的。
下载者周期性的向tracker登记,使得tracker能了解它们的进度;下载者之间通过直接连接进行数据的上传和下载。这种连接使用的是 BitTorrent 对等协议,它基于TCP。
Origin只负责上传,从不下载,因为它已经拥有了完整的文件。Origin是必须的。
元文件和tracker的响应都采用的是一种简单、有效、可扩展的格式,被称为bencoding,它可以包含字符串和整数。由于对不需要的字典关键字可以忽略,所以这种格式具有可扩展性,其它选项以后可以方便的加进来。
Bencoding格式如下:
对于字符串,首先是一个字符串的长度,然后是冒号,后面跟着实际的字符串,例如:4:spam,就是“ spam”
整数编码如下,以 ‘i’ 开始,然后10进制的整数值,最后以’e’结尾。例如,i3e表示3,I-3e表示-3。整数没有大小限制。I-0e是无效的。除了 i0e外,所有以0起始的整数都无效。I0e当然表示0。
列表编码如下,以’l’开始,接下来是列表值的编码(也采用bencoded编码),最后以’e’结束。例如:l4:spam4:eggse 表示 [‘spam’, ‘eggs’]。
字典编码如下,以’d’开始,接下来是可选的keys和它对应的值,最户以’e’结束。例如:d3:cow3:moo4:spam4:eggse,表示{‘cow’:’moo’,’spam’:’eggs’},而d4:spaml1:al:bee 表示 {‘spam’:[‘a’,’b’]}。键值必须是字符串,而且已经排序(并非是按照字母顺序排序,而是根据原始的字符串进行排序)。
元文件是采用bencoded编码的字典,包括以下关键字:
announce tracker的服务器
info 它实际上是一个字典,包括以下关键字:
Name:
一个字符串,在保存文件的时候,作为一个建议值。仅仅是个建议而已,你可以用别的名字保存文件。
Piece length:
为了更好的传输,文件被分隔成等长的片断,除了最后一个片断以外,这个值就是片断的大小。片断大小几乎一直都是2的幂,最常用的是 256k(BT的前一个版本3.2,用的是1M作为默认大小)
Pieces:
一个长度为20的整数倍的字符串。它将再被分隔为20字节长的字符串,每个子串都是相应片断的hash值。
此外,还有一个length或files的关键字,这两个关键字只能出现一个。如果是length,那么表示要下载的仅仅是单个文件,如果是files那么要下载的是一个目录中的多个文件。
如果是单个文件,那么length是该文件的长度。
为了能支持其它关键字,对于多个文件的情况,也把它当作一个文件来看,也就是按照文件出现的顺序,把每个文件的信息连接起来,形成一个字符串。每个文件的信息实际上也是一个字典,包括以下关键字:
Length:文件长度
Path:子目录名称的列表,列表最后一项是文件的实际名称。(不允许出现列表为空的情况)。
Name:在单文件情况下,name是文件的名称,而在多文件情况下,name是目录的名称。
Tracker查询。Trakcer通过HTTP的GET命令的参数来接收信息,而响应给对方(也就是下载者)的是经过bencoded编码的消息。注意,尽管当前的tracker的实现需要一个web服务器,它实际上可以运行的更轻便一些,例如,作为apache的一个模块。
Tracker GET requests have the following keys:
发送给Tracker的GET请求,包含以下关键字:
Info_hash:
元文件中info部分的sha hash,20字节长。这个字符创几乎肯定需要被转义(译注:在URL中,有些字符不能出现,必须通过unicode进行编码)
Peer_id:
下载者的id,一个20字节长的字符串。每个下载者在开始一次新的下载之前,需要随机创建这个id。这个字符串通常也需要被转义。
Ip:
一个可选的参数,给出了peer的ip地址(或者dns名称?)。通常用在origin身上,如果它和tracker在同一个机器上。
Port:
peer所监听的端口。下载者通常在在 6881 端口上监听,如果该端口被占用,那么会一直尝试到 6889,如果都被占用,那么就放弃监听。
Uploaded:
已经上载的数据大小,十进制表示。
Downloaded:
已经下载的数据大小,十进制表示
Left:
该peer还有多少数据没有下载完,十进制表示。注意,这个值不能根据文件长度和已下载数据大小计算出来,因为很可能是断点续传,如果因为检查文件完整性失败而必须重新下载的时候,这也提供了一个机会。
Event:
一个可选的关键字,值是started、compted或者stopped之一(也可以为空,不做处理)。如果不出现该关键字,。在一次下载刚开始的时候,该值被设置为started,在下载完成之后,设置为completed。如果下载者停止了下载,那么该值设置为stopped。
Tracker的响应是用bencoded编码的字典。如果tracker的响应中有一个关键字failure reason,那么它对应的是一个字符串,用来解释查询失败的原因,其它关键字都不再需要了。否则,它必须有两个关键字:Interval:下载者在两次发送请求之间的时间间隔。Peers:一个字典的列表,每个字典包括以下关键字:Peer id,Ip,Port,分别对应peer所选择的id、ip地址或者dns名称、端口号。注意,如果某些事件发生,或者需要更多的peers,那么下载者可能不定期的发送请求,
(downloader 通过 HTTP 的GET 命令来向 tracker 发送查询请求,tracker 响应一个peers 的列表)
如果你想对元信息文件或者tracker查询进行扩展,那么需要同Bram Cohen协调,以确保所有的扩展都是兼容的。
BT对等协议基于TCP,它很有效率,并不需要设置任何socket选项。(译注:BT对等协议指的是peer与peer之间交换信息的协议)
对等的两个连接是对称的,消息在两个方向上同样的传递,数据也可以在任何一个方向上流动。
一旦某个peer下载完了一个片断,并且也检查了它的完整性,那么它就向它所有的peers宣布它拥有了这个片断。
连接的任何一端都包含两比特的状态信息:是否choked,是否感兴趣。Choking是通知对方,没有数据可以发送,除非unchoking发生。Choking的原因以及技术后文解释。
一旦一端状态变为interested,而另一端变为非choking,那么数据传输就开始了。(也就是说,一个peer,如果想从它的某个peer那里得到数据,那么,它首先必须将它两之间的连接设置为 interested,其实就是发一个消息过去,而另一个peer,要检查它是否应该给这个家伙发送数据,如果它对这个家伙是 unchoke,那么就可以给它发数据,否则还是不能给它数据)Interested状态必须一直被设置――任何时候。要用点技巧才能比较好的实现这个目的,但它使得下载者能够立刻知道哪些peers将开始下载。
对等协议由一个握手开始,后面是循环的消息流,每个消息的前面,都有一个数字来表示消息的长度。握手的过程首先是先发送19,然后发送“BitTorrent protocol”。19就是“BitTorrent protocol”的长度。
后续的所有的整数,都采用big-endian 来编码为4个字节
在协议名称之后,是8个保留的字节,这些字节当前都设置为0。
接下来对元文件中的 info 信息,通过 sha1 计算后得到的 hash值,20个字节长。接收消息方,也会对 info 进行一个 hash 运算,如果这两个结果不一样,那么说明对方要的文件,并不是自己所要提供的,所以切断连接。
接下来是20个字节的 peer id。
这就是握手过程
接下来就是以消息长度开始的消息流,这是可选的。长度为0 的消息,用于保持连接的活动状态,被忽略。通常每隔2分钟发送一个这样的消息。
其它类型的消息,都有一个字节长的消息类型,可能的值如下:
‘choke’, ‘unchoe’, ‘interested’, not interested’类型的消息不再含有其它数据了。
‘bitfield’永远也仅仅是第一个被发送的消息。它的数据实际是一个位图,如果downloader已经发送了某个片断,那么对应的位置1,否则置0。Downloaders如果一个片断也没有,可以忽略这个消息。(通过这个消息,能知道什么了?)
‘have’类型的消息,后面的数据是一个简单的数字,它是下载者刚刚下载完并检查过完整性的片断的索引。(由此,可以看到,peer通过这种消息,很快就相互了解了谁都有什么片断)
‘request’类型的消息,后面包含索引、开始位置和长度)长度是2的幂。当前的实现都用的是215 ,而关闭连接的时候,请求一个超过2 17的长度。(这种类型的消息,就是当一个peer希望另一个peer给它提供片断的时候,发出的请求)
‘cancel’类型的消息,它的数据和’request’消息一样。它们通常只在下载趋向完成的时候发送,也就是在‘结束模式“阶段发送。在一次下载接近完成的时候,最后的几个片断需要很长时间才能下载完。为了确保最后几个片断尽快下载完,它向所有的peers发送下载请求。为了保证这不带来可怕的低效,一旦某个片断下载完成,它就其它peers发送’cancel’消息。(意思就是说,我不要这个片断了,你要是准备好了,也不用给我发了,可以想象,如果对方还是把数据发送过来了,那么这边必须忽略这些重复的数据)。
‘piece’类型的消息,后面保护索引号、开始位置和实际的数据。注意,这种类型的消息和 ‘request’消息之间有潜在的联系(译注:因为通常有了request消息之后,才会响应‘piece’消息)。如果choke和unchoke消息发送的过于迅速,或者,传输速度变的很慢,那么可能会读到一些并不是所期望的片断。( 也就是说,有时候读到了一些片断,但这些片断并不是所想要的)