B4A Library [B4X] FTP Server implemented with Socket and AsyncStreams

ftp.gif

(Note that the FTP client above is not part of this solution. It only demonstrates how you can use a standard FTP client to communicate with the server.)

This is an example of using low level network features to implement a high level protocol.
It is an implementation of a standard FTP server. You can use standard FTP client programs to send or receive files.
As it is based on AsyncStreams and it can handle multiple concurrent connections.
It is compatible with B4J, B4i and B4A.
Note that only passive mode (which is the preferred method) is implemented.

It is a good example of using classes to handle multiple clients.
The solution is made of three classes:
FTPServer - There is a single instance of this class. It manages the server socket that listens on the control port (main port).
It also manages the connected clients and assigns a data port for each client.

FTPClient - There is an instance of this class for each active connection. It uses AsyncStreamsText class to read the clients text commands. It creates a new FTPDataConnection instance for each task that requires communication over the data socket (upload, download and list files).

FTPDataConnection - An instance is created for each data task. It sends or receives the data and then closes the connection. Note that a new method was added to AsyncStreams that allows closing the channel after the data was sent (SendAllAndClose). Clients expect the data socket to be closed after the data is sent. There isn't any other cue that tells the client that all data was sent.

Not all commands are implemented. The common feature are supported: upload, download, delete, rename, list and others.

In B4A and B4J, JavaObject is used to call a native API that gets the canonical path and verify that the path is inside the set directory. There is no equivalent API in iOS. It is less important as each app is sandboxed anyway.

Using the server is simple:
B4X:
server.Initialize(Main, "FTPServer")
server.SetPorts(51041, 51042, 51142)
server.AddUser("Test", "test") 'user name and password.
server.BaseDir = File.DirRootExternal
server.Start
SetPorts - Sets the control port and the range of available data ports. When running on a non-mobile device, you need to make sure that the firewall allows incoming connections on all these ports.
AddUser - Adds a user name and password. You can call it multiple times.
BaseDir - Sets the client root folder.

In B4i you also need to stop the server when the application moves to the background and start it again when it resumes.

Implementing an FTP server that properly supports multiple clients is not a simple task. The code itself is not too complicated and is a good example for anyone who is working with network sockets.


SS-2016-12-19_16.47.26.png


Updates

V1.10 - Implemented as a cross platform b4xlib. Fixes an issue with uploading of small files. Adds support for UTF8 files and folders names.

Example is attached. FTPServer.b4xlib is the library itself.
 

Attachments

  • FTPServer.b4xlib
    6.2 KB · Views: 1,540
  • Project.zip
    14.2 KB · Views: 1,700
Last edited:

JackKirk

Well-Known Member
Licensed User
Longtime User
I have been using a copy of this code with minor modifications and it works beautifully.

I can't seem to find a way to find the IP address of a client as he tries to log in.

Is this possible?

Thanks in anticipation...
 

JackKirk

Well-Known Member
Licensed User
Longtime User
Thanks Erel.
 

T201016

Active Member
Licensed User
Longtime User
Hi, Erel
Great idea, I miss SSL in your project yet. Will you think about it? ;)
 

T201016

Active Member
Licensed User
Longtime User
And that's good news, I think I'll have to do something myself, thanks for the message.
 

TomDuncan

Active Member
Licensed User
Longtime User
Hi all,
I have been testing the ftp server over the last few days with a new ip_camera from China.
In a local Ethernet environment all is well.
I then pointed the camera via ftp to my noip address. I have port forwarded the three ports.
Most of the time it works but I do get this error.

B4X:
CurrentPath: /cam_03/20180721/IMG001
client: PASV
client: STOR IMG_chn0_TIMER_MNG_20180721164328_001.jpg
Data connection terminated: /cam_03/20180721/IMG001/IMG_chn0_TIMER_MNG_20180721164328_001.jpg
client: SIZE IMG_chn0_TIMER_MNG_20180721164328_001.jpg
client: PASV
client: APPE IMG_chn0_TIMER_MNG_20180721164328_001.jpg
java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at java.net.SocketInputStream.read(SocketInputStream.java:127)
    at anywheresoftware.b4a.randomaccessfile.AsyncStreams$AIN.run(AsyncStreams.java:199)
    at java.lang.Thread.run(Thread.java:748)
error: (SocketTimeoutException) java.net.SocketTimeoutException: Read timed out
Data connection terminated: null
terminated
Number of clients: 0
Number of clients: 1
client: USER xx
client: PASS xx
User logged in: xx
client: SYST
client: TYPE I
client: CWD cam_03
CurrentPath: /cam_03
client: CWD 20180721

this error does seem to be random.
thanks
Tom
 

ajk

Active Member
Licensed User
Longtime User
Constanty get in WinSPC:

Disconnected from server
Could not retrieve directory listing
Error listing directory '/'.

and in log:

client: USER test
client: PASS test
User logged in: test
client: SYST
client: FEAT
client: OPTS UTF8 ON
client: PWD
client: TYPE A
client: PASV
error: <B4IExceptionWrapper: Error Domain=caught_exception Code=0 "*** -streamError only defined for abstract class. Define -[B4IFastSocket streamError]!" UserInfo={NSLocalizedDescription=*** -streamError only defined for abstract class. Define -[B4IFastSocket streamError]!}>
terminated
DataConnection_Close
Number of clients: 0
Number of clients: 1
client: USER test
client: PASS test
User logged in: test
client: SYST
client: FEAT
client: OPTS UTF8 ON
client: PWD
client: TYPE A
client: PASV
error: <B4IExceptionWrapper: Error Domain=caught_exception Code=0 "*** -streamError only defined for abstract class. Define -[B4IFastSocket streamError]!" UserInfo={NSLocalizedDescription=*** -streamError only defined for abstract class. Define -[B4IFastSocket streamError]!}>
terminated
DataConnection_Close
Number of clients: 0
 

ajk

Active Member
Licensed User
Longtime User
Like in Basic
10 Erel is always right
20 GOTO 10

Why can't I use lower numbers for Port?
 

OliverA

Expert
Licensed User
Longtime User
Why can't I use lower numbers for Port?
Port numbers below 1024 require administrative rights on most system
 
Top