0

I've implemented this C# client which communicate with asynchronous connection to another server I've created in Java. This is the client:

public class NetworkTransaction
{
    private GenericRequest request;
    private const string serverName = "192.168.1.101";
    private const int serverPort = 7777;
    private const int TIMEOUT_MILLISECONDS = 3000;
    private Delegate method;

    public NetworkTransaction(GenericRequest request, Delegate method)
    {
        this.request = request;
        this.method = method;
    }

    public void SendRequest()
    {
        SocketAsyncEventArgs connectionOperation = new SocketAsyncEventArgs();
        DnsEndPoint hostEntry = new DnsEndPoint(serverName, serverPort);
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        connectionOperation.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArg_Completed);
        connectionOperation.RemoteEndPoint = hostEntry;
        connectionOperation.UserToken = socket;

        try
        {
            socket.ConnectAsync(connectionOperation);
        }
        catch (SocketException ex)
        {
            throw new SocketException((int)ex.ErrorCode);
        }
    }

    private void SocketEventArg_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnectCompleted(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceiveCompleted(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSendCompleted(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnectCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(request.ToJson() + "\n\r");
            e.SetBuffer(buffer, 0, buffer.Length);
            Socket sock = e.UserToken as Socket;
            sock.SendAsync(e);
        }
    }

    private void ProcessSendCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            Socket sock = e.UserToken as Socket;
            sock.ReceiveAsync(e);
        }
    }

    private void ProcessReceiveCompleted(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            string dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
            Socket sock = e.UserToken as Socket;
            sock.Shutdown(SocketShutdown.Send);
            sock.Close();

            method.DynamicInvoke(dataFromServer);
        }
    }
}

What can I do to get long messages from the server? What do I need to change in order to make the client try and read more data to see if I actually read the entire message? Since there might be a problem with the line: string dataFromServer = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred); which receives only a part of the message and not the entire one.

halfer
  • 19,824
  • 17
  • 99
  • 186
CodeMonkey
  • 11,196
  • 30
  • 112
  • 203

4 Answers4

3

The answer which was given to me about creating a protocol helped me the most. What i eventually did was attaching a "header" to the message stating it's size. The client then parse the header and just checks if there is still more data to be sent according to message size header and the actual number of bytes received. If there is still more data left to be read then i would just do:

if (messageSizeRemaining > 0)
{
      sock.ReceiveAsync(e);
      return;
}
CodeMonkey
  • 11,196
  • 30
  • 112
  • 203
1

What do i need to change in order to make the client try and read more data to see if i actually read the entire message?

You have to implement a protocol that says "Here come 50 bytes" or "This was all data, goodbye".

ProcessReceiveCompleted() will be called whenever a piece of data is received, this can be anything between one and a lot of bytes. Sockets do not guarantee a "message" to be sent as a whole, since messages don't exist at that level. The amount of data received is dependant on factors like CPU and network utilization.

That's where your protocol comes in. This has to be defined in both server and client. HTTP, for example, uses two CRLF's to let the client say "This was my request, please respond now". The server, on the other hand, (by default) closes the connection when all data is sent, but also sends a Content-length header, so the client can verify it has received all data.

So you'll have to store the data in ProcessReceiveCompleted() and start processing when you have received a whole message.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • That's sounds logical and i can do it with sending 2 messages which the first one will be the size of the incoming message and the second will be the message itself. Is it possible if you could confirm my thoughts of where to change the code? I want thinking that the ProcessConnectCompleted should be changed to get a different size of a buffer. But let's say that I'd do that, then how can i read the second message? For now I only know how to read the first arriving message – CodeMonkey Jun 13 '12 at 12:38
  • @YonatanNir no, you can _not_ do that, because you have no "message" defined and you cannot influence the amount of data you receive: this is determined by the network stack. So if you call `send("5bytes")` and then `send("Hello")`, there is no guarantee you'll receive it as two separate events. It might be received as ("5bytesHello") or ("5b", "ytesH" and "ello"). You have to store everything you receive and parse it according to the protocol you've defined. You could say "XXXbytes" is your header that defines the amount of data to expect, then check for the text "bytes" and check the number. – CodeCaster Jun 13 '12 at 12:46
  • Are you familiar with the SocketAsyncEventArgs class and maybe know of a way to check if it still has stuff to receive? (even without the knowledge of how much more is there to be send). I mean something like while(There is more data) buffer += e.getBuffer() – CodeMonkey Jun 13 '12 at 13:23
  • @YonatanNir as I've been trying to explain, sockets do not provide that functionality; you'll have do describe a set of rules (i.e. a protocol) that determine how your data is sent. You'll have to do something like `buffer += e.GetBuffer();` and after every receive you call `ProcessBuffer()`, where you check the buffer to see if there is data that is meaningful to you. – CodeCaster Jun 13 '12 at 13:26
  • @YonatanNir but why aren't you using a webservice? Both Java and .NET provide great frameworks that let you actually exchange **messages** without having to bother with the underlying stuff you seem to struggle with. – CodeCaster Jun 13 '12 at 13:29
  • This client was made for a wp7 app and as far as I've seen the communication is made there this way – CodeMonkey Jun 13 '12 at 14:48
  • I think i solved it and your answer helped me the most (I also answered my own question if you want to see it) – CodeMonkey Jun 13 '12 at 15:58
  • @YonatanNir nice. :-) Happy to help. – CodeCaster Jun 13 '12 at 21:19
0

If you use web services, you could use a Duplex Channel. Where the client registers for a data stream at the server using something like:

[OperationContract(IsOneWay = true)]
void RequestData();

And the server would send the data back calling an OneWay operation to the client, like:

[OperationContract(IsOneWay = true)]
void SendNewData(byte[] data, bool completed)

The client would combine all the data together and when receiving the completed flag it would unregister from the server.

To read more about DuplexChannels look here: Duplex Services.

Guilherme Duarte
  • 3,371
  • 1
  • 28
  • 35
  • Is there a way to do it in Java? The code i wrote here is of the client but the server itself is written in Java – CodeMonkey Jun 13 '12 at 12:40
  • @YonatanNir: never did it but, I'm sure it should be possible: http://developerzone.genesyslab.com/repository/PSDK/8.0-Java_API_Reference/com/genesyslab/platform/commons/protocol/DuplexChannel.html – Guilherme Duarte Jun 13 '12 at 12:42
0

I've used the following methods synchronously (I know you want async) to acquire data from HTTP

    public static Stream getDataFromHttp(string strUrl)
    {
        Stream strmOutput = null;
        try
        {
            HttpWebRequest request;
            Uri targetUri = new Uri(strUrl);
            request = (HttpWebRequest)HttpWebRequest.Create(targetUri);
            request.Timeout = 5000;
            request.ReadWriteTimeout = 20000;
            request.Method = "Get";


            request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)";
            if (request != null)
            {
                request.GetResponse();
                if (request.HaveResponse)
                {
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                    strmOutput = response.GetResponseStream();
                }
            }

        }
        catch (WebException wex)
        {

        }
        catch (Exception ex)
        {

        }
        return strmOutput;
    }

Now to convert the stream to text use the following:

    Public Static String StreamToString(Stream stream)
    {
            StreamReader SR = new StreamReader(stream);
            try
            {
               String strOutput = SR.ReadToEnd();
               Return strOutput;
             }
             catch(Exception ex)
             {
                  return ex.message
             }
   }

Hope this works

Feuerwehrmann
  • 151
  • 12