当前位置: 移动技术网 > IT编程>开发语言>.net > 在.NET中扫描局域网服务的实现方法

在.NET中扫描局域网服务的实现方法

2018年01月25日  | 移动技术网IT编程  | 我要评论

封天印地txt下载,中老年发型设计,荷花池汽车站

在最近负责的项目中,需要实现这样一个需求:在客户端程序中,扫描当前机器所在网段中的所有机器上是否有某服务启动,并把所有已经启动服务的机器列出来,供用户选择,连接哪个服务。注意:这里所说的服务事实上就是在一个固定的端口监听基于 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 属性是控制每次连接请求超时的时长。

二、具体实现

再来看一下具体实现类:

/// <summary>
 /// 扫描结果
 /// </summary>
 public class connectionresult
 {
 /// <summary>
 /// ipaddress 地址
 /// </summary>
 public ipaddress address { get; set; }
 /// <summary>
 /// 是否可连接上
 /// </summary>
 public bool canconnected { get; set; }
 }
 /// <summary>
 /// 扫描完成事件参数
 /// </summary>
 public class scancompleteeventargs
 {
 /// <summary>
 /// 结果集合
 /// </summary>
 public list<connectionresult> reslut { get; set; }
 }
 /// <summary>
 /// 扫描进度事件参数
 /// </summary>
 public class scanprogresseventargs
 {
 /// <summary>
 /// 进度百分比
 /// </summary>
 public int percent { get; set; }
 }
 /// <summary>
 /// 扫描局域网中的服务
 /// </summary>
 public class serverscanner : iserverscanner
 {
 /// <summary>
 /// 同一网段内 ip 地址的数量
 /// </summary>
 private const int segmentipmaxcount = 255;
 private datetimeoffset _endtime;
 private object _locker = new object();
 private synchronizationcontext _originalcontext = synchronizationcontext.current;
 private list<connectionresult> _resultlist = new list<connectionresult>();
 private datetimeoffset _starttime;
 /// <summary>
 /// 记录调用/完成委托的数量
 /// </summary>
 private int _totalcount = 0;
 public serverscanner()
 {
  timeout = timespan.fromseconds(2);
 }
 /// <summary>
 /// 当扫描完成时,触发此事件
 /// </summary>
 public event eventhandler<list<connectionresult>> onscancomplete;
 /// <summary>
 /// 当扫描进度发生更改时,触发此事件
 /// </summary>
 public event eventhandler<scanprogresseventargs> onscanprogresschanged;
 /// <summary>
 /// 扫描端口
 /// </summary>
 public int scanport { get; set; }
 /// <summary>
 /// 单次请求的超时时长,默认为2秒
 /// </summary>
 public timespan timeout { get; set; }
 /// <summary>
 /// 使用 tcpclient 测试是否可以连上指定的 ip 与 port
 /// </summary>
 /// <param name="ipaddress"></param>
 /// <param name="port"></param>
 /// <returns></returns>
 public bool isconnected(ipaddress ipaddress, int port)
 {
  var result = testconnection(ipaddress, port);
  return result.canconnected;
 }
 /// <summary>
 /// 使用 tcpclient 测试是否可以连上指定的 ip 与 port
 /// </summary>
 /// <param name="ip"></param>
 /// <param name="port"></param>
 /// <returns></returns>
 public bool isconnected(string ip, int port)
 {
  ipaddress ipaddress;
  if (ipaddress.tryparse(ip, out ipaddress))
  {
  return isconnected(ipaddress, port);
  }
  else
  {
  throw new argumentexception("ip 地址格式不正确");
  }
 }
 /// <summary>
 /// 开始扫描当前网段
 /// </summary>
 public void startscan()
 {
  if (scanport == 0)
  {
  throw new invalidoperationexception("必须指定扫描的端口 scanport");
  }
  // 清除可能存在的数据
  _resultlist.clear();
  _totalcount = 0;
  _starttime = datetimeoffset.now;
  // 得到本网段的 ip
  var iplist = getallremoteiplist();
  // 生成委托列表
  list<func<ipaddress, int, connectionresult>> funcs = new list<func<ipaddress, int, connectionresult>>();
  for (int i = 0; i < segmentipmaxcount; i++)
  {
  var tmpf = new func<ipaddress, int, connectionresult>(testconnection);
  funcs.add(tmpf);
  }
  // 异步调用每个委托
  for (int i = 0; i < segmentipmaxcount; i++)
  {
  funcs[i].begininvoke(iplist[i], scanport, oncomplete, funcs[i]);
  _totalcount += 1;
  }
 }
 /// <summary>
 /// 得到本网段的所有 ip
 /// </summary>
 /// <returns></returns>
 private list<ipaddress> getallremoteiplist()
 {
  var localname = dns.gethostname();
  var localipentry = dns.gethostentry(localname);
  list<ipaddress> iplist = new list<ipaddress>();
  ipaddress localinterip = localipentry.addresslist.firstordefault(m => m.addressfamily == addressfamily.internetwork);
  if (localinterip == null)
  {
  throw new invalidoperationexception("当前计算机不存在内网 ip");
  }
  var localinteripbytes = localinterip.getaddressbytes();
  for (int i = 1; i <= segmentipmaxcount; i++)
  {
  // 对末位进行替换
  localinteripbytes[3] = (byte)i;
  iplist.add(new ipaddress(localinteripbytes));
  }
  return iplist;
 }
 private void oncomplete(iasyncresult ar)
 {
  var state = ar.asyncstate as func<ipaddress, int, connectionresult>;
  var result = state.endinvoke(ar);
  lock (_locker)
  {
  // 添加到结果中
  _resultlist.add(result);
  // 报告进度
  _totalcount -= 1;
  var percent = (segmentipmaxcount - _totalcount) * 100 / segmentipmaxcount;
  if (synchronizationcontext.current == _originalcontext)
  {
   onscanprogresschanged?.invoke(this, new scanprogresseventargs { percent = percent });
  }
  else
  {
   _originalcontext.post(constate =>
   {
   onscanprogresschanged?.invoke(this, new scanprogresseventargs { percent = percent });
   }, null);
  }
  if (_totalcount == 0)
  {
   // 通过事件抛出结果
   if (synchronizationcontext.current == _originalcontext)
   {
   onscancomplete?.invoke(this, _resultlist);
   }
   else
   {
   _originalcontext.post(constate =>
   {
    onscancomplete?.invoke(this, _resultlist);
   }, null);
   }
   // 计算耗时
   debug.writeline("compete");
   _endtime = datetimeoffset.now;
   debug.writeline($"duration: {_endtime - _starttime}");
  }
  }
 }
 /// <summary>
 /// 测试是否可以连接到
 /// </summary>
 /// <param name="address"></param>
 /// <param name="port"></param>
 /// <returns></returns>
 private connectionresult testconnection(ipaddress address, int port)
 {
  tcpclient c = new tcpclient();
  connectionresult result = new connectionresult();
  result.address = address;
  using (tcpclient tcp = new tcpclient())
  {
  iasyncresult ar = tcp.beginconnect(address, port, null, null);
  waithandle wh = ar.asyncwaithandle;
  try
  {
   if (!ar.asyncwaithandle.waitone(timeout, false))
   {
   tcp.close();
   }
   else
   {
   tcp.endconnect(ar);
   result.canconnected = true;
   }
  }
  catch
  {
  }
  finally
  {
   wh.close();
  }
  }
  return result;
 }
 }
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)
  {
   ...
  }

如果你有更好的建议或意见,请留言互相交流。

以上这篇在.net中扫描局域网服务的实现方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网