1

My code gets open ports from the given host. This process is very time consuming

Code :

for(int port=0;port<11;port++)
{
    string statusTCP = "Open";
    using (TcpClient tcp = new TcpClient())
    {
         try{ tcp.Connect("127.0.0.1",port);
         }catch { statusTCP="Close";}
    }
    Console.WriteLine("Port " + port + " : " + statusTCP);
}

This process takes 11s for me ! This is very long if i check 100 or 1000 ports...

Any Good & Fast way to do this ?

4
  • 1
    Parallel.for should be using threads and asynchronous where possible Commented Feb 24, 2017 at 9:23
  • 4
    You don't need multithreading at all. You can use the asynchronous ConnectAsync method to connect asynchronously - ie using the underlying IO completion port mechanisms without blocking a thread. Commented Feb 24, 2017 at 9:28
  • You should also consider adding a timeout to the sockets. By default, it should be 1 second which is way too long when scanning localhost. To do so, you can use: tcpClient.ConnectAsync().Wait(timeout); Commented Feb 24, 2017 at 9:29
  • 1
    @Pimich Wait blocks. This would waste whatever was gained by using ConnectAsync Commented Feb 24, 2017 at 9:30

2 Answers 2

5

You don't need multiple threads to connect to multiple ports. You can use ConnectAsync to connect asynchronously. Windows uses asynchronous IO through completion ports from the NT days.

This means that no threads are blocked, the OS notifies the original thread when an IO request completes. In fact, blocking is simulated to make synchronous programming easier.

You can create a single method that connects to a single port and reports a message when finished. Writing to the console blocks though, so I'll use the IProgress< T> interface to report progress:

public async Task ConnectToPortAsync(string host,int port,IProgress<string> progress)
{
    using(var client=new TcpClient())
    {
        try
        {
            await client.ConnectAsync(host,port).ConfigureAwait(false);
            progress.Report($"Port: {port} Open");
            //Do some more work
        }
        catch 
        {
            progress.Report($"Port {port} Closed");
        }
    }
}

Assuming you have a list of ports:

var ports=new[]{80,8080,...};

or

var ports=Enumerable.Range(0,11);

You can call multiple ports simultaneously like this:

var host="127.0.0.1";
var progress=new Progress<string>(msg=>Console.WriteLine(msg););

var allTasks= ports.Select(port=>ConnectToPortAsync(host,port,progress));

await Task.WhenAll(allTasks);

This code will use the main thread up until the first await. This means that all TcpClient instances will be created using the main thread. After that though, the code will execute asynchronously, using a thread from the IO Completion thread pool whenever necessary.

Reporting is performed by the main thread by the Progress< T> object, so the background threads don't block waiting to write to the console.

Finally, once await returns, execution continues on the original synchronization context. For a desktop application (WPF, Winforms) that would be the main thread. That's OK if you want to update the UI after an asynchronous operation, but can cause blocking if you want to perform more work in the background. By using ConfigureAwait(false) we instruct the runtime to keep working on the background thread

Further improvements :

You can add a timeout in case a connection takes too long, by using Task.Delay() and Task.WhenAny :

var connectionTask=client.ConnectAsync(host,port);
var timeout=Task.Delay(1000);
var completed=await Task.WhenAny(connectionTask,timeout);
if(completed==connectionTask)
{ 
    progress.Report($"Port {port} Open");
}
else
{
    progress.Report($"Port {port} Timeout");
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you For Your Answer , but i have 2 errors with your method 1. IProgress<T> : The Type Or Namespace Name 'T' Could Not be Found 2. progress.Report(...) : Cannot Convert From String To T
@alix54 oops, forgot to use IProgress<string> there
It should be noted that there's a big difference in using a thread (do things at the same time wasting CPU) and using a Task (can avoid CPU time on things that do not use CPU time). So a Thread does things at the same time, while a Task will give a result somewhere in the future. Await only handles the delay.
Not quite. Tasks use threads. Using raw threads, requires creating them which is expensive. A Task will probably start executing faster because it will use a ThreadPool thread. The same happens if you use ThreadpoolThread.QueueWorkItem. A Threadpool can grow to thousands of threads. Besides, with IO, it's not the thread that does the blocking work. If you used the Win32 API, you'd use a function with a callback back to the original thread to perform async IO
Only Task.Run can move CPU bound work to a background thread. async and await don't create additional threads, because a async method doesn't run on its own thread. The async method runs in the current synchronization context. That means it's using time on the current thread only when the method is active. msdn.microsoft.com/en-us/library/mt674882.aspx
|
3

If what you're asking is how to run multiple lookups in parallel, take a look at the Parallel class.

Parallel.For(0, 11, new ParallelOptions { MaxDegreeOfParallelism = 5 }, (port) =>
{
    string statusTCP = "Open";
    using (TcpClient tcp = new TcpClient())
    {
        try
        {
            tcp.Connect("127.0.0.1", port);
        }
        catch { statusTCP = "Close"; }
    }
    Console.WriteLine("Port " + port + " : " + statusTCP);
});

Note in the first few method parameters where I specify to go from 0-10 (because 11 is exclusive) and execute 5 lookups in parallel with the ParallelOptions class.

9 Comments

@alix54 Parallel.For won't hang the program. The main thread is one of the 5 threads used. Did you wait for all connections to complete? Did you try debugging?
@PanagiotisKanavos I used this method in BackGroundWorker , I don't think the main method is used...
@alix54 what's the point of using BGW when you have TPL and Parallel.For? If you wanted the loop to run in the background, surround it with Task.Run. BGW is obsolete, as all of its functionality is provided by the TPL (including progress reporting).
@alix54 - BackgroundWorker and Console.WriteLine() don't make much sense together. So you asked a bad question and still got a good answer. Solve the integration issues by yourself.
@alix54 anyway, did you try pausing the debugger and checking the Parallel Stacks window? This will show you which threads are running or not
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.