Azureus源码剖析(二) ---解析Torrent种子文件

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:
 BT种子文件使用了一种叫bencoding的编码方法来保存数据。

bencoding有四种类型的数据:srings(字符串),integers(整数),lists(列表),dictionaries(字典)
编码规则如下:
(1)strings(字符串)编码为:<字符串长度>:<字符串>
例如: 4:test 表示为字符串"test"
 4:例子 表示为字符串“例子”
字符串长度单位为字节
没开始或结束标记

(2)integers(整数)编码为:i<整数>e
开始标记i,结束标记为e
例如: i1234e 表示为整数1234
 i-1234e 表示为整数-1234
整数没有大小限制
 i0e 表示为整数0
 i-0e 为非法
以0开头的为非法如: i01234e 为非法

(3)lists(列表)编码为:l<bencoding编码类型>e
开始标记为l,结束标记为e
列表里可以包含任何bencoding编码类型,包括整数,字符串,列表,字典。
例如: l4:test5:abcdee 表示为二个字符串["test","abcde"]

(4)dictionaries(字典)编码为d<bencoding字符串><bencoding编码类型>e
开始标记为d,结束标记为e
关键字必须为bencoding字符串
值可以为任何bencoding编码类型
例如: d3:agei20ee 表示为{"age"=20}
 d4:path3:C:"8:filename8:test.txte

 表示为{"path"="C:"","filename"="test.txt"}

(5)具体文件结构如下:
全部内容必须都为bencoding编码类型。
整个文件为一个字典结构,包含如下关键字
announce:tracker服务器的URL(字符串)
announce-list(可选):备用tracker服务器列表(列表)
creation date(可选):种子创建的时间,Unix标准时间格式,从1970 1月1日 00:00:00到创建时间的秒数(整数)
comment(可选):备注(字符串)
created by(可选):创建人或创建程序的信息(字符串)
info:一个字典结构,包含文件的主要信息,为分二种情况:单文件结构或多文件结构
单文件结构如下:
          length:文件长度,单位字节(整数)
          md5sum(可选):长32个字符的文件的MD5校验和,BT不使用这个值,只是为了兼容一些程序所保留!(字符串)
          name:文件名(字符串)
          piece length:每个块的大小,单位字节(整数)
          pieces:每个块的20个字节的SHA1 Hash的值(二进制格式)
多文件结构如下:
          files:一个字典结构
                 length:文件长度,单位字节(整数)
                 md5sum(可选):同单文件结构中相同
                 path:文件的路径和名字,是一个列表结构,如"test"test.txt 列表为l4:test8test.txte
          name:最上层的目录名字(字符串)
          piece length:同单文件结构中相同
          pieces:同单文件结构中相同 
(6)实例:
用记事本打开一个.torrent可以看来类似如下内容
d8:announce35:http://www.manfen.net:7802/announce13:creation datei1076675108e4:infod6:lengthi17799e4:name62:MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent12:piece lengthi32768e6:pieces20:?W ?躐?緕排T酆ee

很容易看出
announce=http://www.manfen.net:7802/announce
creation date=1076675108秒(02/13/04 20:25:08)
文件名=MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent
文件大小=17799字节
文件块大小=32768字节

       对Azureus中解析Torrent种子文件的源代码进行了适度裁剪,得到下面这样一个解析torrent文件的示例代码,如下所示:

复制代码
/*
 * BeDecoder.java
 *
 */

package com.vista.test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A set of utility methods to decode a bencoded array of byte into a Map.
 * integer are represented as Long, String as byte[], dictionnaries as Map, and list as List.
 *
 */
public class BDecoder 
{
    //字符集
    public static final String BYTE_ENCODING = "UTF8";
    public static Charset    BYTE_CHARSET;
    static
    {
        try
        {
            BYTE_CHARSET     = Charset.forName(BYTE_ENCODING);
        }
        catch( Throwable e )
        {
            e.printStackTrace();
        }
    }
    private static final boolean TRACE    = true;
    
    private boolean recovery_mode;

    public static Map decode(BufferedInputStream is) throws Exception
    {//解码
        return( new BDecoder().decodeStream( is ));
    }
    
    public BDecoder() 
    {    
    }
    
    public Map decodeStream(BufferedInputStream data ) throws Exception
    {
        Object    res = decodeInputStream(new BDecoderInputStreamStream(data), 0);//0指定递归层次从第一层开始
        if ( res == null )
        {
            throw( new Exception( "BDecoder: zero length file" ));

        }
        else if ( !(res instanceof Map ))
        {
            throw( new Exception( "BDecoder: top level isn't a Map" ));
        }
        return((Map)res );
    }

    /**
     * 
     * @param dbis
     * @param nesting 递归层次
     * @throws Exception
     */
    private Object decodeInputStream(BDecoderInputStream dbis,int nesting ) throws Exception 
    {
        if (nesting == 0 && !dbis.markSupported())
        {
            throw new IOException("InputStream must support the mark() method");
        }
        //set a mark
        dbis.mark(Integer.MAX_VALUE);

        //read a byte
        int tempByte = dbis.read();//读一个字节

        //decide what to do        
        switch (tempByte)
        {
            case 'd' :
            {//是字典
                //create a new dictionary object
                Map tempMap = new HashMap();
                try
                {
                    //get the key   
                    byte[] tempByteArray = null;
                    while ((tempByteArray = (byte[]) decodeInputStream(dbis, nesting+1)) != null)
                    {
                        //decode some more
                        Object value = decodeInputStream(dbis,nesting+1);//读值                
                        // value interning is too CPU-intensive, let's skip that for now
                        //if(value instanceof byte[] && ((byte[])value).length < 17)
                        //value = StringInterner.internBytes((byte[])value);
                        // keys often repeat a lot - intern to save space                
                        String    key = null;
    
                        if ( key == null )
                        {
                            CharBuffer    cb = BYTE_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
                            key = new String(cb.array(),0,cb.limit());//键
                        }
                        if ( TRACE )
                        {
                            System.out.println( key + "->" + value + ";" );
                        }
                        
                            // recover from some borked encodings that I have seen whereby the value has
                            // not been encoded. This results in, for example, 
                            // 18:azureus_propertiesd0:e
                            // we only get null back here if decoding has hit an 'e' or end-of-file
                            // that is, there is no valid way for us to get a null 'value' here
                        
                        if ( value == null )
                        {
                            //Debug.out( "Invalid encoding - value not serialsied for '" + key + "' - ignoring" );
                            break;
                        }
                        tempMap.put( key, value);//放入结果集中
                    }
    
                    dbis.mark(Integer.MAX_VALUE);
                    tempByte = dbis.read();
                    dbis.reset();
                    if ( nesting > 0 && tempByte == -1 )
                    {
                        throw( new Exception( "BDecoder: invalid input data, 'e' missing from end of dictionary"));
                    }
                }catch( Throwable e )
                {
                    if ( !recovery_mode )
                    {
                        if ( e instanceof IOException )
                        {
                            throw((IOException)e);
                        }
    
                        throw( new IOException(e.getMessage()));
                    }
                }
                return tempMap;
            }
        case 'l' :
        {
            //create the list
            ArrayList tempList = new ArrayList();
            try
            {
                //create the key
                Object tempElement = null;
                while ((tempElement = decodeInputStream(dbis, nesting+1)) != null)
                {
                    //add the element
                    tempList.add(tempElement);//读取列表元素并加入列表中
                }

                tempList.trimToSize();
                dbis.mark(Integer.MAX_VALUE);
                tempByte = dbis.read();
                dbis.reset();
                if ( nesting > 0 && tempByte == -1 )
                {
                    throw( new Exception( "BDecoder: invalid input data, 'e' missing from end of list"));
                }
            }
            catch( Throwable e )
            {
                if ( !recovery_mode )
                {
                    if ( e instanceof IOException )
                    {
                        throw((IOException)e);
                    }
                    throw( new IOException(e.getMessage()));
                }
            }
                //return the list
            return tempList;
        }
        case 'e' :
        case -1 :
            return null;//当前结束

        case 'i' :
            return new Long(getNumberFromStream(dbis, 'e'));//整数

        case '0' :
        case '1' :
        case '2' :
        case '3' :
        case '4' :
        case '5' :
        case '6' :
        case '7' :
        case '8' :
        case '9' :
                //move back one
            dbis.reset();
                //get the string
            return getByteArrayFromStream(dbis);//读取指定长度字符串

        default :
        {
            int    rem_len = dbis.available();
            if ( rem_len > 256 )
            {
                rem_len    = 256;
            }

            byte[] rem_data = new byte[rem_len];
            dbis.read( rem_data );
            throw( new Exception("BDecoder: unknown command '" + tempByte + ", remainder = " + new String( rem_data )));
        }
        }
    }

    /** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method)
     */
    private final char[] numberChars = new char[32];
    
    private long getNumberFromStream(BDecoderInputStream dbis, char parseChar) throws IOException 
    {
        int tempByte = dbis.read();
        int pos = 0;
        while ((tempByte != parseChar) && (tempByte >= 0)) 
        {//读取整数字节,直到终结字符'e'
            numberChars[pos++] = (char)tempByte;
            if ( pos == numberChars.length )
            {
                throw( new NumberFormatException( "Number too large: " + new String(numberChars,0,pos) + "" ));
            }
            tempByte = dbis.read();
        }

        //are we at the end of the stream?
        if (tempByte < 0) 
        {
            return -1;
        }
        else if ( pos == 0 )
        {
            // support some borked impls that sometimes don't bother encoding anything
            return(0);
        }
        return( parseLong( numberChars, 0, pos ));//转换为Long型整数
    }

    public static long parseLong(char[]    chars,int start,int    length )
    {//转换为Long型整数
        long result = 0;
        boolean negative = false;
        int     i     = start;
        int    max = start + length;
        long limit;
        if ( length > 0 )
        {
            if ( chars[i] == '-' )
            {
                negative = true;
                limit = Long.MIN_VALUE;
                i++;
            }
            else
            {
                limit = -Long.MAX_VALUE;
            }

            if ( i < max )
            {
                int digit = chars[i++] - '0';
                if ( digit < 0 || digit > 9 )
                {
                    throw new NumberFormatException(new String(chars,start,length));
                }
                else
                {
                    result = -digit;
                }
            }

            long multmin = limit / 10;

            while ( i < max )
            {
                // Accumulating negatively avoids surprises near MAX_VALUE
                int digit = chars[i++] - '0';
                if ( digit < 0 || digit > 9 )
                {
                    throw new NumberFormatException(new String(chars,start,length));
                }
                if ( result < multmin )
                {
                    throw new NumberFormatException(new String(chars,start,length));
                }
                result *= 10;
                if ( result < limit + digit )
                {
                    throw new NumberFormatException(new String(chars,start,length));
                }

                result -= digit;
            }
        }
        else
        {
            throw new NumberFormatException(new String(chars,start,length));
        }

        if ( negative )
        {
            if ( i > start+1 )
            {
                return result;

            }
            else
            {    /* Only got "-" */
                throw new NumberFormatException(new String(chars,start,length));
            }
        }
        else
        {
            return -result;
        }  
    }

    private byte[] getByteArrayFromStream(BDecoderInputStream dbis ) throws IOException 
    {
        int length = (int) getNumberFromStream(dbis, ':');
        if (length < 0)
        {
            return null;
        }
        // note that torrent hashes can be big (consider a 55GB file with 2MB pieces
        // this generates a pieces hash of 1/2 meg
        if ( length > 8*1024*1024 )
        {
            throw( new IOException( "Byte array length too large (" + length + ")"));
        }

        byte[] tempArray = new byte[length];
        int count = 0;
        int len = 0;
        //get the string
        while (count != length && (len = dbis.read(tempArray, count, length - count)) > 0) 
        {
            count += len;
        }
        if ( count != tempArray.length )
        {
            throw( new IOException( "BDecoder::getByteArrayFromStream: truncated"));
        }
        return tempArray;
    }

    public void setRecoveryMode(boolean    r )
    {
        recovery_mode    = r;
    }
    
    public static void print(PrintWriter writer,Object obj )
    {
        print( writer, obj, "", false );
    }

    private static void print(PrintWriter writer,Object obj,String indent,boolean    skip_indent )
    {
        String    use_indent = skip_indent?"":indent;
        if ( obj instanceof Long )
        {
            writer.println( use_indent + obj );

        }
        else if ( obj instanceof byte[])
        {
            byte[]    b = (byte[])obj;
            if ( b.length==20 )
            {
                writer.println( use_indent + " { "+ ByteFormatter.nicePrint( b )+ " }" );
            }
            else if ( b.length < 64 )
            {
                writer.println( new String(b) + " [" + ByteFormatter.encodeString( b ) + "]" );
            }else{
                writer.println( "[byte array length " + b.length );
            }
        }else if ( obj instanceof String )
        {
            writer.println( use_indent + obj );

        }
        else if ( obj instanceof List )
        {
            List    l = (List)obj;
            writer.println( use_indent + "[" );
            for (int i=0;i<l.size();i++)
            {
                writer.print( indent + "  (" + i + ") " );
                print( writer, l.get(i), indent + "    ", true );
            }
            writer.println( indent + "]" );

        }
        else
        {
            Map    m = (Map)obj;
            Iterator    it = m.keySet().iterator();
            while( it.hasNext())
            {
                String    key = (String)it.next();
                if ( key.length() > 256 )
                {
                    writer.print( indent + key.substring(0,256) + " = " );
                }
                else
                {
                    writer.print( indent + key + " = " );
                }
                print( writer, m.get(key), indent + "  ", true );
            }
        }
    }

    private static void print(File f,File output)
    {
        try
        {
            BDecoder    decoder = new BDecoder();//解码器
            PrintWriter    pw = new PrintWriter( new FileWriter( output ));//输出结果
            print( pw, decoder.decodeStream( new BufferedInputStream( new FileInputStream( f ))));
            pw.flush();
        }
        catch( Throwable e )
        {
            e.printStackTrace();
        }
    }

    private interface BDecoderInputStream
    {
        public int read() throws IOException;
        public int read(byte[] buffer)throws IOException;
        public int read(byte[]     buffer,int    offset,int    length )throws IOException;
        public int available()throws IOException;
        public boolean markSupported();
        public void mark(int limit );
        public void reset() throws IOException;
    }

    private class BDecoderInputStreamStream implements BDecoderInputStream
    {
        final private BufferedInputStream is;
        private BDecoderInputStreamStream(BufferedInputStream _is )
        {
            is    = _is;
        }
        /**
         * 从此输入流中读取下一个数据字节。返回一个 0 到 255 范围内的 int 字节值。
         * 如果因为已经到达流末尾而没有字节可用,则返回 -1。
         * 在输入数据可用、检测到流末尾或抛出异常之前,此方法将一直阻塞。 
         */
        public int read()throws IOException
        {
            return( is.read());
        }
        /**
         * 从此输入流中将 byte.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。 
         */
        public int read(byte[] buffer )throws IOException
        {
            return( is.read( buffer ));
        }
        /**
         * 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
         */
        public int read(byte[]     buffer,int    offset,int    length)throws IOException
        {
            return( is.read( buffer, offset, length ));  
        }
        /**
         * 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。
         */
        public int available() throws IOException
        {
            return( is.available());
        }

        /**
         * 测试此输入流是否支持 mark 和 reset 方法。
         */
        public boolean markSupported()
        {
            return( is.markSupported());
        }

        /**
         * 在输入流中的当前位置上作标记。reset 方法的后续调用将此流重新定位在最后标记的位置上,以便后续读取操作重新读取相同的字节。
         * @param limit 在标记位置变为无效之前可以读取字节的最大限制。
         */
        public void mark(int limit )
        {
            is.mark( limit );
        }
        /**
         * 将此流重新定位到对此输入流最后调用 mark 方法时的位置。
         */
        public void reset()throws IOException
        {
            is.reset();
        }
    }

    public static void main(String[]    args )
    {      
        print(new File( "C:\\1001.torrent" ),new File( "C:\\tables.txt" ));
    }
}

复制代码



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/05/06/1451025.html,如需转载请自行联系原作者
目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
3天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
3天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
3天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
26天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
54 12
|
22天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
4天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
1月前
|
消息中间件 存储 Java
RocketMQ文件刷盘机制深度解析与Java模拟实现
【11月更文挑战第22天】在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、日志流处理等场景。为了保证消息的可靠性,RocketMQ引入了一种称为“刷盘”的机制,将消息从内存写入到磁盘中,确保消息持久化。本文将从底层原理、业务场景、概念、功能点等方面深入解析RocketMQ的文件刷盘机制,并使用Java模拟实现类似的功能。
42 3
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
81 0

热门文章

最新文章

推荐镜像

更多