0

I want to implement a communication between 2 servers. On the client side: one thread prepares the data and enqueues it. The second thread dequeues data, serializes it and sends to the server. On the server side: receives the data (bytes) and enqueues it. The further process of the enqueued data is irrelevant.

For test purposes only:

  1. I run the client and the server on the same machine.
  2. I imitate the queue by initiating records with a string of 70 bytes length (the object I want to send is about 70 bytes). See the Client->StartClient code below.
  3. I send the data iteratively. The data will be queued very fast, so I want to send it as soon as it gets to the queue. I imitate it by sending it iteratively.

I used WCF for both client and server to send the data, but it took over 5min to send 70MB(1 million records of 70bytes each). Then I decided to use Socket. I used asynchronous approach and it still took a few minutes to send 70MB. In my understanding, having client and server on the same machine, it should NOT take a few minutes to send 70MB. It should take a few seconds max, right? How I can speed it up?

P.S. I know that you can say "accumulate and then send". But, I want to be able to send data as soon as it gets to the queue.

Client:

public class StateObject
{
    // Client socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 256;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();
}

public class AsynchronousClient
{
    // The port number for the remote device.
    private const int port = 11000;

    // ManualResetEvent instances signal completion.
    private static ManualResetEvent connectDone =
        new ManualResetEvent(false);
    private static ManualResetEvent sendDone =
        new ManualResetEvent(false);
    private static ManualResetEvent receiveDone =
        new ManualResetEvent(false);

    // The response from the remote device.
    private static String response = String.Empty;

    private static void StartClient()
    {
        // Connect to a remote device.
        try
        {                
            IPHostEntry ipHostInfo = Dns.Resolve("MY_HOST");
            IPAddress ipAddress = ipHostInfo.AddressList[0];
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);

            // Create a TCP/IP socket.
            Socket client = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            // Connect to the remote endpoint.
            client.BeginConnect(remoteEP,
                new AsyncCallback(ConnectCallback), client);
            connectDone.WaitOne();

//============================================================
            var records = new List<string>();                
            for (int i = 0; i < 1000000; i++)
            {
                records.Add(new string('a', 70));
            }

            var serializer = new BinaryFormatter();
            var stopWatch = new Stopwatch();
            stopWatch.Start();                
            foreach (var rec in records)
            {
                using (var ms = new MemoryStream())
                {
                    serializer.Serialize(ms, rec);
                    var serData = ms.ToArray();

                    // Send test data to the remote device.
                    Send(client, serData);                                                  
                }
            }
            stopWatch.Stop();    
//================================================================                                      
            // Release the socket.
            client.Shutdown(SocketShutdown.Both);
            client.Close();

        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private static void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            // Complete the connection.
            client.EndConnect(ar);

            Console.WriteLine("Socket connected to {0}",
                client.RemoteEndPoint.ToString());

            // Signal that the connection has been made.
            connectDone.Set();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    private static void Send(Socket client, byte[] data)
    {
        // Convert the string data to byte data using ASCII encoding.
        byte[] byteData = data;// Encoding.ASCII.GetBytes(data);

        // Begin sending the data to the remote device.
        client.BeginSend(byteData, 0, byteData.Length, 0,
            new AsyncCallback(SendCallback), client);
    }

    private static void SendCallback(IAsyncResult ar)
    {
        try
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            // Complete sending the data to the remote device.
            int bytesSent = client.EndSend(ar);
            Console.WriteLine("Sent {0} bytes to server.", bytesSent);                   
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    public static int Main(String[] args)
    {
        StartClient();
        return 0;
    }
}

Server:

public class StateObject
{
    // Client  socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 1024;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();
}

public class AsynchronousSocketListener
{
    // Thread signal.
    public static ManualResetEvent allDone = new ManualResetEvent(false);

    public AsynchronousSocketListener()
    {
    }

    public static void StartListening()
    {
        // Data buffer for incoming data.
        byte[] bytes = new Byte[1024];

        IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
        IPAddress ipAddress = ipHostInfo.AddressList[0];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

        // Create a TCP/IP socket.
        Socket listener = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream, ProtocolType.Tcp);

        // Bind the socket to the local endpoint and listen for incoming connections.
        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(100);

            while (true)
            {
                // Set the event to nonsignaled state.
                allDone.Reset();

                // Start an asynchronous socket to listen for connections.
                Console.WriteLine("Waiting for a connection...");
                listener.BeginAccept(
                    new AsyncCallback(AcceptCallback),
                    listener);

                // Wait until a connection is made before continuing.
                allDone.WaitOne();
            }

        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }

        Console.WriteLine("\nPress ENTER to continue...");
        Console.Read();

    }

    public static void AcceptCallback(IAsyncResult ar)
    {
        // Signal the main thread to continue.
        allDone.Set();

        // Get the socket that handles the client request.
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReadCallback), state);
    }

    public static void ReadCallback(IAsyncResult ar)
    {
        String content = String.Empty;

        // Retrieve the state object and the handler socket
        // from the asynchronous state object.
        StateObject state = (StateObject)ar.AsyncState;
        Socket handler = state.workSocket;

        // Read data from the client socket. 
        int bytesRead = handler.EndReceive(ar);
        Console.WriteLine("Read {0} bytes from socket", bytesRead);

        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
               new AsyncCallback(ReadCallback), state);
    }                

    public static int Main(String[] args)
    {
        StartListening();
        return 0;
    }
}

sdf

theateist
  • 13,879
  • 17
  • 69
  • 109
  • 1
    Have you tried using a named pipe? – Yosef O Aug 27 '15 at 05:43
  • Are you looking for speed in terms of "lowest time between enqueuing message and receiving it" or "highest throughput over a fixed timespan"? Have you tried disabling Nagle algorithm (`socket.NoDelay = true;`) or modifying any receive and send buffer sizes? – sisve Aug 27 '15 at 06:00
  • If your metric is on CPU usage then I would suggest working with as few protocols as possible, OR you could use a TCP Offload Engine to reduce the CPU load when doing your network call. – Aron Aug 27 '15 at 06:19
  • @Yosef, named pipes are used when the processes on the same machine. Eventually, my client and server would be in different machines. Meanwhile I run both of them on single machines for test purposes only. – theateist Aug 27 '15 at 13:50
  • @SimonSvensson, in general, are you agree that sending million objects of 70bytes each, should NOT take a few minutes when running client and server on the same machine? – theateist Aug 27 '15 at 13:53
  • 1 000 000 objects/packets (70 000 000 bytes + tcp overhead) in one minute is ... 16 667 packets per second. Can you handle incoming requests that fast? (And on a side note, what's the speed without console output? Writing will slow down your loop.) – sisve Aug 27 '15 at 17:42
  • Also, if you are using a Windows Client OS, you may have issues with IIS limiting the number of concurrent connections. – Aron Aug 28 '15 at 01:01

3 Answers3

1

You've effectively cancelled out the benefit of the async pattern by blocking further progress with

sendDone.WaitOne();

In other words: You've managed to implement blocking socket IO with asynchronous calls: You do use async sending of data, but you're also waiting for it to be done without doing something else, so in effect, you might as well use blocking calls.

Instead of using the sendDone event to control the flow, you should determine in the EndSend() callback if there's more to be send, and start a new BeginSend().

Whether the blocking by the sendDone event is responsible for the full time consumed by the operation is impossible to say, but it sure doesn't help.

Willem van Rumpt
  • 6,490
  • 2
  • 32
  • 44
  • `sendDone.WaitOne();` shouldn't be there. I forgot to remove it. I did my tests without it. I'll edit my post. In general, do you agree that it should **NOT** take few minutes to send million records **iteratively** of 70bytes each when client and server on the same machine? – theateist Aug 27 '15 at 15:05
  • @theateist: Does it matter if I agree or not? I don't, btw, and it shouldn't. You might want to start by removing the "Console.Writeline()" statement while you're testing performance. Only that made the transfer drop from [too long, didn't wait to finish] to 18 seconds. – Willem van Rumpt Aug 27 '15 at 15:40
  • @theateist: And btw: It's not 70 million bytes, it's 94 million bytes. – Willem van Rumpt Aug 27 '15 at 15:41
  • 1
    @theateist: And while you may not like it: Doubling the buffer size (i.e.: A string size of 140 characters, and 500000 items) drops that 18 seconds to 9 seconds, doubling again drops it to 5 seconds (after I changed your sample program to correctly implement the async pattern for sockets, for which, as an aside, kudos. It's rare that something almost immediately usable gets posted). – Willem van Rumpt Aug 27 '15 at 16:22
  • 1
    @theateist: Last thing, before it's dinner time: I'm running in debug mode, and forgot to remove the "Console.WriteLine()" from the server part. Removing that, with a "buffersize" of 3000, and 30000 iterations (~90 Mb) makes it in around 1.8 seconds. – Willem van Rumpt Aug 27 '15 at 16:52
0

A few questions:

  • Have you tried without Begin/End functions? I don't understand the use of the lock in the client send loop. Also, There is a slight overhead in using Begin/End methods in comparison to Async methods (the creation of a IAsyncResult object every call instead of recycling it) but I doubt this is a real issue.
  • Have you timed the serialization of the message on the client side? I'm sure serializing once is fast, but perhaps looping 70MB of serialization is pressuring the GC. Try serializing once and sending the same block (which is essentially what you are doing).

Strategy:

  • You need to find the bottle neck, why not use the VS profiler? it does a decent job.
  • If you suspect the problem is the socket speed, try using a named pipe or shared memory.
  • If you suspect the problem is with locking, try without locking.
  • If you suspect the problem is with async methods, try synch methods.

Good luck!

Yosef O
  • 367
  • 2
  • 7
  • I have tried. Serialization of million objects iteratively without sending takes 15sec. So, I'm pretty sure that the issue is in send/receive. I've also tried w/o Begin/End and it is also slow. In general, are you agree that sending million records **iteratively** when client and server on the same machine should **NOT** take few minutes? – theateist Aug 27 '15 at 14:08
  • The real question is why this is taking so long... As I suggested, please make an effort to find the elements where the machine is crunching or idling. I understand your frustration and agree that this feels very slow. Have you tried profiling your code and monitoring the runtime? It's fairly simple to perform and gives a lot of information directly related to your issues. – Yosef O Aug 29 '15 at 10:47
0

WCF by default will use what is called "Buffered mode". This means that every byte needs to be sent before you start processing. This has two major implications.

  1. It takes at least the time for the last byte to transfer + the time to process the data for the request to complete.
  2. .net needs to dynamically allocate 70MB of buffer, but it does not know the size of the buffer at the start. As a result it actually allocates about 150MB of memory and copies the data back and forth lots of times.

The simplest way to fix this.

In your binding set your binding to use Stream mode. Then in your WCF contract replace all your method parameters with a single Stream parameter.

Aron
  • 15,464
  • 3
  • 31
  • 64
  • in general, are you agree that sending million objects*iteratively* of 70bytes each, should NOT take a few minutes when running client and server on the same machine? – theateist Aug 27 '15 at 14:03
  • @theateist Depends on the machine. I assume it is a Client OS Windows, since it is your debugging on it. In which case IIS will only allow you to open 3 concurrent HTTP connections. Given that is the case, you are trying to open a million connections, with the overhead. Assume an estimated 100ms to open and close connection (plus the overhead due to the fact that you have saturated the HTTP connection queue), yeah...a few minutes is pretty much right. Try your solution on a Windows Server box, it will be "faster", but not fast enough, since 1 million concurrent connection is a difficult beast. – Aron Aug 28 '15 at 01:08