views:

434

answers:

3

Can someone please explain me why the following code doesn't work?

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace SocketThreadingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(delegate()
            {
                BeginConnect(new IPEndPoint("some address"));
            });
            t.Start();

            Console.ReadKey();
        }

        public static void BeginConnect(IPEndPoint address)
        {
            try
            {
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.BeginConnect(address, ConnectCallback, socket);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private static void ConnectCallback(IAsyncResult ar)
        {
            Socket sock = (Socket)ar.AsyncState;
            try
            {
                sock.EndConnect(ar);
                Console.WriteLine("Connected {0}", sock.LocalEndPoint);

                sock.Send(Encoding.UTF8.GetBytes("Hello"));

                Console.WriteLine("success");
                sock.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("send ex " + ex);

                if (sock != null)
                    sock.Close();
            }
        }
    }
}

The output is (note the local end point of the socket):

Connected 0.0.0.0:28142
send ex System.Net.Sockets.SocketException: A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram
 socket using a sendto call) no address was supplied
   at System.Net.Sockets.Socket.Send(Byte[] buffer, Int32 offset, Int32 size, So
cketFlags socketFlags)
   at System.Net.Sockets.Socket.Send(Byte[] buffer)
   at SocketThreadingTest.Program.ConnectCallback(IAsyncResult ar) in Program.cs:line 44

Of course when I don't use a thread and call BeginConnect directly it works fine. What's even more puzzling is that adding a Thread.Sleep that is long enough (1 sec) it also works fine. Any ideas? Thanks.

A: 

Your IPEndPoint should contain a port -- I'm not even sure how your EndPoint will compile, as it's required. You can supply the port as the second parameter to your IPEndAddress or modify your BeginConnect method as follows:

 socket.BeginConnect(address, [port], ConnectCallback, socket); 

...where [port] represents the listening port on the Server.

George
don't be silly George... of course the code as is doesn't compile. what did you expect me to paste here a real server address?! I thought that writing "some address" is a strong enough hint that this is a place you need to fill before running this code.
Zvika
You thought wrong! ;)
George
+1  A: 

Which makes sense to use separate Thread and BeginConnect? If you creating separate thread (with Thread pool preferably) why are you using asynchronous connection (in this case separate thread will be taken from the thread pool)?

There are several options: Use ThreadPool and Socket.Connect

class Program {

    static void Connect(object o)
    {
        IPEndPoint address = (IPEndPoint)o;
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Connect(address);
        Console.WriteLine("Connected {0}", socket.LocalEndPoint);
        socket.Send(Encoding.UTF8.GetBytes("Hello"));
        Console.WriteLine("success");
        socket.Close();
    }

    static void Main(string[] args)
    {
        IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 5111);
        ThreadPool.QueueUserWorkItem(Connect, endPoint);
        Console.ReadKey();
    }
}

Use BeginConnect without separate thread.

class Program {

static void Main(string[] args)
{
    BeginConnect(new IPEndPoint(IPAddress.Loopback, 5111));
    Console.ReadKey();
}

public static void BeginConnect(IPEndPoint address)
{
    try
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.BeginConnect(address, ConnectCallback, socket);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

private static void ConnectCallback(IAsyncResult ar)
{
    Socket sock = (Socket)ar.AsyncState;
    try
    {
        sock.EndConnect(ar);
        Console.WriteLine("Connected {0}", sock.LocalEndPoint);
        sock.Send(Encoding.UTF8.GetBytes("Hello"));
        Console.WriteLine("success");
        sock.Close();
    }
    catch (Exception ex)
    {
        Console.WriteLine("send ex " + ex);
        if (sock != null)
            sock.Close();
    }
}
}

Use BeginConnect with separate thread:

class Program
{

    static void Main(string[] args)
    {
        Thread t = new Thread(delegate()
        {
            BeginConnect(new IPEndPoint(IPAddress.Loopback, 5111));
        });
        t.Start();
        Console.ReadKey();
    }

    public static void BeginConnect(IPEndPoint address)
    {
        try
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.BeginConnect(address, ConnectCallback, socket);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    private static void ConnectCallback(IAsyncResult ar)
    {
        Socket sock = (Socket)ar.AsyncState;
        try
        {
            sock.EndConnect(ar);
            Console.WriteLine("Connected {0}", sock.LocalEndPoint);
            sock.Send(Encoding.UTF8.GetBytes("Hello"));
            Console.WriteLine("success");
            sock.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine("send ex " + ex);
            if (sock != null)
                sock.Close();
        }
    }
}
Sergey Teplyakov
I am aware of these options but BeginConnect is being called from a separate Thread. This is the design of the application. I'm asking why in this situation I get an error.
Zvika
My second example works well... You can change this code to use Thread class instead.
Sergey Teplyakov
I changed my example to use Thread class. And it works fine. I think that the problem is not in the Thread class at all.
Sergey Teplyakov
It's a timing issue. Your code is exactly as mine and we get different results. the problem is that on my machine the thread exists before the operation completes. see my discussion with nos above.
Zvika
OK. I reproduced this behavior on my own machine (now, I'm trying to connect to external host)... and trying to understand why this code doesn't work. Can you use Connect method instead?
Sergey Teplyakov
Connect can block for a long time (30 sec on windows by default) and I can't block the thread for such a long time.
Zvika
I've noticed, that code works well with ThreadPool instead code with separate thread (with remote host, not local).
Sergey Teplyakov
In this case you block newly created thread or thread from thread pool...
Sergey Teplyakov
solution: IAsyncResult result = socket.BeginConnect(address, ConnectCallback, socket); result.AsyncWaitHandle.WaitOne();
Sergey Teplyakov
I've solved it by wrapping the BeginConnect with a ThreadPool.QueueUserWorkItem. ugly, but works. thanks for the help.
Zvika
But I think you also would add result.AsyncWaitHandle.WaitOne(), because thread from thread pool also can be utilize, and in that case you can face very subtle bug! May I change my post to reflect solution there? Or you can reflect it in your own post!
Sergey Teplyakov
I don't think you need to add WaitOne or else every call to Socket.BeginConnect on a thread pool thread will need to also to wait which defies the purpose of this async method. And anyways a thread from the thread pool that just called BeginConnect will take some time to be removed from the pool (enough time to let the connect finish), so I don't worry about a bug there.
Zvika
hmm.. But if connect would last for 30 sec? This can be enough to remove thread from the pool.
Sergey Teplyakov
maybe, but if in 30 sec it can't connect - i say let it fail. I'll take my chances :)
Zvika
A: 

is it possible that because you are not waiting for the initial thread, the OS is cancelling the I/O request? Windows will cancel the I/O request if the original thread that started the async I/O dies.

In this case, you are calling BeginConnect from a thread, and letting the thread die, so the I/O is getting cancelled. Now, there might be some situation where the I/O might not get cancelled if the thread you started did not actually die by the time you called Send() on the socket.

If you really want this to work, you could try the following variation:

       static void Main(string[] args) 
    { 
        Thread t = new Thread(delegate() 
        { 
            IAsyncResult ar = BeginConnect(new IPEndPoint("some address")); 
            // wait for the async connect to finish.
            ar.WaitOne();
        }); 
        t.Start(); 

        Console.ReadKey(); 
    } 

    public static IAsyncResult BeginConnect(IPEndPoint address) 
    { 
        try 
        { 
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
            return socket.BeginConnect(address, ConnectCallback, socket); 
        } 
        catch (Exception ex) 
        { 
            Console.WriteLine(ex); 
        } 
        return null;
    } 
feroze