简单高效的短链接生成服务C#实现.docx

上传人:b****5 文档编号:8575076 上传时间:2023-01-31 格式:DOCX 页数:15 大小:20.45KB
下载 相关 举报
简单高效的短链接生成服务C#实现.docx_第1页
第1页 / 共15页
简单高效的短链接生成服务C#实现.docx_第2页
第2页 / 共15页
简单高效的短链接生成服务C#实现.docx_第3页
第3页 / 共15页
简单高效的短链接生成服务C#实现.docx_第4页
第4页 / 共15页
简单高效的短链接生成服务C#实现.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

简单高效的短链接生成服务C#实现.docx

《简单高效的短链接生成服务C#实现.docx》由会员分享,可在线阅读,更多相关《简单高效的短链接生成服务C#实现.docx(15页珍藏版)》请在冰豆网上搜索。

简单高效的短链接生成服务C#实现.docx

简单高效的短链接生成服务C#实现

简单高效的短链接生成服务C#实现

项目中有一处需求,需要把长网址缩为短网址,把结果通过短信、微信等渠道推送给客户。

刚开始直接使用网上现成的开放服务,然后在某个周末突然手痒想自己动手实现一个别具特色的长网址(文本)缩短服务。

由于以前做过socket服务,对数据包的封装排列还有些印象,因此,短网址服务我第一反应是先设计数据的存储格式,我这里没有采用数据库,而是使用2个文件来实现:

Url.db存储用户提交的长网址文本,Url.idx存储数据索引,记录每次提交数据的位置(Begin)与长度(Length),还有一些附带信息(Hits,DateTime)。

由于每次添加长网址,对两个文件都是进行Append操作,因此即使这两个文件体积很大(比如若干GB),也没有太大的IO压力。

再看看Url.idx文件的结构,ID是主键,设为Int64类型,转换为字节数组后的长度为8,紧跟的是Begin,该值是把长网址数据续写到Url.db文件之前,Url.db文件的长度,同样设为Int64类型。

长网址的字符串长度有限,Int16足够使用了,Int16.MaxValue==65536,比Url规范定义的4Kb长度还大,Int16转换为字节数组后长度为2字节。

Hits表示短网址的解析次数,设为Int32,字节长度为4,DateTime设为Int64,长度8。

由于ID不会像数据库那样自动递增,因此需要手工实现。

因此在开始写入Url.idx前,需要预先读取最后一行(行是虚的,其实就是最后30字节)中的的ID值,递增后才开始写入新的一行。

也就是说每次提交一个长网址,不管数据有多长(最大不能超过65536字节),Url.idx文件都固定增加30字节。

数据结构一旦明确下来,整个网址缩短服务就变得简单明了。

例如连续两次提交长网址,可能得到的短网址为http:

//域名/1000,与http:

//域名/1001,结果显然很丑陋,域名后面的ID全是数字,而且递增关系明显,很容易暴力枚举全部的数据。

而且10进制的数字容量有限,一次提交100万条的长网址,产生的短网址越来越长,失去意义。

因此下面就开始对ID进行改造,改造的目标有2:

1、增加混淆机制,相邻两个ID表面上看不出区别。

2、增加容量,一次性提交100万条长网址,ID的长度不能有明显变化。

最简单最直接的混淆机制,就是把10进制转换为62进制(0-9a-zA-Z),由于顺序的abcdef…也很容易猜到下一个ID,因此62进制字符序列随机排列一次:

///

///生成随机的0-9a-zA-Z字符串

///

///

publicstaticstringGenerateKeys()

{

string[]Chars="0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".Split(',');

intSeekSeek=unchecked((int)DateTime.Now.Ticks);

RandomSeekRand=newRandom(SeekSeek);

for(inti=0;i<100000;i++)

{

intr=SeekRand.Next(1,Chars.Length);

stringf=Chars[0];

Chars[0]=Chars[r-1];

Chars[r-1]=f;

}

returnstring.Join("",Chars);

}

运行一次上面的方法,得到随机序列:

stringSeq="s9LFkgy5RovixI1aOf8UhdY3r4DMplQZJXPqebE0WSjBn7wVzmN2Gc6THCAKut";

用这个序列字符串替代0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,具有很强的混淆特性。

一个10进制的数字按上面的序列转换为62进制,将变得面目全非,附转换方法:

///

///10进制转换为62进制

///

///

///

privatestaticstringConvert(longid)

{

if(id<62)

{

returnSeq[(int)id].ToString();

}

inty=(int)(id%62);

longx=(long)(id/62);

returnConvert(x)+Seq[y];

}

///

///将62进制转为10进制

///

///

///

privatestaticlongConvert(stringNum)

{

longv=0;

intLen=Num.Length;

for(inti=Len-1;i>=0;i--)

{

intt=Seq.IndexOf(Num[i]);

doubles=(Len-i)-1;

longm=(long)(Math.Pow(62,s)*t);

v+=m;

}

returnv;

}

例如执行Convert(123456789)得到RYswX,执行Convert(123456790)得到RYswP。

如果通过分析大量的连续数值,还是可以暴力算出上面的Seq序列值,进而猜测到某个ID左右两边的数值。

下面进一步强化混淆,ID每次递增的单位不是固定的1,而是一个随机值,比如1000,1005,1013,1014,1020,毫无规律可言。

privatestaticInt16GetRnd(RandomseekRand)

{

Int16s=(Int16)seekRand.Next(1,11);

returns;

}

即使把62进制的值逆向计算出10进制的ID值,也难于猜测到左右两边的值,大大增加暴力枚举的难度。

难度虽然增加,但是连续产生的2个62进制值如前面的RyswX与RyswP,仅个位数不同,还是很像,因此我们再进行第三次简单的混淆,把62进制字符向左(右)旋转一定次数(解析时反向旋转同样的次数):

///

///混淆id为字符串

///

///

///

privatestaticstringMixup(longid)

{

stringKey=Convert(id);

ints=0;

foreach(charcinKey)

{

s+=(int)c;

}

intLen=Key.Length;

intx=(s%Len);

char[]arr=Key.ToCharArray();

char[]newarr=newchar[arr.Length];

Array.Copy(arr,x,newarr,0,Len-x);

Array.Copy(arr,0,newarr,Len-x,x);

stringNewKey="";

foreach(charcinnewarr)

{

NewKey+=c;

}

returnNewKey;

}

///

///解开混淆字符串

///

///

///

privatestaticlongUnMixup(stringKey)

{

ints=0;

foreach(charcinKey)

{

s+=(int)c;

}

intLen=Key.Length;

intx=(s%Len);

x=Len-x;

char[]arr=Key.ToCharArray();

char[]newarr=newchar[arr.Length];

Array.Copy(arr,x,newarr,0,Len-x);

Array.Copy(arr,0,newarr,Len-x,x);

stringNewKey="";

foreach(charcinnewarr)

{

NewKey+=c;

}

returnConvert(NewKey);

}

执行Mixup(123456789)得到wXRYs,假如随机递增值为7,则下一条记录的ID执行Mixup(123456796)得到swWRY,肉眼上很难再联想到这两个ID值是相邻的。

以上讲述了数据结构与ID的混淆机制,下面讲述的是短网址的解析机制。

得到了短网址,如wXRYs,我们可以通过上面提供的UnMixup()方法,逆向计算出ID值,由于ID不是递增步长为1的数字,因此不能根据ID马上计算出记录在索引文件中的位置(如:

ID*30)。

由于ID是按小到大的顺序排列,因此在索引文件中定位ID,非二分查找法莫属。

//二分法查找的核心代码片段

FileStreamIndex=newFileStream(IndexFile,FileMode.OpenOrCreate,FileAccess.ReadWrite);

longId=;//解析短网址得到的真实ID

longLeft=0;

longRight=(long)(Index.Length/30)-1;

longMiddle=-1;

while(Left<=Right)

{

Middle=(long)(Math.Floor((double)((Right+Left)/2)));

if(Middle<0)break;

Index.Position=Middle*30;

Index.Read(buff,0,8);

longval=BitConverter.ToInt64(buff,0);

if(val==Id)break;

if(val

{

Left=Middle+1;

}

else

{

Right=Middle-1;

}

}

Index.Close();

二分法查找的核心是不断移动指针,读取中间的8字节,转换为数字后再与目标ID比较的过程。

这是一个非常高速的算法,如果有接近43亿条短网址记录,查找某一个ID,最多只需要移动32次指针(上面的while循环32次)就能找到结果,因为2^32=4294967296。

用二分法查找是因为前面使用了随机递增步长,如果递增步长设为1,则二分法可免,直接从ID*30就能一次性精准定位到索引文件中的位置。

下面是完整的代码,封装了一个ShortenUrl类:

usingSystem;

usingSystem.Linq;

usingSystem.Web;

usingSystem.IO;

usingSystem.Text;

///

///ShortenUrl的摘要说明

///

publicclassShortenUrl

{

conststringSeq="s9LFkgy5RovixI1aOf8UhdY3r4DMplQZJXPqebE0WSjBn7wVzmN2Gc6THCAKut";

privatestaticstringDataFile

{

get{returnHttpContext.Current.Server.MapPath("/Url.db");}

}

privatestaticstringIndexFile

{

get{returnHttpContext.Current.Server.MapPath("/Url.idx");}

}

///

///批量添加网址,按顺序返回Key。

如果输入的一组网址中有不合法的元素,则返回数组的相同位置(下标)的元素将为null。

///

///

///

publicstaticstring[]AddUrl(string[]Url)

{

FileStreamIndex=newFileStream(IndexFile,FileMode.OpenOrCreate,FileAccess.ReadWrite);

FileStreamData=newFileStream(DataFile,FileMode.Append,FileAccess.Write);

Data.Position=Data.Length;

DateTimeNow=DateTime.Now;

byte[]dt=BitConverter.GetBytes(Now.ToBinary());

int_Hits=0;

byte[]Hits=BitConverter.GetBytes(_Hits);

string[]ResultKey=newstring[Url.Length];

intseekSeek=unchecked((int)Now.Ticks);

RandomseekRand=newRandom(seekSeek);

stringHost=HttpContext.Current.Request.Url.Host.ToLower();

byte[]Status=BitConverter.GetBytes(true);

//index:

ID(8)+Begin(8)+Length

(2)+Hits(4)+DateTime(8)=30

for(inti=0;i4096)continue;

longBegin=Data.Position;

byte[]UrlData=Encoding.UTF8.GetBytes(Url[i]);

Data.Write(UrlData,0,UrlData.Length);

byte[]buff=newbyte[8];

longLast;

if(Index.Length>=30)//读取上一条记录的ID

{

Index.Position=Index.Length-30;

Index.Read(buff,0,8);

Index.Position+=22;

Last=BitConverter.ToInt64(buff,0);

}

else

{

Last=1000000;//起步ID,如果太小,生成的短网址会太短。

Index.Position=0;

}

longRandKey=Last+(long)GetRnd(seekRand);

byte[]BeginData=BitConverter.GetBytes(Begin);

byte[]LengthData=BitConverter.GetBytes((Int16)(UrlData.Length));

byte[]RandKeyData=BitConverter.GetBytes(RandKey);

Index.Write(RandKeyData,0,8);

Index.Write(BeginData,0,8);

Index.Write(LengthData,0,2);

Index.Write(Hits,0,Hits.Length);

Index.Write(dt,0,dt.Length);

ResultKey[i]=Mixup(RandKey);

}

Data.Close();

Index.Close();

returnResultKey;

}

///

///按顺序批量解析Key,返回一组长网址。

///

///

///

publicstaticstring[]ParseUrl(string[]Key)

{

FileStreamIndex=newFileStream(IndexFile,FileMode.OpenOrCreate,FileAccess.ReadWrite);

FileStreamData=newFileStream(DataFile,FileMode.Open,FileAccess.Read);

byte[]buff=newbyte[8];

long[]Ids=Key.Select(n=>UnMixup(n)).ToArray();

string[]Result=newstring[Ids.Length];

long_Right=(long)(Index.Length/30)-1;

for(intj=0;j

{

longId=Ids[j];

longLeft=0;

longRight=_Right;

longMiddle=-1;

while(Left<=Right)

{

Middle=(long)(Math.Floor((double)((Right+Left)/2)));

if(Middle<0)break;

Index.Position=Middle*30;

Index.Read(buff,0,8);

longval=BitConverter.ToInt64(buff,0);

if(val==Id)break;

if(val

{

Left=Middle+1;

}

else

{

Right=Middle-1;

}

}

stringUrl=null;

if(Middle!

=-1)

{

Index.Position=Middle*30+8;//跳过ID

Index.Read(buff,0,buff.Length);

longBegin=BitConverter.ToInt64(buff,0);

Index.Read(buff,0,buff.Length);

Int16Length=BitConverter.ToInt16(buff,0);

byte[]UrlTxt=newbyte[Length];

Data.Position=Begin;

Data.Read(UrlTxt,0,UrlTxt.Length);

intHits=BitConverter.ToInt32(buff,2);//跳过2字节的Length

byte[]NewHits=BitConverter.GetBytes(Hits+1);//解析次数递增,4字节

Index.Position-=6;//指针撤回到Length之后

Index.Write(NewHits,0,NewHits.Length);//覆盖老的Hits

Url=Encoding.UTF8.GetString(UrlTxt);

}

Result[j]=Url;

}

Data.Close();

Index.Close();

returnResult;

}

///

///混淆id为字符串

///

///

///

privatestaticstringMixup(longid)

{

stringKey=Convert(id);

ints=0;

foreach(charcinKey)

{

s+=(int)c;

}

intLen=Key.Length;

intx=(s%Len);

char[]arr=Key.ToCharArray();

char[]newarr=newchar[arr.Length];

Array.Copy(arr,x,newarr,0,Len-x);

Array.Copy(arr,0,newarr,Len-x,x);

stringNewKey="";

foreach(charcinnewarr)

{

NewKey+=c;

}

returnNewKey;

}

///

///解开混淆字符串

///

///

///

privatestaticlongUnMixup(stringKey)

{

ints=0;

foreach(charcinKey)

{

s+=(int)c;

}

intLen=Key.Length;

intx=(s%Len);

x=Len-x;

char[]arr=Key.ToCharArray();

char[]newarr=newchar[arr.Length];

Array.Copy(arr,x,newarr,0,Len-x);

Array.Copy(arr,0,newarr,Len-x,x);

stringNewKey="";

foreach(charcinnewarr)

{

NewKey+=c;

}

returnConvert(NewKey);

}

///

///10进制转换为62进制

///

///

///

privatestaticstringConvert(longid)

{

if(id<62)

{

returnSeq[(int)id].ToString();

}

inty=(int)(id%62);

longx=(long)(id/62);

returnConvert(x)+Seq[y];

}

///

///将62进制转为10进制

///

///

///

privatestaticlongConvert(stringNum)

{

longv=0;

intLen=Num.Length;

for(inti=Len-1;i>=0;i--)

{

intt=Seq.IndexOf(Num[i]);

doubles=(Len-i)-1;

longm=(long)(Math.Pow(62,s)*t);

v+=m;

}

returnv;

}

///

///生成随机的0-9a-zA-Z字符串

///

///

publicstaticstringGenerateKeys()

{

string[]Chars="0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1