3

I have a Java android code that sends data (image or text) to a C# application, to receive these data I'm using Async socket. But exists a problem that is relative to BeginReceive() function is not receiving the complete data when is sent an image.. Then how I can make a kind of "loop" to receive full data and after show the image on Picturebox (for example)?

Form

private Listener listener;
private Thread startListen;

private Bitmap _buffer;

public frmMain()
{
  InitializeComponent();
}

private void serverReceivedImage(Client client, byte[] image)
{
    try
    {
        byte[] newImage = new byte[image.Length - 6];

        Array.Copy(image, 6, newImage, 0, newImage.Length);

            using (var stream = new MemoryStream(newImage))
            {
                using (var msInner = new MemoryStream())
                {

                  stream.Seek(2, SeekOrigin.Begin);

                  using (DeflateStream z = new DeflateStream(stream, CompressionMode.Decompress))
                  {
                    z.CopyTo(msInner);
                  }

                  msInner.Seek(0, SeekOrigin.Begin);

                  var bitmap = new Bitmap(msInner);
                  Invoke(new frmMain.ImageCompleteDelegate(ImageComplete), new object[] { bitmap });
                }
            }
    }
     catch (Exception)
     {
        System.Diagnostics.Process.GetCurrentProcess().Kill();
     }
}

private delegate void ImageCompleteDelegate(Bitmap bitmap);
private void ImageComplete(Bitmap bitmap)
{
   if (_buffer != null)
       _buffer.Dispose();

       _buffer = new Bitmap(bitmap);
       pictureBox1.Size = _buffer.Size;
       pictureBox1.Invalidate();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
  if (_buffer == null) return;
      e.Graphics.DrawImage(_buffer, 0, 0);
}

private void startToolStripMenuItem_Click(object sender, EventArgs e)
{
  startListen = new Thread(listen);
  startListen.Start();
}

private void listen()
{
  listener = new Listener();
  listener.BeginListen(101);
  listener.receivedImage += new Listener.ReceivedImageEventHandler(serverReceivedImage);

  startToolStripMenuItem.Enabled = false;
}

Listener

class Listener
{

    private Socket s;
    public List<Client> clients;

    public delegate void ReceivedImageEventHandler(Client client, byte[] image);
    public event ReceivedImageEventHandler receivedImage;

    private bool listening = false;

    public Listener()
    {
        clients = new List<Client>();
        s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

    public bool Running
    {
        get { return listening; }
    }

    public void BeginListen(int port)
    {
        s.Bind(new IPEndPoint(IPAddress.Any, port));
        s.Listen(100);
        s.BeginAccept(new AsyncCallback(AcceptCallback), s);
        listening = true;
    }

    public void StopListen()
    {
        if (listening == true)
        {
            s.Close();
            listening = false;
        }
    }

    void AcceptCallback(IAsyncResult ar)
    {
        Socket handler = (Socket)ar.AsyncState;
        Socket sock = handler.EndAccept(ar);
        Client client = new Client(sock);
        clients.Add(client);

        sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);

        client.Send("REQUEST_PRINT" + Environment.NewLine); 

        handler.BeginAccept(new AsyncCallback(AcceptCallback), handler);
    }

    void ReadCallback(IAsyncResult ar)
    {

        Client client = (Client)ar.AsyncState;
        try
        {
            int rec = client.sock.EndReceive(ar);
            if (rec != 0)
            {
                string data = Encoding.UTF8.GetString(client.buffer, 0, rec);

                if (data.Contains("SCREEN"))
                { 
                    byte[] bytes = Encoding.UTF8.GetBytes(data);
                    receivedImage(client, bytes);
                }
                else // not is a image, is a text
                {
                    // prepare text to show in TextBox
                }
            }
            else
            {
                Disconnected(client);
                return;
            }

            client.sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);
        }
        catch
        {
            Disconnected(client);
            client.sock.Close();
            clients.Remove(client);
        }
    }

}

Client

class Client
{
    public Socket sock;
    public byte[] buffer = new byte[8192];

    public Client(Socket sock)
    {
        this.sock = sock;
    }

    public void Send(string data)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(data);
        sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
        {
            sock.EndSend(ar);
        }), buffer);
    }
}

Android code

private byte[] compress(byte[] data) {

    Deflater deflater = new Deflater();
    deflater.setInput(data);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
    deflater.finish();
    byte[] buffer = new byte[1024];
    while (!deflater.finished()) {
        int count = deflater.deflate(buffer);
        outputStream.write(buffer, 0, count);
    }
    outputStream.close();
    byte[] output = outputStream.toByteArray();

    return output;
}

public static DataOutputStream dos;
public static byte[] array;

ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
array = compress(bos.toByteArray());

//...

dos = new DataOutputStream(SocketBackgroundService.clientSocket.getOutputStream());
byte[] header = ("SCREEN").getBytes(StandardCharsets.UTF_8);
byte[] dataToSend = new byte[header.length + array.length];
System.arraycopy(header, 0, dataToSend, 0, header.length);
System.arraycopy(array, 0, dataToSend, header.length, array.length);
dos.writeInt(dataToSend.length);
dos.write(dataToSend, 0, dataToSend.length);

dos.flush();

EDITION

i'm always getting the error Invalid Parameter in this line

var bitmap = new Bitmap(msInner);

and using compression also happens the same here

z.CopyTo(msInner);

IvalidDataException

on ServerReceivedImage() method respectively.

using this

File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "image.png"), newImage);

i noted that is receiving only 15KB (size of file without use compression).

FLASHCODER
  • 1
  • 7
  • 24
  • You have EndReceive, but you will have synchronization and dead locks problems, read a bit here https://stackoverflow.com/questions/3504654/c-sharp-socket-beginreceive-endreceive-capturing-multiple-messages, build your system to process TCP vs UDP in a concurrent approach. – SilentTremor Jun 24 '19 at 19:07
  • With complications of writing good BeginRecieve/EndReceive code, you should consider trying to write the network stream processing parts with async/await code. – Kind Contributor Jun 28 '19 at 16:07
  • Are you using .net core or .net framework? – theCuriousOne Jul 01 '19 at 10:38
  • 1
    You cannot use encoding on binary data (images) it will corrupt the data. the following causing the issue : byte[] buffer = Encoding.ASCII.GetBytes(data); With binary data you need to proceed the data with a bytes count when transmitting and then read at receiving end the number of bytes. You should check when debugging your code the number of bytes received, and make sure it is the same as the value you started with. – jdweng Jul 02 '19 at 12:48
  • 1
    Jon Skeet's explanation of [the three standard options for sending a "message" (whether that's an image or whatever - a whole blob of data which may be large)](https://stackoverflow.com/a/9224106/11647724) will point you in the right direction. – Hans Kapitein Jul 03 '19 at 09:10
  • I think you misunderstand. You are dealing with a stream. You wrote a function called `receiveImage` but all you have really done is received _some of the bytes of the image_. Your job is not to receive the image in that function, but rather, to append the bytes you've received so far into a buffer. You'll also be looking for an end-of-message indicator according to whatever protocol you decide to use to frame your messages. Somehow, you'll need to introduce bytes that serve as boundaries between logical messages in your stream. A length header, or an escaped delimiter are popular choices. – Wyck Jul 04 '19 at 12:48
  • I have answered this so many times. [Here](https://social.msdn.microsoft.com/Forums/vstudio/en-US/1a880f94-0eba-47fc-aecd-4768486329a7/parsing-bytearray-data-in-tcp-socket-server#749a6207-c04f-4269-8f65-3609a2b658dd), and [here](https://social.msdn.microsoft.com/Forums/vstudio/en-US/9fc335df-d5f0-4c5d-9bdc-3b7a962016c2/asynchronous-server-socket#0a9fc22b-6b41-44a0-adc5-f70f584af300), with [explanation](https://social.msdn.microsoft.com/Forums/vstudio/en-US/6e15e204-c244-43da-8b55-e31c7f48129d/winsock-is-uploading-file-of-1kb-not-of-its-original-size#306cd587-61f8-4eb9-8b27-fdf5f1be22cc). – Wyck Jul 04 '19 at 12:57

1 Answers1

1

I was writing a comment but it does not give me enough space to express my frustration with your code.

My main points are

  • You try to recompress and perfectly compressed image. PNG is portable network graphics. It was designed for network transfers. If it is acceptable you should use something like jpeg.

  • You just decode received buffer using UTF8.GetString and search for a text, then re-encode that string and try to decompress and read an image from it, by starting from index 6 which is pretty meaningless considering you added a two byte size field to the start of stream and you really do not know position of "SCREEN".

  • You do not check if you have received ALL of the stream data.

  • All of the code looks like you have scoured the SO questions and answers and created a copy pasta.

Now my recommendations.

  • When transferring data from network, do not try to invent wheels. Try something like gRPC which has both android java and c# packages.

  • If you will use raw data, please, please know your bytes.

  • I assume you will extend your code by adding new command pairs. Since you have no magic markers of some kind of signal system, it will be very hard for you to distinguish data from header. For a simple implementation add some kind of magic data to your header and search for that data, then read header and then read data. You may need to read from socket again and again until you receive all of the data.

    424A72 0600 53435245454E 008E0005   .....  724A42
     B J r    6  S C R E E N    36352   .....  rJB
    

this sample data shows that we have a valid stream by looking at "BJr". Then read a 2 byte unsigned integer to read command size which is 6 for SCREEN. Read command and then read four bytes unsigned length for command data. For our sample it is 36352. Just to be safe I've added an end of command marker "rJB".

For a bonus point try reducing memory allocations / copies, you can look at System.Span<T>

Ryan McDonough
  • 9,732
  • 3
  • 55
  • 76
Erdogan Kurtur
  • 3,630
  • 21
  • 39
  • *"`considering you added a two byte size field to the start of stream and you really do not know position of "SCREEN`"* where i'm adding 2 bytes before "SCREEN"? i not saw. – FLASHCODER Jul 03 '19 at 03:13
  • dos.writeInt(dataToSend.length); – Erdogan Kurtur Jul 03 '19 at 06:28
  • then how is right? only `dos.write(dataToSend, 0, dataToSend.length);`? – FLASHCODER Jul 03 '19 at 23:08
  • 1
    It is not wrong to write length of data. It is that you just ignore when you try to read data. You ignore 6 bytes (SCREEN.length) where it should be 8 (+size). You also check if output contains "SCREEN" and you ignore position of that.You should move your pointer 6 bytes after where you have found SCREEN text. – Erdogan Kurtur Jul 04 '19 at 07:57
  • *"`where it should be 8 (+size)`"*, thank you. Will adjust. – FLASHCODER Jul 04 '19 at 12:29