Implementation method of scanning LAN service in. NET

  • 2021-10-24 19:19:54
  • OfStack

In the recent project in charge, we need to realize such a requirement: in the client program, scan whether a service is started on all machines in the network segment where the current machine is located, and list all machines that have started the service for users to choose which service to connect to. Note: The service referred to here is actually a program or service (such as WCF service) that listens for requests based on the TCP protocol on a fixed port.

To realize this function, the core point is that after obtaining the IP of all machines in the same network segment of the current machine, an TCP connection request occurs for every 1 IP. If the request times out or other abnormalities occur, it is considered that there is no service; On the contrary, if it can be connected normally, it is considered that the service is normal.

After the implementation of basic functions and subsequent refactoring, we have the following code: 1 interface and specific implementation of the class. It should be noted that in the following code, the interface is mentioned first, and then the concrete class is mentioned; In the development process, the class is created first, and then the interface is extracted. The reason why the interface should be extracted is 2: 1, which can support IoC inversion of control; 2 is that if other similar requirements are required in the future, new functions can be realized in this interface.

1. Interface definition

First look at the interface under 1:


/// <summary>
 ///  Scanning service 
 /// </summary>
 public interface IServerScanner
 {
 /// <summary>
 ///  Scan complete 
 /// </summary>
 event EventHandler<List<ConnectionResult>> OnScanComplete;
 /// <summary>
 ///  Report scan progress 
 /// </summary>
 event EventHandler<ScanProgressEventArgs> OnScanProgressChanged;
 /// <summary>
 ///  Scan port 
 /// </summary>
 int ScanPort { get; set; }
 /// <summary>
 ///  Time out for a single connection 
 /// </summary>
 TimeSpan Timeout { get; set; }
 /// <summary>
 ///  Returns the specified IP Can you connect to the port 
 /// </summary>
 /// <param name="ipAddress"></param>
 /// <param name="port"></param>
 /// <returns></returns>
 bool IsConnected(IPAddress ipAddress, int port);
 /// <summary>
 ///  Returns the specified IP Can you connect to the port 
 /// </summary>
 /// <param name="ip"></param>
 /// <param name="port"></param>
 /// <returns></returns>
 bool IsConnected(string ip, int port);
 /// <summary>
 ///  Start scanning 
 /// </summary>
 void StartScan();
 }

Where the Timeout attribute controls how long each connection request times out.

2. Implementation

Let's look at the concrete implementation class under 1:


/// <summary>
 ///  Scanning results 
 /// </summary>
 public class ConnectionResult
 {
 /// <summary>
 /// IPAddress  Address 
 /// </summary>
 public IPAddress Address { get; set; }
 /// <summary>
 ///  Can you connect to 
 /// </summary>
 public bool CanConnected { get; set; }
 }
 /// <summary>
 ///  Scan completion event parameters 
 /// </summary>
 public class ScanCompleteEventArgs
 {
 /// <summary>
 ///  Result set 
 /// </summary>
 public List<ConnectionResult> Reslut { get; set; }
 }
 /// <summary>
 ///  Scan progress event parameters 
 /// </summary>
 public class ScanProgressEventArgs
 {
 /// <summary>
 ///  Percentage of progress 
 /// </summary>
 public int Percent { get; set; }
 }
 /// <summary>
 ///  Scan services in LAN 
 /// </summary>
 public class ServerScanner : IServerScanner
 {
 /// <summary>
 ///  Same as 1 Within network segment  IP  Number of addresses 
 /// </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>
 ///  Record call / Quantity of delegation completed 
 /// </summary>
 private int _totalCount = 0;
 public ServerScanner()
 {
  Timeout = TimeSpan.FromSeconds(2);
 }
 /// <summary>
 ///  This event is triggered when the scan is complete 
 /// </summary>
 public event EventHandler<List<ConnectionResult>> OnScanComplete;
 /// <summary>
 ///  This event is triggered when the scan progress changes 
 /// </summary>
 public event EventHandler<ScanProgressEventArgs> OnScanProgressChanged;
 /// <summary>
 ///  Scan port 
 /// </summary>
 public int ScanPort { get; set; }
 /// <summary>
 ///  The timeout of a single request, which defaults to 2 Seconds 
 /// </summary>
 public TimeSpan Timeout { get; set; }
 /// <summary>
 ///  Use  TcpClient  Tests whether the specified  IP  And  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>
 ///  Use  TcpClient  Tests whether the specified  IP  And  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  The address format is incorrect ");
  }
 }
 /// <summary>
 ///  Start scanning the current network segment 
 /// </summary>
 public void StartScan()
 {
  if (ScanPort == 0)
  {
  throw new InvalidOperationException(" You must specify a port to scan  ScanPort");
  }
  //  Clear possible data 
  _resultList.Clear();
  _totalCount = 0;
  _startTime = DateTimeOffset.Now;
  //  Object of this network segment  IP
  var ipList = GetAllRemoteIPList();
  //  Generate delegate list 
  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);
  }
  //  Invoke each delegate asynchronously 
  for (int i = 0; i < SegmentIpMaxCount; i++)
  {
  funcs[i].BeginInvoke(ipList[i], ScanPort, OnComplete, funcs[i]);
  _totalCount += 1;
  }
 }
 /// <summary>
 ///  Get all of this network segment  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(" There is no intranet on the current computer  IP");
  }
  var localInterIPBytes = localInterIP.GetAddressBytes();
  for (int i = 1; i <= SegmentIpMaxCount; i++)
  {
  //  Replace the last bit 
  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)
  {
  //  Add to the result 
  _resultList.Add(result);
  //  Report progress 
  _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)
  {
   //  Throw results through events 
   if (SynchronizationContext.Current == _originalContext)
   {
   OnScanComplete?.Invoke(this, _resultList);
   }
   else
   {
   _originalContext.Post(conState =>
   {
    OnScanComplete?.Invoke(this, _resultList);
   }, null);
   }
   //  Calculation time consuming 
   Debug.WriteLine("Compete");
   _endTime = DateTimeOffset.Now;
   Debug.WriteLine($"Duration: {_endTime - _startTime}");
  }
  }
 }
 /// <summary>
 ///  Test whether you can connect to 
 /// </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

The comments in the above code are basically detailed, so let's briefly mention a few points here:

The TestConnection function implements the core function, that is, requests the given IP and port, and returns the result; The timeout control is realized by calling WaitOne method of IAsyncResult. AsyncWaitHandle attribute;

In StartScan method, after obtaining IP list, the whole method is asynchronous and will not block UI by generating delegate list and calling these delegates asynchronously, and the method pointed by these delegates is TestConnection function;

Using the synchronization context SynchronizationContext, you can guarantee that the caller handles the progress update event or scan completion event on the original thread (usually the UI thread);

After each delegate completes asynchronously, the callback method OnComplete is executed, in which operations on global variables need to be locked to ensure thread safety.

3. How to use

Finally, let's look at how to use it, which is very simple:


private void View_Loaded()
  {
   //  In the interface  Load  Add the following code to the event 
   ServerScanner.OnScanComplete += ServerScanner_OnScanComplete;
   ServerScanner.OnScanProgressChanged += ServerScanner_OnScanProgressChanged;
   //  Port number scanned 
   ServerScanner.ScanPort = 7890;
  }
  private void StartScan()
  {
   //  Start scanning 
   ServerScanner.StartScan();
  }

  private void ServerScanner_OnScanComplete(object sender, List<ConnectionResult> e)
  {
   ...
  }
  private void ServerScanner_OnScanProgressChanged(object sender, ScanProgressEventArgs e)
  {
   ...
  }

If you have better suggestions or opinions, please leave a message to communicate with each other.


Related articles: