在C#的网络开发场景中,判断IP地址属于哪个网段、进行子网掩码计算是高频需求,比如实现局域网设备扫描、网络访问权限控制等功能时都需要用到相关逻辑。核心思路是将IP地址和子网掩码转换为32位二进制数,通过按位与运算得到网络地址,再对比网络地址判断归属。

基础概念说明
IP地址是互联网上设备的唯一标识,IPv4地址由32位二进制数组成,通常分为4段十进制数表示,每段范围是0到255。子网掩码用于划分网络地址和主机地址,同样由32位二进制数组成,左边是连续的1表示网络位,右边是连续的0表示主机位。
网段判断的核心逻辑是:将IP地址和子网掩码做按位与运算,得到的结果就是该IP地址所在的网络地址,相同网络地址的IP属于同一个网段。
IP地址合法性校验
在进行计算前,首先需要校验输入的IP地址是否合法,C#中可以通过System.Net.IPAddress类实现基础校验,同时需要额外处理一些边界情况。
using System;
using System.Net;
public class IpHelper
{
/// <summary>
/// 校验IPv4地址是否合法
/// </summary>
/// <param name="ip">待校验的IP地址字符串</param>
/// <returns>合法返回true,否则返回false</returns>
public static bool IsValidIpv4(string ip)
{
if (string.IsNullOrEmpty(ip))
{
return false;
}
// 尝试解析IP地址
if (IPAddress.TryParse(ip, out IPAddress ipAddress))
{
// 排除IPv6地址,只处理IPv4
if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
// 进一步校验格式,避免类似"192.168.1"这种不完整的地址被解析
string[] segments = ip.Split('.');
return segments.Length == 4;
}
}
return false;
}
}
子网掩码计算与校验
子网掩码需要符合二进制中1连续、0连续且1在前的规则,我们可以通过转换二进制数来校验子网掩码的合法性,同时支持通过前缀长度(比如/24)生成对应的子网掩码。
using System;
using System.Linq;
public class SubnetMaskHelper
{
/// <summary>
/// 校验子网掩码是否合法
/// </summary>
/// <param name="subnetMask">子网掩码字符串</param>
/// <returns>合法返回true,否则返回false</returns>
public static bool IsValidSubnetMask(string subnetMask)
{
if (!IpHelper.IsValidIpv4(subnetMask))
{
return false;
}
// 将子网掩码转换为32位无符号整数
uint maskValue = IpToUint(subnetMask);
// 取反后加1,如果结果是2的幂次,说明子网掩码合法
uint reverseMask = ~maskValue + 1;
return (reverseMask & (reverseMask - 1)) == 0;
}
/// <summary>
/// 根据前缀长度生成子网掩码,比如前缀24对应255.255.255.0
/// </summary>
/// <param name="prefixLength">前缀长度,范围1-32</param>
/// <returns>子网掩码字符串</returns>
public static string GenerateSubnetMaskByPrefix(int prefixLength)
{
if (prefixLength < 1 || prefixLength > 32)
{
throw new ArgumentException("前缀长度必须在1到32之间");
}
// 生成32位二进制数,前prefixLength位是1,其余是0
uint maskValue = prefixLength == 0 ? 0 : 0xFFFFFFFF << (32 - prefixLength);
return UintToIp(maskValue);
}
/// <summary>
/// 将IP地址字符串转换为32位无符号整数
/// </summary>
private static uint IpToUint(string ip)
{
string[] segments = ip.Split('.');
uint result = 0;
for (int i = 0; i < 4; i++)
{
result = (result << 8) | uint.Parse(segments[i]);
}
return result;
}
/// <summary>
/// 将32位无符号整数转换为IP地址字符串
/// </summary>
private static string UintToIp(uint value)
{
return string.Format("{0}.{1}.{2}.{3}",
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF);
}
}
判断IP地址所属网段
结合前面的基础方法,我们可以实现判断两个IP是否属于同一网段,以及获取某个IP所在网段网络地址的功能。
using System;
public class NetworkSegmentHelper
{
/// <summary>
/// 判断两个IP地址是否属于同一个网段
/// </summary>
/// <param name="ip1">第一个IP地址</param>
/// <param name="ip2">第二个IP地址</param>
/// <param name="subnetMask">子网掩码</param>
/// <returns>属于同一网段返回true,否则返回false</returns>
public static bool IsSameNetworkSegment(string ip1, string ip2, string subnetMask)
{
if (!IpHelper.IsValidIpv4(ip1) || !IpHelper.IsValidIpv4(ip2))
{
throw new ArgumentException("输入的IP地址不合法");
}
if (!SubnetMaskHelper.IsValidSubnetMask(subnetMask))
{
throw new ArgumentException("输入的子网掩码不合法");
}
uint ip1Value = SubnetMaskHelper.IpToUint(ip1);
uint ip2Value = SubnetMaskHelper.IpToUint(ip2);
uint maskValue = SubnetMaskHelper.IpToUint(subnetMask);
// 分别和子网掩码做按位与运算,得到网络地址
uint network1 = ip1Value & maskValue;
uint network2 = ip2Value & maskValue;
return network1 == network2;
}
/// <summary>
/// 获取IP地址所在的网络地址
/// </summary>
/// <param name="ip">IP地址</param>
/// <param name="subnetMask">子网掩码</param>
/// <returns>网络地址字符串</returns>
public static string GetNetworkAddress(string ip, string subnetMask)
{
if (!IpHelper.IsValidIpv4(ip))
{
throw new ArgumentException("输入的IP地址不合法");
}
if (!SubnetMaskHelper.IsValidSubnetMask(subnetMask))
{
throw new ArgumentException("输入的子网掩码不合法");
}
uint ipValue = SubnetMaskHelper.IpToUint(ip);
uint maskValue = SubnetMaskHelper.IpToUint(subnetMask);
uint networkValue = ipValue & maskValue;
return SubnetMaskHelper.UintToIp(networkValue);
}
}
完整使用示例
下面是调用上述方法的具体示例,演示如何判断IP归属网段以及获取网络地址。
using System;
class Program
{
static void Main()
{
string ip1 = "192.168.1.100";
string ip2 = "192.168.1.200";
string subnetMask = "255.255.255.0";
string prefix = "24";
// 校验IP和子网掩码合法性
Console.WriteLine($"IP1合法性:{IpHelper.IsValidIpv4(ip1)}");
Console.WriteLine($"子网掩码合法性:{SubnetMaskHelper.IsValidSubnetMask(subnetMask)}");
// 根据前缀生成子网掩码
string generatedMask = SubnetMaskHelper.GenerateSubnetMaskByPrefix(int.Parse(prefix));
Console.WriteLine($"前缀/{prefix}对应的子网掩码:{generatedMask}");
// 判断两个IP是否同网段
bool isSame = NetworkSegmentHelper.IsSameNetworkSegment(ip1, ip2, subnetMask);
Console.WriteLine($"{ip1}和{ip2}是否同网段:{isSame}");
// 获取IP所在网络地址
string networkAddr = NetworkSegmentHelper.GetNetworkAddress(ip1, subnetMask);
Console.WriteLine($"{ip1}所在的网络地址:{networkAddr}");
}
}
注意事项
- 上述代码仅处理IPv4地址,如果需要支持IPv6,需要调整二进制转换和运算逻辑。
- 子网掩码校验时,排除了类似255.0.255.0这种1不连续的非法掩码,符合网络规范。
- 实际开发中如果需要处理大量IP判断,可以将IP转换后的整数缓存,减少重复转换的开销。