Sergey
Sergey

Reputation: 301

Problem realizing FTP-client with asynchronous socket in C#

(excuse me for my bad english))

I am writing a class for simple requests to FTP-server using .NET-class System.Net.Sockets.Socket.Synchronous functions (Socket.Connect, Socket.Send, Socket.Receive) works fine. But I need asynchronous operation to be able to interrupt the process from the outside.

I used an example from MSDN: http://msdn.microsoft.com/en-us/library/bew39x2a%28VS.80%29.aspx Asynchronous connection with Socket.BeginConnect/EndConnect methods goes well. Immediately after connect I received a response string from server using methods Socket.BeginReceive/EndReceive (see code below).

Next, I need to send a user name in the form of FTP-command "USER anonymous". Asynchronous sending of this string using the methods Socket.BeginSend/EndSend goes well. FTP-server accepts the request and, as it should be, send me back string "331 User name ok, need password". (for debugging I am using a local FTP-server, from which logs I can see that answer was send) But receiving server response using methods Socket.BeginReceive/EndReceive for some reason fails.

I call Socket.BeginReceive method, but the appropriate callback-function is not called and the program "hangs" waiting for an answer. Here are pieces of code I use to receive server response (_ftp - object of my private class FtpState for storing data in asynchronous operations):


...
// Begin receiving data
IAsyncResult ar = (IAsyncResult)_ftp.socket.BeginReceive(
    _ftp.buffer, 0, FtpState.BUFFER_SIZE, 0,
    new AsyncCallback(ReceiveCallback), _ftp);
// Waiting one of the following events:
// - receiving complete
// - external abort event
WaitHandle[] events = new WaitHandle[] { abortEvent, ar.AsyncWaitHandle };
i = WaitHandle.WaitAny(events);
...
// Callback-function on data receive
private static void ReceiveCallback(IAsyncResult ar)
{
    FtpState ftp = (FtpState)ar.AsyncState;
    try
    {
        int size = ftp.socket.EndReceive(ar);
        if (size > 0)
        {
            ftp.sb.Append(Encoding.ASCII.GetString(ftp.buffer, 0, size));
            ftp.socket.BeginReceive(ftp.buffer, 0, FtpState.BUFFER_SIZE, 0,
                new AsyncCallback(ReceiveCallback), ftp);
        }
        else
        {
            ... this code branch never executes at all ... 
            // In MSDN example ManualResetEvent is used to signal
            // the end of data receiving:
            // receiveDone.Set();
            // Instead i am using the event of synchronization object:
            // ar.AsyncWaitHandle (see code above)
            // Actually, i tried MSDN version too, but problem remains
            // as callback-function never calls.
         }
    }
    catch
    {
        ...
    }
}
...

Again, the code above works fine to get answer immediately after connect. The problem of receiving answer appears after sending any FTP-command to server. I tried to combine sync/async methods: connect/send using asynchronous methods and receive using synchronous method - everything works fine.

I also tried to use class System.Net.Sockets.TcpClient (extension over Socket class), but a similar problem with asynchronous receiving remains. Searching the web did not help me.

Maybe somebody encountered similar problems? I would be grateful for any advice!

PS: using FtpWebRequest does not suit me.

Upvotes: 2

Views: 2899

Answers (2)

David Montgomery
David Montgomery

Reputation: 1648

Yes, I'm answering a really old post, but I was having the same problem using the MSDN sample code.

Sergey's original code:

if (size > 0)
        {
            ftp.sb.Append(Encoding.ASCII.GetString(ftp.buffer, 0, size));
            ftp.socket.BeginReceive(ftp.buffer, 0, FtpState.BUFFER_SIZE, 0,
                new AsyncCallback(ReceiveCallback), ftp);
        }

should be changed to:

if (size > 0)
        {
            ftp.sb.Append(Encoding.ASCII.GetString(ftp.buffer, 0, size));
            // Check Available property before passing off control
            if (ftp.socket.Available > 0) 
              ftp.socket.BeginReceive(ftp.buffer, 0, FtpState.BUFFER_SIZE, 0,
                new AsyncCallback(ReceiveCallback), ftp);
            else
              {
                // send the finished result somewhere
                myResult = ftp.sb.ToString();
                // tell your ManualResetEvent you are finished receiving
                receiveDone.Set();
               }
        }

Why, you ask? Well, as Sergey pointed out the ReceiveCallback wasn't being called after the server sent its final data -- if there is nothing left to receive the callback will NEVER fire!

This was a painful one to discover...

Upvotes: 2

Sergey
Sergey

Reputation: 301

It seems i have solved the problem.
I don't know how it should be in other cases, but when sending commands to
FTP-server and receiving answer you should not do as written in MSDN example.
Namely - callback-function does not need to initiate a new client.BeginReceive.
FTP-server answer on command is a string (or several, separated by "\r\n"),
which comes all at once, at a time (at least in my case).
Therefore, my callback-function now looks like this:


        private static void ReceiveCallback(IAsyncResult ar)
        {
            FtpState ftp = (FtpState)ar.AsyncState;
            try
            {
                int size = ftp.socket.EndReceive(ar);
                if (size > 0)
                {
                    ftp.reply = Encoding.ASCII.GetString(ftp.buffer, 0, size);
                }
            }
            catch (SocketException ex)
            {
                // ...
            }
            catch (Exception ex)
            {
                // ...
            }
        }
I.e. i simply convert received data to a string thinking that reading answer is complete.

Upvotes: -1

Related Questions