今天在项目中考虑这样一件事情:我需要动态实例化一个服务,监听某个端口。那么该怎么来实现这个需求呢?
我立马想到是否有这样的函数,例如GetAvaliablePorts呢?主意不错,但确没有找到。原先Win32 API中有一个函数(EnumPorts),但import来过来之后也没有用。
此路不通,看来要自己动手了。再大的困难也吓不倒英雄的中华儿女嘛。
首先,要知道一些有关端口号的基础知识
- 所有的端口都应该大于0,而且小于65535
- 微软建议,1024及以前的端口号保留给系统用。也就是说,我们自己程序监听的端口最好是大于1024
其次,因为没有内置的方法来测试某个端口是否可用,我们可能需要自己编写方法来做这个事情。那么怎么做呢?我想到,可以用socket来测试。我编写了如下这样一个函数
/// <summary>
/// 这个方法是验证某个端口是否可用
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
static bool IsAvaliable(int port)
{
Socket sk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
bool result = false;
try
{
sk.Bind(new IPEndPoint(IPAddress.Any, port));//尝试绑定,因为如果该端口已经被使用,则无法绑定,会导致异常
result = true;
}
catch
{
result = false;
}
finally
{
sk.Close();
}
return result;
}
然后,我就可以在代码中这样使用
static void Main(string[] args)
{
Console.WriteLine(IsAvaliable(8080));//如果8080端口可用,则返回true,反之,返回false
Console.Read();
}
最后,我还可以编写另外一个方法,得到一个可用的端口
/// <summary>
/// 这个方法取得一个可用的端口
/// </summary>
/// <returns></returns>
static int GetAvaliablePort()
{
Random rnd = new Random();
int port = rnd.Next(1024, 65535);//随机产生一个动态的端口号
while (!IsAvaliable(port))
{
port = rnd.Next(1024, 65535);
}
return port;
}
在调用程序中,就可以这样来调用它
Console.WriteLine(GetAvaliablePort());
看起来不错,对吧?但是如果我们要一次性得到10个不同的可用端口号呢?我们会大致下面这样写代码
for (int i = 0; i < 10; i++)
{
Console.WriteLine(GetAvaliablePort());
}
但是,让人吃惊的是,我们看到下面这样的输出结果。
这是如何解释呢?为什么既然是随机数,又仍然是重复呢?
这是因为我们的10次循环之间几乎没有间隔(特别是我的机器配置又很好的情况下),所以,对于随机数的产生器,它实际上无法进行区分。我自己感觉就是,如果两个操作之间几乎没有时间间隔,那么随机数也是一样的。
那么该如何解决该问题?好吧,我们可以刻意地让每次循环有一些间隔
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);//我们强制让进程休眠0.1秒
Console.WriteLine(GetAvaliablePort());
}
这样大家总该是没有意见了吧?
最后,我们对代码进行一些重构,封装之后的完整代码如下
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
PortHelper helper = new PortHelper();
Console.WriteLine("当前8080端口是否可用?"+helper.IsAvaliable(8080));
Console.WriteLine("取得10个可用的端口:");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
Console.WriteLine(helper.GetAvaliablePort());
}
Console.Read();
}
}
public class PortHelper
{
/// <summary>
/// 这个方法是验证某个端口是否可用
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public bool IsAvaliable(int port)
{
Socket sk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
bool result = false;
try
{
sk.Bind(new IPEndPoint(IPAddress.Any, port));//尝试绑定,因为如果该端口已经被使用,则无法绑定,会导致异常
result = true;
}
catch
{
result = false;
}
finally
{
sk.Close();
}
return result;
}
/// <summary>
/// 这个方法取得一个可用的端口
/// </summary>
/// <returns></returns>
public int GetAvaliablePort()
{
Random rnd = new Random();
int port = rnd.Next(1024, 65535);//随机产生一个动态的端口号
while (!IsAvaliable(port))
{
port = rnd.Next(1024, 65535);
}
return port;
}
}
}