出于安全考虑,几乎每一个动态网站都具有IP
地址屏蔽功能,而网上流传的不少关于该功能的教程大都采用字符串保存和验证IP
地址,我认为这是不太科学的,我试图找到最佳的设计方案。
“IP
地址的长度为32
位,分为4
段,每段8
位,用十进制数字表示,每段数字范围为0
~255
,段与段之间用句点隔开。”
由此咱们了解到,IP
地址其实是一个32
位正整数,在C#
中可使用
uint
类型来表示,但SQLServer
数据库里好像没有对应的类型;转而使用数据库支持的
int
类型的话,则会出现溢出的状况;所以咱们作出妥协:使用
long(
bigint)
类型。
TIP:
int
取值范围:-2,147,483,648 到 2,147,483,647
uint
取值范围:0
到 4,294,967,295
long
取值范围:-9,223,372,036,854,775,808
到 9,223,372,036,854,775,807
那么如何将IP
地址转为整数呢?咱们看到IPAddress
类中有一个“[
否决的]
”实例属性Address
,这个属性的确能够返回一个
long
值,可是测试一下,获得的数据确实这样的:
“127.0.0.1
” -> 16777343
“127.0.0.2
” –> 33554559
的确该让它“否决”,这样的整数对咱们来讲毫无心义,咱们是没法经过这样的方法比较传入的IP
是否介于两个IP
值之间的。
那么只有本身动手了,咱们将经过IPAddress
类的GetAddressBytes()
实例方法获取IP
的4
个段的值,而后将它们组合为一个整数,下面将提供这个扩展方法:
/// <summary>
///
将
IP
地址转为整数形式
/// </summary>
/// <returns>
整数</returns>
public
static long
转换为整数(
this IPAddress ip)
{
int x = 3;
long o = 0;
foreach (byte f in ip.GetAddressBytes())
{
o += (long)f << 8 * x--;
}
return o;
}
你能够这样使用这个扩展方法:
IPAddress
.Parse("127.0.0.1").
转换为整数()
这里还有一个用于逆转换的扩展方法,用于将
long
转回IPAddress
:
/// <summary>
///
将整数转为
IP
地址
/// </summary>
/// <returns>
IP
地址</returns>
public
static IPAddress
转换为IP
地址(
this long l)
{
var b = new byte[4];
for (int i = 0; i < 4; i++)
{
b[3 - i] = (byte)(l >> 8 * i & 255);
}
return new IPAddress(b);
}
这样咱们就能够经过计算获得正确并有意义的整数了:
“127.0.0.1
” -> 2130706433
“127.0.0.2
” –> 2130706434
OK
,确立了方案核心,下面开始设计SQLServer
数据表:
这样设计后,在添加时将起始和终止IP
地址转为long
类型并存入,并指定一个过时时间。
在验证时只须要获取全部未过时的条目,比较传入的IP
地址是否介于起始值和终止值之间便可。
以往经过字符串存储和验证的方案中,屏蔽时要么屏蔽一个精确的IP
地址,要么就屏蔽一段或两段IP
,如“192.168.*.*
”,要想屏蔽“192.168.1.200
”到“192.168.4.64
”之间的IP
的话,将会很是麻烦;
而咱们这样设计就能够轻松实现:“192.168.1.200
”在数据库里存储的是“3232235976
”,“192.168.4.64
”在数据库中是“3232236608
”,即便使用肉眼也能极快地判断传入的地址是否介于它们之间,更不要说计算机查询了。
下面为数据表生成EDM
模型:
添加IP
屏蔽记录的代码:
/// <summary>
///
添加一个新的
IP
屏蔽区段
/// </summary>
/// <param name="IP
区段起始值">
起始
IP
,如
61.51.200.0</param>
/// <param name="IP
区段终止值">
终止
IP
,如
61.51.255.255</param>
/// <param name="
过时时间">
屏蔽截止时间</param>
/// <returns>
ID
号</returns>
public
static Guid
添加(
string IP
区段起始值,
string IP
区段终止值,
DateTime
过时时间)
{
var id = Guid.NewGuid();
var sip = IPAddress.Parse(IP
区段起始值)
.
转换为整数();
var eip = IPAddress.Parse(IP
区段终止值)
.
转换为整数();
using (var c = new SiteMainEntities())
{
//
检测是否已存在相同的
IP
屏蔽记录
var a = c.IP
地址屏蔽
.Where(f
=> f.
区段起始值
== sip && f.
区段终止值
== eip);
//
若是存在则更新其过时时间
if (a.Count()>0)
{
var l = a.First();
if (l.
过时时间
<
过时时间
) l.
过时时间
=
过时时间;
}
//
不存在则正常添加一个新的屏蔽记录
else c.AddToIP
地址屏蔽(
new IP
地址屏蔽 { ID
= id,
过时时间
=
过时时间
,
区段起始值
= sip,
区段终止值
= eip });
c.SaveChanges();
}
return id;
}
检测指定IP
地址是否被屏蔽的代码:
/// <summary>
///
检测指定
IP
地址是否已受到屏蔽
/// </summary>
/// <param name="IP
地址">
要检测的
IP
地址</param>
/// <returns>
是否属于已屏蔽的
IP</returns>
public
static bool
检测是否被屏蔽(
string IP
地址)
{
var ip = IPAddress.Parse(IP
地址)
.
转换为整数();
using (var c = new SiteMainEntities())
{
return c.IP
地址屏蔽
.Count(f
=> f.
过时时间
> DateTime.Now && ip >= f.
区段起始值
&& ip <= f.
区段终止值)
> 0;
}
}
这种方案比起以往的字符串验证方案来讲优雅了许多,并能够提升数据库查询的效率,建议各位在往后的网站开发中都采用此方案。