对种子文件进行解析需要先了解一下种子文件的格式, 然后根据格式进行对应的解析.

一. BT种子文件格式

这里只是简单的介绍一下, 具体信息可以自行google.
BT种子文件使用了一种叫bencoding的编码方法来保存数据。
bencoding现有四种类型的数据:srings(字符串),integers(整数),lists(列表),dictionaries(字典)

字符串:
字符串被如此编码:<字符串长度>:字符串正文.这种表示法没有任何的分界符.
例子:如”8:announce”指”announce”.

整数:
整数被如此编码: i整数值e. 可以为负数,如’i-3e’
例子:’i3e’ 指 3.

列表:
列表是如此被表示的:

<l>Bencode Value<e>

列表可以用来表示多个对象.
列表内容可以包括字符串,整数,字典,甚至列表本身.
例子:’l4:spam4:eggse’ 指 [ “spam”, eggs” ]

字典:
字典是一个一对一的映射.它表示了一个主键(必须为字符串)和一个数据项(可以为任何Bencode值)的关系.字典可以用来表示一个对象的多种属性.
字典是如此被编码:

 <d><bencoded string><bencoded element><e>

注意:字典必须根据主键预排序.

二. 对BT文件进行解析
1.首先创建一个对象BtValue来保存bt文件的四种类型的数据

public class BtValue {//mValue可以是String, int, list或Mapprivate final Object mValue;public BtValue(byte[] mValue) {this.mValue = mValue;}public BtValue(String mValue) throws UnsupportedEncodingException {this.mValue = mValue.getBytes("UTF-8");}public BtValue(String mValue, String enc) throws UnsupportedEncodingException {this.mValue = mValue.getBytes(enc);}public BtValue(int mValue) {this.mValue = mValue;}public BtValue(long mValue) {this.mValue = mValue;}public BtValue(Number mValue) {this.mValue = mValue;}public BtValue(List<BtValue> mValue) {this.mValue = mValue;}public BtValue(Map<String, BtValue> mValue) {this.mValue = mValue;}public Object getValue() {return this.mValue;}/*** 将BtValue作为String返回, 使用UTF-8进行编码*/public String getString() throws InvalidBtEncodingException {return getString("UTF-8");}public String getString(String encoding) throws InvalidBtEncodingException {try {return new String(getBytes(), encoding);} catch (ClassCastException e) {throw new InvalidBtEncodingException(e.toString());} catch (UnsupportedEncodingException e) {throw new InternalError(e.toString());}}/*** 将Btvalue对象作为byte[]数组返回*/public byte[] getBytes() throws InvalidBtEncodingException {try {return (byte[])mValue;} catch (ClassCastException e) {throw new InvalidBtEncodingException(e.toString());}}/*** 将BtValue对象作为数字返回*/public Number getNumber() throws InvalidBtEncodingException {try {return (Number)mValue;} catch (ClassCastException e) {throw new InvalidBtEncodingException(e.toString());}}/*** 将BtValue对象作为short返回*/public short getShort() throws InvalidBtEncodingException {return getNumber().shortValue();}/*** 将BtValue对象作为int返回*/public int getInt() throws InvalidBtEncodingException {return getNumber().intValue();}/*** 将BtValue对象作为long返回*/public long getLong() throws InvalidBtEncodingException {return getNumber().longValue();}/*** 将BtValue对象作为List返回*/@SuppressWarnings("unchecked")public List<BtValue> getList() throws InvalidBtEncodingException {if (mValue instanceof ArrayList) {return (ArrayList<BtValue>)mValue;} else {throw new InvalidBtEncodingException("Excepted List<BtValue> !");}}/*** 将BtValue对象作为Map返回*/@SuppressWarnings("unchecked")public Map<String, BtValue> getMap() throws InvalidBtEncodingException {if (mValue instanceof HashMap) {return (Map<String, BtValue>)mValue;} else {throw new InvalidBtEncodingException("Expected Map<String, BtValue> !");}}

为了更好的管理异常, 在这里自定义 异常InvalidBtEncodingException来统一管理

public class InvalidBtEncodingException extends IOException {public static final long serialVersionUID = -1;public InvalidBtEncodingException(String message) {super(message);}
}

2.编写解析类BtParser, 这里采用的方法为递归回溯, 根据bt文件的特有格式分四种类型进行解析, 将解析内容保存为BtValue对象.

public class BtParser {private final InputStream mInput;// Zero 未知类型// '0'..'9' 表示是byte[]数组也就是字符串类型.// 'i' 表示是数字数字.// 'l' 表示是列表类型.// 'd' 表示是字典类型// 'e' 表示是数字,列表或字典的结束字符// -1 表示读取到流的结尾// 调用getNextIndicator接口获取当前的值private int mIndicator = 0;private BtParser(InputStream in) {mInput = in;}public static BtValue btDecode(InputStream in) throws IOException {return new BtParser(in).btParse();}private BtValue btParse() throws IOException{if (getNextIndicator() == -1)return null;if (mIndicator >= '0' && mIndicator <= '9')return btParseBytes();     //read stringelse if (mIndicator == 'i')return btParseNumber();    // read integerelse if (mIndicator == 'l')return btParseList();      // read listelse if (mIndicator == 'd')return btParseMap();       // read Mapelsethrow new InvalidBtEncodingException("Unknown indicator '" + mIndicator + "'");}/*** 对应解析bt文件的字符串类型* 1. 解析字符串的长度* 2. 根据解析的长度从输入流中读取指定长度的字符* 3. 根据读取到的字符数组构建BtValue对象* 对应bt文件的 4:ptgh 字符串格式*/private BtValue btParseBytes() throws IOException{int b = getNextIndicator();int num = b - '0';if (num < 0 || num > 9) {throw new InvalidBtEncodingException("parse bytes(String) error: not '"+ (char)b +"'");}mIndicator = 0;b = read();int i = b - '0';while (i >= 0 && i <= 9) {num = num * 10 + i;b = read();i = b - '0';}if (b != ':') {throw new InvalidBtEncodingException("Colon error: not '" +(char)b + "'");}return new BtValue(read(num));}/*** 对应解析bt文件中的数字类型* 1. 判断是否是以 i 字符开头* 2. 判断要解析的数字是否为负数* 3. 读取数字到chars数组中直到遇见字符e* 4. 有chars数组生成数字, 并生成BtValue对象* 对应bt文件的 i5242e 数字格式*/private BtValue btParseNumber() throws IOException{int b = getNextIndicator();if (b != 'i') {throw new InvalidBtEncodingException("parse number error: not '" +(char)b + "'");}mIndicator = 0;b = read();if (b == '0') {b = read();if (b == 'e') {return new BtValue(BigInteger.ZERO);} else {throw new InvalidBtEncodingException("'e' expected after zero," +" not '" + (char)b + "'");}}// don't support more than 255 char big integerschar[] chars = new char[255];int offset = 0;// to determine whether the number is negativeif (b == '-') {b = read();if (b == '0') {throw new InvalidBtEncodingException("Negative zero not allowed");}chars[offset] = '-';offset++;}if (b < '1' || b > '9') {throw new InvalidBtEncodingException("Invalid Integer start '"+ (char)b + "'");}chars[offset] = (char)b;offset++;// start read the number, save in charsb = read();int i = b - '0';while (i >= 0 && i <= 9) {chars[offset] = (char)b;offset++;b = read();i = b - '0';}if (b != 'e') {throw new InvalidBtEncodingException("Integer should end with 'e'");}String s = new String(chars, 0, offset);return new BtValue(new BigInteger(s));}/*** 对应解析bt文件中的列表类型* 1. 判断是否是以'l'字符开头* 2. 调用btParse解析出BtValue对象, 添加到list中, 直到遇见'e'字符* 3. 使用获得的list对象构造BtValue对象(这时代表了list)* 对应bt文件的 l4:spam4:tease 格式* 如果是 l4:spam4:tease 那么 list对象包含两个BtValue对象, 分别为 spam 和 tease 字符串*/private BtValue btParseList() throws IOException{int b = getNextIndicator();if (b != 'l') {throw new InvalidBtEncodingException("Expected 'l', not '" +(char)b + "'");}mIndicator = 0;List<BtValue> result = new ArrayList<>();b = getNextIndicator();while (b != 'e') {result.add(btParse());b = getNextIndicator();}mIndicator = 0;return new BtValue(result);}/*** 对应解析bt文件中的字典类型* 1. 判断是否是以'd'字符开头* 2. 调用btParse解析获得key与value, 添加到Map中, 直到遇见'e'字符* 3. 使用获得的Map对象构造BtValue对象(这时代表了Map)* 对应bt文件的 <d> <key String> <value content> <e>格式*/private BtValue btParseMap() throws IOException{int b = getNextIndicator();if (b != 'd') {throw new InvalidBtEncodingException("Expected 'd', not '" +(char)b + "'");}mIndicator = 0;Map<String, BtValue> result = new HashMap<>();b = getNextIndicator();while (b != 'e') {// Dictionary keys are always stringsString key = btParse().getString();BtValue value = btParse();result.put(key, value);b = getNextIndicator();}mIndicator = 0;return new BtValue(result);}private int getNextIndicator() throws IOException{if (mIndicator == 0) {mIndicator = mInput.read();}return mIndicator;}/*** 从输入流读取一个数据*/private int read() throws IOException {int b = mInput.read();if (b == -1)throw new EOFException();return b;}/*** 根据指定长度, 从输入流读取字符数组*/private byte[] read(int length) throws IOException {byte[] result = new byte[length];int read = 0;while (read < length){int i = mInput.read(result, read, length - read);if (i == -1)throw new EOFException();read += i;}return result;}
}

其实这个解析类并不难, 逻辑的编写主要根据bt文件格式来进行解析.

3.最后创建Torrent对象来对解析的文件信息进行分类整理

public class Torrent {private final static String TAG = "Torrent";private final Map<String, BtValue> mDecoded;private final Map<String, BtValue> mDecoded_info;private final HashSet<URI> mAllTrackers;private final ArrayList<List<URI>> mTrackers;private final Date mCreateDate;   private final String mComment;    private final String mCreatedBy;private final String mName;private final int mPieceLength;private final LinkedList<TorrentFile> mFiles;private final long mSize;// 对应bt文件中包含多个文件, 定义TorrentFile类来表示每个文件,方便管理public static class TorrentFile {public final File file;public final long size;public TorrentFile(File file, long size) {this.file = file;this.size = size;}}public Torrent(byte[] torrent) throws IOException {mDecoded = BtParser.btDecode(new ByteArrayInputStream(mEncoded)).getMap();mDecoded_info = mDecoded.get("info").getMap();try {mAllTrackers = new HashSet<>();mTrackers = new ArrayList<>();// 解析获得announce-list, 获取tracker地址if (mDecoded.containsKey("announce-list")) {List<BtValue> tiers = mDecoded.get("announce-list").getList();for (BtValue bv : tiers) {List<BtValue> trackers = bv.getList();if (trackers.isEmpty()) {continue;}List<URI> tier = new ArrayList<>();for (BtValue tracker : trackers) {URI uri = new URI(tracker.getString());if (!mAllTrackers.contains(uri)) {tier.add(uri);mAllTrackers.add(uri);}}if (!tier.isEmpty()) {mTrackers.add(tier);}}} else if (mDecoded.containsKey("announce")) { // 对应单个tracker地址URI tracker = new URI(mDecoded.get("announce").getString());mAllTrackers.add(tracker);List<URI> tier = new ArrayList<>();tier.add(tracker);mTrackers.add(tier);}} catch (URISyntaxException e) {throw new IOException(e);}// 获取文件创建日期mCreateDate = mDecoded.containsKey("creation date") ?new Date(mDecoded.get("creation date").getLong() * 1000): null;// 获取文件的commentmComment = mDecoded.containsKey("comment")? mDecoded.get("comment").getString(): null;// 获取谁创建的文件mCreatedBy = mDecoded.containsKey("created by")? mDecoded.get("created by").getString(): null;// 获取文件名字mName = mDecoded_info.get("name").getString();mPieceLength = mDecoded_info.get("piece length").getInt();mFiles = new LinkedList<>();// 解析多文件的信息结构if (mDecoded_info.containsKey("files")) {for (BtValue file : mDecoded_info.get("files").getList()) {Map<String, BtValue> fileInfo = file.getMap();StringBuilder path = new StringBuilder();for (BtValue pathElement : fileInfo.get("path").getList()) {path.append(File.separator).append(pathElement.getString());}mFiles.add(new TorrentFile(new File(mName, path.toString()),fileInfo.get("length").getLong()));}} else {// 对于单文件的bt种子, bt文件的名字就是单文件的名字mFiles.add(new TorrentFile(new File(mName),mDecoded_info.get("length").getLong()));}// 计算bt种子中所有文件的大小long size = 0;for (TorrentFile file : mFiles) {size += file.size;}mSize = size;// 下面就是单纯的将bt种子文件解析的内容打印出来String infoType = isMultiFile() ? "Multi" : "Single";Log.i(TAG, "Torrent: file information: " + infoType);Log.i(TAG, "Torrent: file name: " + mName);Log.i(TAG, "Torrent: Announced at: " + (mTrackers.size() == 0 ? " Seems to be trackerless" : ""));for (int i = 0; i < mTrackers.size(); ++i) {List<URI> tier = mTrackers.get(i);for (int j = 0; j < tier.size(); ++j) {Log.i(TAG, "Torrent: {} " + (j == 0 ? String.format("%2d. ", i + 1) : "    ")+ tier.get(j));}}if (mCreateDate != null) {Log.i(TAG, "Torrent: createDate: " + mCreateDate);}if (mComment != null) {Log.i(TAG, "Torrent: Comment: " + mComment);}if (mCreatedBy != null) {Log.i(TAG, "Torrent: created by: " + mCreatedBy);}if (isMultiFile()) {Log.i(TAG, "Found {} file(s) in multi-file torrent structure." + mFiles.size());int i = 0;for (TorrentFile file : mFiles) {Log.i(TAG, "Torrent: file is " +(String.format("%2d. path: %s size: %s", ++i, file.file.getPath(), file.size)));}}long pieces = (mSize / mDecoded_info.get("piece length").getInt()) + 1;Log.i(TAG, "Torrent: Pieces....: (byte(s)/piece" +pieces + " " + mSize / mDecoded_info.get("piece length").getInt());Log.i(TAG, "Torrent: Total size...: " + mSize);}/*** 加载指定的种子文件, 将种子文件转化为Torrent对象*/public static Torrent load(File torrent) throws IOException {byte[] data = readFileToByteArray(torrent);return new Torrent(data);}public boolean isMultiFile() {return mFiles.size() > 1;}/*** 由file对象获得byte[]对象*/private static byte[] readFileToByteArray(File file) {byte[] buffer = null;try {FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);byte[] b = new byte[1024];int n;while ((n = fis.read(b)) != -1) {bos.write(b, 0, n);}fis.close();bos.close();buffer = bos.toByteArray();} catch (IOException e) {e.printStackTrace();}return buffer;}
}

4.使用方式

File fileTorrent = new File("file path");
try {Torrent.load(fileTorrent);
} catch (IOException e) {e.printStackTrace();
}

至此, 对BT种子文件的解析完成. 其实对种子文件的解析就是根据种子文件的编码格式进行对应的解码过程.

参考: mpetazzoni/ttorrent

Android BT种子文件解析相关推荐

  1. B编码与BT种子文件分析,以及模仿json-cpp写一个B编码解析器

    B编码与BT种子文件分析,以及模仿json-cpp写一个B编码解析器 1.什么是B编码 2.B编码格式 3.种子文件结构 3.1.主文件结构 3.2.info结构 4.简单的例子了解一下种子文件和B编 ...

  2. 一个简单的BitTorrent客户端实现(二):种子文件解析及信息保存

    关于种子文件 BT的种子文件一般是以.torrent作为后缀的.关于种子文件的编码,这里不再做任何介绍.本程序采用的测试种子文件为ubuntu-14.04.3-desktop-i386.torrent ...

  3. php解析bt,PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解

    PHP基于闭包思想实现的BT(torrent)文件解析工具实例详解 发布于 2017-09-08 20:05:36 | 124 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP( ...

  4. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  5. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

  6. Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  7. Android init.rc文件解析过程详解(一)

        Android init.rc文件解析过程详解(一) 一.init.rc文件结构介绍 init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字 ...

  8. 苹果电脑怎么查看服务器信息,苹果电脑上如何查看BT种子文件的详细信息

    Folx是一款综合型的面向MacOS系统的下载管理器,具有Mac风格的用户界面,提供便捷的下载管理以及灵活的设置等.尤其在BT下载方面,可以说Folx已经是一款名副其实的BT专业下载软件,能替代uTo ...

  9. php获取种子失败,php读取BT种子文件内容的方法

    这篇文章主要介绍了php读取BT种子文件内容的方法,可实现读取并显示BT种子文件内容的功能,简单实用.需要的朋友可以参考下.希望对大家有所帮助. 具体如下:<?php /** * Class x ...

  10. BT种子文件文件结构分析(转)

    估计80%以上接触互联网的人都知道bt是什么东西,任何一个用bt下载的人都知道这样一个概念,种子.bt种子就是记录了p2p对等网络中tracker, nodes, files等信息,也就是说,这个种子 ...

最新文章

  1. 重磅发布:Redis 对象映射框架来了,操作大大简化!
  2. linux lnmp yum,yum安装LNMP
  3. 使用容器的概念理解多维数组和多维空间
  4. python3基础语法-Python3的一些基础语法介绍和理解
  5. 【年终总结】2019年有三AI知识星球做了什么,明年又会做什么
  6. 零位扩展和符号位扩展
  7. 数据结构与算法——选择排序
  8. 手把手教你用Flutter做炫酷动画
  9. 小米全球第二,雷军签发内部嘉奖令;亚马逊被欧盟处以创纪录的8.88亿美元罚款;​PyCharm 2021.2 发布|极客日报...
  10. ERP小白必读|十篇ERP初学者经典论文
  11. 计算机新生导论感言,新生入学感言范文精选
  12. 联通屏蔽80端口后利用NAT端口映射穿透解决WEB网站应用发布
  13. Nginx优化与防盗链
  14. IDEA 自动导入的配置(Auto import)
  15. 【Mac版word转PPT技巧】谁说Word转PPT在Mac电脑中不能实现?
  16. SEGGER公司JlinkV9仿真器实现串口通讯VCOM和SWD调试双功能
  17. 大牛讲解信号与系统以及数字信号处理
  18. 电磁原理---电磁炉
  19. 如何配置Windows平台轻量级vscode c++开发环境
  20. 【转载】VMware虚拟机安装黑群晖7.0教程

热门文章

  1. 长沙理工大学c语言编程题,长沙理工大学2014年上期期末c语言编程题库.doc
  2. linux下运行exe程序之wine的使用与安装
  3. 常用软件版本号及软件安装包格式
  4. 离散数学(第2版)屈婉玲版知识点小结(用于个人快速复习)-1
  5. Log4cpp 库安装及使用
  6. 数据结构C语言版 学习整理
  7. 【NDSS 2021】On the Insecurity of SMS One-Time Password Messages against Local Attackers 论文笔记
  8. XP 上金山词霸 占CPU 特别大的问题
  9. MySQL数据库设计-案例
  10. NAT技术与代理服务器的区别