Android Tutorial FileTransfer - Send and receive files with AsyncStreams

Status
Not open for further replies.
Newer example based on B4XSerializator: https://www.b4x.com/android/forum/threads/72149
AsyncStream v1.50 (part of RandomAccessFile library) includes support for sending and receiving streams of any size.

Unlike the regular write methods that send an array of bytes there is a new WriteStream method that takes an InputStream and sends it to the other size. This method is only supported when AsyncStreams is initialized in prefix mode.

WriteStream can efficiently handle streams of any sizes. In the receiving side the NewStream event is raised after a complete stream is received.

Note that the data is checked for errors automatically using Adler-32 algorithm. The Error event is raised if the checksums do not match.

While a file is received you can check StreamTotal and StreamReceived properties to track the progress.

The received stream is saved in a temporary file. NewStream event includes the file folder and name. The folder can be changed by setting the StreamFolder field. The file name is an arbitrary string. It is recommended to do whatever needs to be done with the file and then delete it. The files will not be deleted automatically and will not be overwritten.

FileTransfer example demonstrates how AsyncStreams can be used to send and receive files of any size. This example allows you to connect two devices over Bluetooth or Wifi (assuming that both are connected to the same local network).

Once connected you can choose files to send.

SS-2013-06-24_13.24.55.png


The code includes an activity and service. All the communication is managed in the service. The activity is responsible for the UI and it also manages the process of finding unpaired Bluetooth devices.

As mentioned above the progress of received files is monitored with a timer that checks the StreamTotal and StreamReceived properties. In order to monitor the progress of the file being sent we use a CountingInputStream. This is a stream that wraps another stream and counts how many bytes were read.


C# implementation of FileTransfer: http://www.basic4ppc.com/forum/basi...ment-asyncstreams-prefix-mode.html#post178604

Edit: example removed as it was outdated.
 
Last edited:

tdocs2

Well-Known Member
Licensed User
The code in the link of Post #59 in this thread (courtesy of Erel) works perfectly.

I changed the FileTransfer to display the IP address of the WiFi connection and show who initiated the connection - Client, initiator and Host.

Code follows:

Client Side
B4X:
Public Sub ConnectWifi(IP As String)
    'added by me to change display label as to who I am connecting"
    HostIP=IP 'added HostIP to Process Globals
    socket1.Initialize("socket1")
    socket1.Connect(IP, port, 30000)
    WifiStatus = "Trying to connect..."
    UpdateUI
End Sub

Private Sub socket1_Connected (Successful AsBoolean)
'client connected to server
If Successful Then
WifiConnected = True
StartAStream(socket1.InputStream, socket1.OutputStream)
WifiStatus = "Client - Connected to " & HostIP
Else
WifiStatus = "Error: " & LastException
EndIf
UpdateUI
End Sub

Server Side
B4X:
Private Sub server_NewConnection (Successful As Boolean, NewSocket As Socket)
    'server accepted client
    If Successful Then
      'this code just added to get IP address of the sender
        Dim r As Reflector
        r.Target = NewSocket
        r.Target = r.GetField("socket")
        r.Target = r.RunMethod("getInetAddress") 'InetAddress
        Dim senderip As String = r.RunMethod("getHostAddress") 'it gets sender or client IP
      'end this code just added to get IP address of the sender
        WifiConnected = True
        StartAStream(NewSocket.InputStream, NewSocket.OutputStream)
        WifiStatus = "Host - Connected with " & senderip
    Else
        WifiStatus = "Error: " & LastException
    End If
    UpdateUI
    server.Listen
End Sub

It seems a bit paradoxical that r.RunMethod("getHostAddress") actually gets sender or client IP, but it does.

Thanks again, Erel.

Sandy
 
Last edited:

tdocs2

Well-Known Member
Licensed User
Greetings, all.

Thank you for your patience and generosity in answering my questions....
upload_2014-10-20_0-34-36.png


I repeat that FileTransfer stretches my tech skills - no experience in Network or AsyncStreams, but I will keep chipping at it...

1. Three tablets A, B, and C. A is the "server". B and C are the clients.
2. Run File Transfer on all 3 tablets.
3. Connect B to A. Success.
4. Connect C to A.
5. B is disconnected. Great!

Where in the FileTransfer Service does step 5 take place?
- In tablet A, Sub server_NewConnection.
- In tablet B, where and what does tablet A tell tablet B for tablet B to know it has been or is disconnected?

Best regards.

Sandy
 

tdocs2

Well-Known Member
Licensed User
Thank you, Erel.

I may not want to close it if ReceivingFile = True.

How do I reject the new connection with something like "busy... Try again."?

Best regards.

Sandy
 

tdocs2

Well-Known Member
Licensed User
You can immediately close the new socket.

Thank you again, Erel

Included Close newsocket in server_NewConnection (see code below).

Tested as follows:
1. 3 tablets, A, B, C. A is "server"
2. Connected B to A. Success
3. Connected C to A. B disconnected automatically. Success
4. Connected B to A. C disconnected automatically. Success
5. Sent file from B to A. Success
6. Connected C to A. B disconnected automatically. Success
7. Sent large file from C to A. File started to send.
9. Connected B to A while C was transmitting to A.
10. ERROR in A and activity terminated.

server_NewConnection code and Error log from Tablet A (server) follows:

server_NewConnection code
B4X:
Private Sub server_NewConnection (Successful As Boolean, NewSocket As Socket)
'added by me to prevent disconnect on ongoing file transfer
    Log("server_NewConnection")
    If ReceivingFile = True Then
        NewSocket.Close
    Log("ReceivingFile "& ReceivingFile)
        'Return - if Return is left - both B and C stay connected
    End If 

    'server accepted client
    If Successful Then
      'this code just added to get IP address of the sender
        Dim r As Reflector
        r.Target = NewSocket
           r.Target = r.GetField("socket")
           r.Target = r.RunMethod("getInetAddress") 'InetAddress
           Dim senderip As String = r.RunMethod("getHostAddress") 'it gets sender or client IP
      'end this code just added to get IP address of the sender
        WifiConnected = True
        StartAStream(NewSocket.InputStream, NewSocket.OutputStream)
        WifiStatus = "Host - Connected with " & senderip
    Else
        WifiStatus = "Error: " & LastException
    End If
    UpdateUI
    server.Listen
End Sub

Error log from Tablet A (server)
B4X:
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Service (filetranser) Create **
** Service (filetranser) Start **
server_NewConnection
StartAStream
ReceivingFile false
server_NewConnection
StartAStream
ReceivingFile false
1 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*7
2 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*7
3 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*book1.xls
server_NewConnection
StartAStream
ReceivingFile false
1 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*7
2 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*7
3 /storage/emulated/0/Android/data/b4a.example.filetransfer/files*1.txt
server_NewConnection
StartAStream
ReceivingFile false
server_NewConnection
ReceivingFile true


java.lang.NullPointerException
    at anywheresoftware.b4a.agraham.reflection.Reflection.runmethod(Reflection.java:205)
    at anywheresoftware.b4a.agraham.reflection.Reflection.RunMethod(Reflection.java:802)
    at b4a.example.filetransfer.filetranser._server_newconnection(filetranser.java:372)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
    at anywheresoftware.b4a.BA$3.run(BA.java:320)
    at android.os.Handler.handleCallback(Handler.java:725)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:5039)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
    at dalvik.system.NativeStart.main(Native Method)
java.lang.RuntimeException: java.lang.NullPointerException


    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:201)
    at anywheresoftware.b4a.BA$3.run(BA.java:320)
    at android.os.Handler.handleCallback(Handler.java:725)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:5039)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
    at anywheresoftware.b4a.agraham.reflection.Reflection.runmethod(Reflection.java:205)
    at anywheresoftware.b4a.agraham.reflection.Reflection.RunMethod(Reflection.java:802)
    at b4a.example.filetransfer.filetranser._server_newconnection(filetranser.java:372)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
    ... 10 more

Any help or guidance truly welcomed.

Sandy
 

tdocs2

Well-Known Member
Licensed User
Shalom, Erel.

From Post 62 from me, I will keep chipping at it...

I changed the server_NewConnection Sub (see below), and it seems to work with 3 tablets (see Test steps from post # 66). I do not want to dilute myself in thinking that this is the solution, but if you could give it the onceover and let me know. The invitation extends to any and all.

server_NewConnection code

B4X:
Private Sub server_NewConnection (Successful As Boolean, NewSocket As Socket)
'added by me to prevent disconnect on ongoing file transfer
    Log("server_NewConnection")
    If ReceivingFile = True OR SendingFile = True Then
        NewSocket.Close
        Log("ReceivingFile "& ReceivingFile)
    End If

Try
    'server accepted client
    If Successful Then
      'this code just added to get IP address of the sender
        Dim r As Reflector
        r.Target = NewSocket
           r.Target = r.GetField("socket")
           r.Target = r.RunMethod("getInetAddress") 'InetAddress
           Dim senderip As String = r.RunMethod("getHostAddress") 'it gets sender or client IP
      'end this code just added to get IP address of the sender
        WifiConnected = True
        StartAStream(NewSocket.InputStream, NewSocket.OutputStream)
        WifiStatus = "Host - Connected with " & senderip
    Else
        WifiStatus = "Error: " & LastException
    End If
    UpdateUI
    server.Listen
Catch
    Log ("LastException "&LastException)
End Try
    server.Listen 'added to restart listening - OK if twice

End Sub

Thank you.

Sandy
 
Last edited:

Douglas Farias

Expert
Licensed User
lol i dont see the last i go try to use
i and my friend make a new changes on the c# example

form1.cs
B4X:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using ICSharpCode.SharpZipLib.Checksums;

namespace FileTransfer
{
    public partial class Form1 : Form
    {
        private Thread connector;
        private TcpClient client;
        private volatile NetworkStream stream;
        private IPAddress ip;
        private volatile bool connectorRunning;
        private readonly int port = 21341;
        private readonly int STREAM_PREFIX = -2;
        private string fileToSend;
        public Form1()
        {
            InitializeComponent();
          
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            SetStatus(false);
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (connectorRunning)
            {
                log("O conector já esta em uso!.");
                return;
            }
            try
            {
                ip = IPAddress.Parse(txtIP.Text);
            }
            catch (Exception ee)
            {
                log(ee.Message);
            }
            connectorRunning = true;
            connector = new Thread(startConnection);
            connector.Name = "Conector";
            //connector.IsBackground = true;
            connector.Start();
        }
        private void SetStatus(bool connected)
        {
            MethodInvoker mi = delegate()
            {
                btnSend.Enabled = connected;
                btnConnect.Enabled = !connected;
                lblStatus.Text = connected ? "Conectado" : "Disconectado";
            };
            this.BeginInvoke(mi);
        }
        private void log(string msg)
        {
            MethodInvoker mi = delegate()
            {
                txtLogs.AppendText(msg);
                txtLogs.AppendText("\r\n");
                txtLogs.SelectionStart = txtLogs.Text.Length;
                txtLogs.ScrollToCaret();
            };
            if (this.InvokeRequired)
                this.BeginInvoke(mi);
            else
                mi.Invoke();
        }
      
        private void startConnection()
        {
            try
            {
                client = new TcpClient();
                client.Connect(ip, port);
                log("Connected!");
                SetStatus(true);
                stream = client.GetStream();
                BinaryReader br = new BinaryReader(stream);
                string fileName = "";
                while (connectorRunning)
                {                  
                    string prefix = br.ReadString();
                    if(prefix.Length  > 0)
                    {
                        log(string.Format("Prefixo recebido >> {0} - {1}", prefix, prefix.Length));
                    if (prefix == "SFF") //regular message
                    {
                        while((fileName = br.ReadString()).Length == 0 && fileName.Replace(" ", "").Length == 0);
                        log("Receiving: " + fileName);
                        fileName = System.IO.Path.Combine(Environment.CurrentDirectory, fileName);
                   }
                    else if (prefix == "SFD") //stream message
                    {
                        string SizeStr;
                        while((SizeStr = br.ReadString()).Length == 0 && SizeStr.Replace(" ", "").Length == 0);
                        int Size = int.Parse(SizeStr);
                        log(string.Format("Tamanho recebido: {0}", Size));
                        long length = br.ReadInt64(); //total stream length
                        log(string.Format("Inicializando download ({0})..", length));
                        Adler32 adler = new Adler32();
                        // using (FileStream fs = new FileStream(fileName, FileMode.Create)) //isso n tem a ver? n
                        if(File.Exists(fileName))
                            File.Delete(fileName);
                        using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate))
                        {
                            log(string.Format("Recebendo arquivo \"{0}\"!", fileName));
                            int total = 0;
                            int received = 0;
                            byte[] buffer = new byte[Size - total < 1024 ? Size - total : 1024];
                            while ((total += (received = stream.Read(buffer, 0, buffer.Length))) < Size)
                            {
                                this.Text = string.Format("Total: {0} - {1} => {2}", total, received, buffer.Length);
                                fs.Write(buffer, 0, received);
                                adler.Update(buffer, 0, received);
                            }
                            log("Fim!");
                        }
                        long checkSum = br.ReadInt64();
                        log(string.Format("Verifique o sol: {0} => {1}", checkSum, adler.Value));
                        if (checkSum != adler.Value)
                        {
                            throw new Exception("Checksum does not match.");
                        }
                        log("File received successfully.");
                    }
                    else
                    {
                        throw new Exception("Unexpected value: " + prefix);
                    }
                }
                }
                log("End of the connection");
            }
            catch (Exception e)
            {
                log(e.Message);
                log(e.StackTrace);
                SetStatus(false);
                connectorRunning = false;
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            System.Diagnostics.Process.GetCurrentProcess().Kill();
        }
      
        private void btnSend_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
            {
                btnSend.Enabled = false;
                fileToSend = openFileDialog1.FileName;
                Thread sendThread = new Thread(sendFile);
                sendThread.Name = "SendThread";
                sendThread.IsBackground = true;
                sendThread.Start();
            }
        }
        private void sendFile()
        {
            try
            {
                Adler32 adler = new Adler32();
                log("Sending: " + fileToSend);
                using (FileStream fs = new FileStream(fileToSend, FileMode.Open))
                {
                    byte[] name = Encoding.UTF8.GetBytes(Path.GetFileName(fileToSend));
                    BinaryWriter bw = new BinaryWriter(stream);
                    //send the first message which is the file name
                    bw.Write(name.Length);
                    bw.Write(name);
                    //send the stream message
                    bw.Write(STREAM_PREFIX);
                    long size = new FileInfo(fileToSend).Length;
                    bw.Write(size);
                    long written = 0;
                    byte[] buffer = new byte[8192];
                    while (written < size)
                    {
                        int count = fs.Read(buffer, 0, Math.Min((int)(size - written), buffer.Length));
                        stream.Write(buffer, 0, count);
                        adler.Update(buffer, 0, count);
                        written += count;
                    }
                    bw.Write(adler.Value);
                    log("File sent successfully.");
                }
            }
            catch (Exception e)
            {
                log(e.Message);
            }
            MethodInvoker mi = delegate()
            {
                btnSend.Enabled = !btnConnect.Enabled;
            };
            this.BeginInvoke(mi);
        }


    }
}


and on the file transfer we made,
B4X:
Public Sub SendFile(Dir As String, FileName As String)
Private bConverter As ByteConverter
Private www As String
    Dim totalSizeForSending As Long = File.Size(Dir, FileName)
    Dim In As InputStream = File.OpenInput(Dir, FileName)
    countingStream.Initialize(In)
    currentFile = FileName.SubString(FileName.LastIndexOf("/") + 1)
    astream.Write("SFF".GetBytes("UTF8"))
    astream.Write(currentFile.GetBytes("UTF8"))
    astream.Write("SFD".GetBytes("UTF8"))
    www = totalSizeForSending
    astream.Write(www.GetBytes("UTF8"))
    astream.WriteStream(countingStream, totalSizeForSending)
    lblFile = "Sending: " & currentFile
    timer1.Enabled = True
    SendingFile = True
    UpdateProgress
End Sub

i go test your samples with b4j
 

a_carignan

Member
Licensed User
I programmed with Purebasic a server to receive files sent by my phone. I rencotre three problems.
1) The file name is not communicating with the server.
2) Two lines entours the file contents, these lines are visible in an HTML file, plus it makes a JPG file unreadable.
3) Sometimes my server software opens once more to send the file. All open files are identical.
Hopefully we will you give me pourez possible solutions.
Ps. Everything leads me to believe that the content of the file is sent in tags, but I do not know how to capture them.
 

a_carignan

Member
Licensed User
I recommend you to use B4J to implement such a server. It will be much simpler as you can share almost all of the code.

https://www.b4x.com/android/forum/threads/37201/#content
I tried your solution but I can not implement it yet. Then, somehow, I must find the means to tell my PureBasic software file has arrived. Since the software that communicates the file that I want to transfer are already well advanced. I am the last additions to the software, not the beginning. Among the latest addition, I would like to send given by Bluetooth and WiFi. That is why I would like to know what is sent with the file, so I can rid myself. The file name is unimportant to me, since it will probably be the same every time.
 

a_carignan

Member
Licensed User
Thank you for information, I finally managed to transfer files. Here's what I finally understood the contents of the given ennvoyé for those interested.
1 - The number of characters of the file name.
2 - The name of the file.
3-4 bytes that seems to serve no purpose.
4 - The size of the file.
5 - The actual file.
6 - Return of 4 byte end junk.
 

a_carignan

Member
Licensed User
The software you create sent the file name and file such time, my list is taking in count. The software that I designed to receive RECEIVE gave them several block and this creates SEVERAL receptions event. This is why I created a temporary file that receives the given and eventually I created the file destinations from its data. Laziness, I do not have to try to understand the checksum is why I called junk. While this information is undoubtedly important for the proper test data sent, but I have not needed.
 

Dale_Schroeder

New Member
Licensed User
can someone tell me how they got the buttons and edittext box's different colors in this app then the default light grey?
 
Status
Not open for further replies.
Top