在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 TCP 协议的请求的程序或者服务(如 WCF 服务)。
要实现这样的功能,核心的一点就是在得到当前机器同网段的所有机器的 IP 后,对每一 IP 发生 TCP 连接请求,如果请求超时或者出现其它异常,则认为没有服务,反之,如果能够正常连接,则认为服务正常。
经过基本功能的实现以及后续的重构之后,就有了本文以下的代码:一个接口和具体实现的类。需要说明的是:在下面的代码中,先提到接口,再提到具体类;而在开发过程中,则是首先创建了类,然后才提取了接口。之所以要提取接口,原因有二:一是可以支持 IoC控制反转;二是将来如果其它的同类需求,可以基于此接口创建新类而实现新需求。
回到顶部
一、接口定义
先看来一下接口:
/// <summary>
/// 扫描服务
/// </summary>
public interface IServerScanner
{
/// <summary>
/// 扫描完成
/// </summary>
event EventHandler<List<ConnectionResult>> OnScanComplete;
/// <summary>
/// 报告扫描进度
/// </summary>
event EventHandler<ScanProgressEventArgs> OnScanProgressChanged;
/// <summary>
/// 扫描端口
/// </summary>
int ScanPort { get; set; }
/// <summary>
/// 单次连接超时时长
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// 返回指定的IP与端口是否能够连接上
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <returns></returns>
bool IsConnected(IPAddress ipAddress, int port);
/// <summary>
/// 返回指定的IP与端口是否能够连接上
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
/// <returns></returns>
bool IsConnected(string ip, int port);
/// <summary>
/// 开始扫描
/// </summary>
void StartScan();
}
其中 Timeout 属性是控制每次连接请求超时的时长。
回到顶部
二、具体实现
再来看一下具体实现类:
ServerScanner
以上代码中注释基本上已经比较详细,这里再简单提几个点:
- TestConnection 函数实了现核心功能,即请求给定的 IP 和端口,并返回结果;其中通过调用 IAsyncResult.AsyncWaitHandle 属性的 WaitOne 方法来实现对超时的控制;
- StartScan 方法中,在得到 IP 列表后,通过生成委托列表并异步调用这些委托来实现整个方法是异步的,不会阻塞 UI,而这些委托指向的方法就是 TestConnection 函数;
- 使用同步上下文 SynchronizationContext,可以保证调用方在原来的线程(通常是 UI 线程)上处理进度更新事件或扫描完成事件;
- 对于每个委托异步完成后,会执行回调方法 OnComplete,在它里面,对全局变量的操作需要加锁,以保证线程安全。
回到顶部
三、如何使用
最后来看一下如何使用,非常简单:
private void View_Loaded()
{
// 在界面 Load 事件中添加以下代码
ServerScanner.OnScanComplete += ServerScanner_OnScanComplete;
ServerScanner.OnScanProgressChanged += ServerScanner_OnScanProgressChanged;
// 扫描的端口号
ServerScanner.ScanPort = 7890;
}
private void StartScan()
{
// 开始扫描
ServerScanner.StartScan();
}
private void ServerScanner_OnScanComplete(object sender, List<ConnectionResult> e)
{
...
}
private void ServerScanner_OnScanProgressChanged(object sender, ScanProgressEventArgs e)
{
...
}
如果你有更好的建议或意见,请留言互相交流。











