一个远程下载verycd的小技巧

贝壳家里开了emule,天天下载,问题是又不能每次都是晚上添加资源。
怎么办呢?不知道大家知道不知道,emule是可以网络管理的,端口是4711。不过不是https,密码容易泄露。而且贝壳已经有一个nginx服务器了,也懒的再做端口映射,换端口。于是,在nginx中做如下设定。(当然,贝壳是放在https段中的)

location ^~ /emule/ {
rewrite             ^/emule/(.*)$ /$1 break;
proxy_pass          http://hostip:4711;
proxy_set_header    Host            $host;
proxy_set_header    X-Real-IP       $remote_addr;
proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
}

然后,用https://dynname/emule/就可以访问你家里的emule了,记得密码设强点。

接下来,verycd里面曾经有复制所有连接的选项,现在没了。面对几个链接,也就手工复制一下,面对上百个连接,贝壳就无语了。贝壳试验过几个插件,都无法正确识别ed2k的链接获取。那怎么办呢?这时就要请出我们伟大的Linux。
需要准备的工具是lynx,请预先装好,然后如下操作。(范例是个动画^_^)

wget -c http://www.verycd.com/topics/2779234/
lynx -dump -listonly index.html | grep “ed2k://” | sed “s/.*ed2k/ed2k/g” | grep -v BIG5 > out.txt
wc -l out.txt

lynx的-dump选项是将某个网页全部渲染成文本进行展示,这是html2text的好方法,效果还不错哦,不过中文支持好像不是很好。而-listonly则是展示出页面上的所有链接,这就拿到了我们需要的原始数据。
然后,我们取其中的ed2k行,忽略其他链接,再通过sed转换,去除头部的编号和空格,这样就可以得到所有的ed2k链接。
我在下面是用-v BIG5的参数,忽略了其中繁体中文的资源,然后输出到一个文件中。数数行数,52行,大致和文件个数相当(其中好像有两个v2)。那就是可用资源了,复制到emule的控制页面中——出错?
这是因为emule的控制页面使用GET方式传递参数,因此有长度限制。你需要将链接10个一批往里面复制——有个几次就OK了。当然,如果还是多,贝壳回头会写一篇文章,介绍如何使用curl自动干这事情——这还不算太难。
这就是为啥贝壳喜欢Linux的原因了。相比Windows下的两个解决方案,找插件太费劲,自己写程序更费劲。Linux使用现有工具的组合可以轻松完成这一任务。

VeryCD版电驴(eMule)存在封锁

eMule是一个GPL程序,所以VeryCD的改版必须公开源码。今天听说VeryCD版有封锁的现象,所以贝壳抓源码来看看。如果大家认为老调重弹的话,不妨把文章拉到最后。
源码从此处下载:http://www.emule.org.cn/download/
最下方链接:http://download.verycd.com/eMule-VeryCD-src.rar
贝壳下到的文件大小13,703,064字节,打包时间2008-09-11。经过贝壳查找,在eMule-VeryCD-src\src \WordFilter发现两个文件,WordFilter.cpp 2008-03-12 09:57 13374和WordFilter.h
2007-11-20 17:56 1009。仔细阅读里面,发现有以下内容。
void CWordFilter::Init()
{
HANDLE hFile;
DWORD dwRead;
int nLen;
BOOL bResult;
CStringList list;

//m_count = 0;

CString saaa = thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE;
CString sbbb = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE;

// 如果文件目录不对,程序移动一下,到config目录下 added by kernel1983 2006.07.31
if (PathFileExists(thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE))
MoveFile(thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE, thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE);

if (!PathFileExists(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE))
{
// 不存在,所有的都过滤 added by kernel1983 2006.08.08
m_filterall = true;
return;
}

// Open file for read
hFile = CreateFile(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//AddLogLine(false,_T(“:%s\n”),thePrefs.GetConfigDir() + FLITER_FILE);
if(hFile == NULL || hFile == INVALID_HANDLE_VALUE)
{
// 读取错误,所有的都过滤 added by kernel1983 2006.08.08
m_filterall = true;
return;
}

DWORD dwSize = GetFileSize(hFile, NULL);

TCHAR * pszData = new TCHAR[(dwSize / sizeof(TCHAR)) + 1]; // 申请空间
bResult = ReadFile(hFile, pszData, dwSize, &dwRead, NULL); // 读入文件1
CloseHandle(hFile);
pszData[(dwSize / sizeof(TCHAR))] = 0;

if(bResult)
{
// 加入解码算法
{
std::string tempstr( (char*)pszData + 1 , ((int)dwSize – 1) > 0 ? dwSize -1 : 0 );

// 查看是否是老格式
char * pszData_a = (char*) pszData;

if( pszData_a[0] != 0×15 ) {
// 老格式,进行转换
CUnicodeToMultiByte wc2mb( thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE );
tempstr.assign( (char*)pszData , dwSize );
InternalBase64::encode2file( tempstr , std::string((LPCSTR)wc2mb , wc2mb.GetLength()) );

delete [] pszData;
// 重新载入
return Init();
}

vector vec = InternalBase64::decode( tempstr );
char * pszt = (char*) pszData;
for( size_t i = 0; i < vec.size() ; i++ ) {
pszt[i] = vec[i];
}
dwSize = vec.size();
}

TCHAR * pszTemp = wcstok(pszData + 1, _T(“\r\n”));
while(pszTemp != NULL)
{
nLen = wcslen(pszTemp);
while(pszTemp[nLen - 1] == ‘\t’ || pszTemp[nLen - 1] == ‘ ‘)
{
nLen –;
pszTemp[nLen] = 0;
}
while(*pszTemp == ‘\t’ || *pszTemp == ‘ ‘)
{
pszTemp ++;
nLen –;
}
//AddLogLine(false,_T(“pszTemp:%s”),pszTemp);
//AddLogLine(false,_T(“nLen:%d”),nLen);
if(nLen > 0)list.AddTail(pszTemp);
//if(nLen == 8)AddLogLine(false,_T(“:%d %d %d %d “),((char*)pszTemp)[0],((char*)pszTemp)[1],((char*)pszTemp)[2],((char*)pszTemp)[3]);
pszTemp = wcstok(NULL, _T(“\r\n”));
}
}

delete[] pszData;

m_count = list.GetCount();
//AddLogLine(false,_T(“m_count:%d”),m_count);

if(bResult && m_count > 0)
{
m_filterwords = new TCHAR*[m_count+1];
m_kmpvalue = new int*[m_count+1];
ZeroMemory(m_filterwords, sizeof(TCHAR *) * m_count);
ZeroMemory(m_kmpvalue, sizeof(int *) * m_count);
}

for(int i = 0; bResult && (i < m_count); i ++)
{
CString s = list.GetAt(list.FindIndex(i));
s.MakeLower();
nLen = s.GetLength();
//AddLogLine(false,_T(“nLen:%d”),nLen);
m_filterwords[i] = new TCHAR[nLen + 1];
m_filterwords[i][nLen] = 0; // 最后一个字节设为0
m_kmpvalue[i] = new int[nLen];
//AddLogLine(false,_T(“nLen:%d”),nLen);
_tcscpy(m_filterwords[i],s);
//AddLogLine(false,_T(“m_filterwords[i]:%s”),m_filterwords[i]);
KMP_GetNext(m_filterwords[i], m_kmpvalue[i]); // 得到一个与内容有关的数值m_kmpvalue[i]
}

if(m_count == 0 || !bResult)
{
Free();
//m_filterall = true;
}
}

bool CWordFilter::VerifyString(const CString & sString) // 验证字符是否合法
{
bool bIsRm = sString.Right(3)==_T(“.rm”);
CString sReduceString=sString;
CString sInterpunctionString = _T(“(),().。·;:-《》『』~ “”〓!【】★×┇”);
try // VC-Huby[2007-03-20]:满足中国国情特色,加强过滤
{
int j=0;
for( int i=0; i< sString.GetLength(); i++ )
{
if( sString.GetAt(i)<=_T(‘/’) && sString.GetAt(i)>=_T(‘ ‘) ) //从空格到’/'之间的字符减掉后再过滤
{
continue;
}
else if( sString.GetAt(i)<=_T(‘@’) && sString.GetAt(i)>=_T(‘:’) )
{
continue;
}
else if( sString.GetAt(i)<=_T(‘`’) && sString.GetAt(i)>=_T(‘[') )
{
continue;
}
else if( sString.GetAt(i)<=_T('~') && sString.GetAt(i)>=_T('{') )
{
continue;
}
else if( sInterpunctionString.Find(sString.GetAt(i))>=0 )
{
continue;
}
else
{
sReduceString.SetAt(j,sString.GetAt(i));
j++;
}
}
if( j< sString.GetLength() )
sReduceString.SetAt(j,_T('\0'));
}
catch (...)
{
}

if(m_filterall){
//AddLogLine(false,_T("m_filterall"));
return true; // 检测不到文件,或者读取错误的情况下放弃过滤
}
if(m_count == 0){
//AddLogLine(false,_T("m_count == 0"));
return true; // 文件是空的时候,放弃过滤功能
}
CString strSearch = ((CString)sReduceString).MakeLower();

//vc-huby: 过滤中文字符超过15字符
//CString sReduceString2=strSearch;
int k=0;
for( int i=0; i< strSearch.GetLength(); i++ )
{
if( strSearch.GetAt(i)<=_T('9') && strSearch.GetAt(i)>=_T('0') )
{
continue;
}
if( strSearch.GetAt(i)<=_T('z') && strSearch.GetAt(i)>=_T('a') )
{
continue;
}
else
{
k++;
}
}

if( k>=20 && bIsRm )
return false;
//int m = sReduceString2.GetLength();
/*
if( k>=60 )
return false;*/

/*if (strSearch.GetLength() > 20)
{
return false;
}*/

for(int i = 0; i < m_count; i ++)
{
if(KMP_Match(strSearch, m_filterwords[i], m_kmpvalue[i]))
{
//AddLogLine(false,_T(“KMP_Match”));
return false; // 关键词命中了,被fliter了
}
}
//AddLogLine(false,_T(“漏掉的”));
return true;

}

void CWordFilter::Free() //
{
for(int i = 0; i < m_count; i ++)
{
if(m_filterwords[i])
delete[] m_filterwords[i];
if(m_kmpvalue[i])
delete[] m_kmpvalue[i];
}
delete[] m_filterwords;
delete[] m_kmpvalue;
}

CWordFilter::~CWordFilter()
{
Free();
}
其中WordFilter.h的第17行有以下定义。
#define FLITER_FILE _T(“wordfilter.txt”)
于是贝壳查看了C:\Program Files\eMule\config目录,在下面发现了wordfilter.txt 2007-09-30 12:58 10788。大家有兴趣自己看看里面的内容,贝壳就不贴了,贴出来绝对被封,死1090次。
下面说一点起效方式,也许大家很奇怪,这些内容是可以搜索的。贝壳仔细查看了代码,类在两处被引用了,一个是MFC初始化系统的时候初始化类,载入词典。另外一个是在SearchList.cpp 2007-11-20 17:56 22505,351行AddToList函数,第360行,内容如下。
// WordFilter added by kernel1983 2006.07.31
if(!WordFilter.VerifyString(toadd->GetFileName()))
{
delete toadd;
return false;
}
这个封锁手法尤其狠毒,并非封锁你的搜索,而是如果你的文件信息内有这些关键词,那么文件共享消息就不会被发送到服务器上,如同这个文件没有被共享一样。这样既没有用户会发现被封锁的事实(因为有少量其他客户端的数据会被检索出来),又能达到封锁的目地。
当然,贝壳理解VeryCD这帮人的苦心,毕竟他们还住在中国,不过估计从此后,贝壳和朋友的机器上不会装VeryCD了。

论P2P构架的变革

    目前P2P正在蓬勃发展,不过其中也有很多问题。本文试图列举目前P2P所碰到的最大几个问题,并分析成因和解决方法,最后提出一种P2P传输方式。
    P2P传输目前所碰到的几大问题有,传输数据不可控,及其衍生出的版权控制问题,广告控制问题,病毒控制问题。传输速度和传输持续性的冲突。“吸血”、“限速”和“卡种”。搜索的敏感性和代价。传输过程的保密和安全性。
    首先讨论最大的一类问题,传输数据不可控,又称为无障碍特性。所谓传输数据不可控,是指所有通过P2P共享传输的数据都无法被某个人标记和阻止。Http的网页内容很容易被封锁掉,而ED/BT的数据可就封不住了。基于这个特点,很多人都用P2P来下载XXX内容。不过相对来说有利有弊,这同样造成了病毒,色情,暴力的泛滥。而且衍生了另外一个相当巨大的问题,版权问题。
    上文可知,单个个体是无法对P2P内容的传输构成干扰的,此处的个体不单单指个人,还包括了企业实体等等。相对的全体则是指所有个体的集合。其实从某种意义上说,上面一句根本就是说,P2P传输内容不受干扰。那么我们可以想见,如果是一般传媒,个人利益受到侵害的时候个人是无力的,企业利益收到侵害的时候企业是财大气粗的。但是P2P这里,企业和个人一样无力了。因此P2P就成了很多企业的眼中钉肉中刺,而保护版权,就是他们最好的理由。其实从某种意义上来说,传统媒体就没有版权的问题吗?P2P传播方式就一定带来版权问题吗?
    而更进一步来说,我们面对着自由传播和反自由传播的斗争。前者认为人类的知识财富应当为人类所共有,前人的知识应当能让后人学习。而后者则认为人类的知识应当保密,以便能带给创造知识的人利益。前者为黑客时代的精神核心,目前的开源软件基金即继承了此种思想。其中哪种更好确实难说,没有学习就没有进步,可是没有利益谁去学习呢?
    OK,言归正传,当前我们面对着版权保护上的困难。从根本上说,我们应当考虑的是如何才能在新情况下重新分配利益,而不是如何阻拦技术的进步以保护旧有的利益格局。尽力争取将其价值内在体现而不是外在化,更直接的说就是尽力使得公开技术本身就能获得收益,而不是拿去卖钱。例如将内容公开,而且从感兴趣的人中收集潜在客户,或者是在内容中引导宣传等等。此时P2P的无障碍特性不但不是缺陷,反而是优势。
    而后,P2P的传输速度和传输持续性的矛盾。其实严格来说这并不是矛盾,P2P越热,传输速度就越快。但是一个东西的热度总是有限的,因此就P2P应当牺牲持续提供下载的能力来保持传输速度,还是牺牲传输速度来保证持续下载的能力。P2P分成了两类,ED阵营和BT阵营。本质上说没有什么问题,要保证又有持续下载性又有速度是很困难的。因此这个问题是一个内在的矛盾。
    再而后,“吸血”、“限速”和“卡种”的问题,这也是P2P最为内在和难以解决的一个问题。所谓吸血,是ED术语。指仅仅从别人那里下载,而不为别人提供上传的人。在BT中就是减小上传,下了就跑。限速是P2P术语,指限制上传和下载速度。卡种则是BT术语。指当上传到99.9%的时候停止发布,使得大量的人维持在99.9%无法完成下载的方法。实践证明短期的卡种可以“强迫”下了就跑的人持续“做种”上传,使得整体速度提升。但是时间长一点就会打击某些下载积极分子的积极性,导致跑种,浪费带宽。在ED中,使用收益上传者来解决此类问题。
    本质上,这个问题是源于P2P的核心思想的。P2P认为,我为人人,人人为我。但是有人的带宽要收费,有人懒得为别人做贡献,怎么办?于是吸血骡等等软件和方法就应运而生。下载的时候凶猛,上传的时候不给。于是整体的速度就被吸血骡拖慢了,所有人的下载也越来越困难。为了对应吸血骡和下了就跑的人,人们开发了收益上传者系统和卡种方法,不过都是治标不治本。技术无论如何进步,都解决不了人自身的贪婪。
    至于限速,则是一个两说的话题,有人认为限速拖慢了整体网速,有人认为限速无妨。其实应当这么说,如果一个人限速的时候同时限制了上传和下载速度,那么无所谓。如果上传限速一点点,下载限速非常高,或者根本不限速。那么只能说RPWT了。
    搜索的敏感性和代价,这个问题不是一个完整的P2P上的问题。可以说,这个问题牵涉了互联网的本质,即信息的收集和获得。作为P2P来说,在信息的敏感性和代价上具有特殊的特点。
    最简单情况下,数据被集中到核心服务器上,搜索者驱动核心服务器来做搜索。当数据量增大后,核心服务器就无法支撑了。于是这时候出现了集群服务器,即以一组服务器替代一个服务器。按照读写发生的频率,又可以分为多对等服务器和多分离服务器两大类型。多对等服务器将每个服务器视为具备相同的数据,查询操作可以在和单服务器同等的时间内完成,但是更新和修改操作就必须花费原来时间的N倍,同时数据存储成本是单服务器的N倍。而多分离服务器则认为每个服务器负担一部分数据,更新和修改操作的时间比单服务器略长,模糊查询操作则需要付出原先N倍的代价,数据存储成本则和单服务器一致。前者可以看做是RAID0的变形,后者则是RAID1的变形。当前主流产品都具有多负载冗余的能力,基本就是RAID5的变形。
    在更复杂的情况下,例如互联网的数据索引,当前的服务器根本无法支撑。于是就产生一个基础想法,分布式数据库。分布式数据库又可以称为网格数据库。和分布式数据库本质的区别在于,无协调状况下可以自适应的分布数据,并且完成数据的冗余工作。简单来说,理想状态下一个机器加入或者离开网格的影响只体现在性能上。
    听上去很好,不过实际还有很多问题。例如ED中使用的Kadimila协议就是一种分布数据库系统,具体请看前面的一篇文章。Kadimila的最大弱点在于无法模糊搜索。通俗的说,一般搜索引擎,“国家体制”搜索的出的东西,“国家”的搜索结果中一定也有。虽然不一定靠前,能让你一次看到。然而,Kadimila协议中,“国家体制”搜索的出的东西,“国家”基本就出不来了。这是因为需要通过关键字来计算HashNum,然后查找索引了数据的计算机。关键字变了,索引也变了,因此结果就不一样了。
    这是一个关键而难解决的问题,问题的关键则在于“必须根据关键字做出索引”上。如果没有关键字索引和根据索引查找机器,那么查询就会在所有机器上实行。从而带来非常大的开销。而通过关键字索引的话,目前没有一种算法能够获得一个上下文所有的关键字。因此我们只能将内容和可能最大的几个关键字关联,最终导致搜索不全。
    最后就是传输过程的保密性和安全性,这是一个密码学上的双重意义的概念。保密就是传输的内容不会为第三者所知道,安全性则是传输的内容不会为第三者篡改。当然,在这里还要加入一个不会为第三者阻拦。
    P2P的客户端大部分数据都来自其他客户端,由此,其他客户端可以巧妙的构造传输内容,使得客户