One of the main limitations that people found when trying to utilise the server/client scripts for their own applications, was that it was incredibly inefficient at shifting large volumes of data around. This is thanks to the following lines:
For each individual byte within the message, there was the overhead of a function call.message = zeros(1, bytes_available, 'uint8');readByte
for i = 1:bytes_available
message(i) = d_input_stream.;
end
I mentioned in comments on MATLAB Central that I made a Java class that bypassed this overhead and allowed for more efficient transfer. Being in a caring sharing mood (and getting sick of emailing it to people all the time!) I thought I would upload it also.
The is available on the Mathworks File Exchange: TCP/IP Socket Comms Example using Java Class
Compiled Java?
The client/server scripts are essentially identical to their previous iterations.
The server still uses a ServerSocket and the resulting DataOutputStream to which we write data.
The client side uses a Socket to connect to the specified host and port which provides us an InputStream which we wrap in a DataInputStream to read data from. However, instead of using the interface of DataInputStream directly, we hand off to another function (the new DataReader class) to perform the read.
The code for the example client is outlined below. I have bolded the changes. (The server is unchanged).
client.m
Instead of looping for each byte, we now ask the DataReader object to read the specified number of bytes and return them to us. So a single function call per read. This could be used to read buffers of expected data sizes.% CLIENT connect to a server and read a messagenargin
%
% Usage - message = client(host, port, number_of_retries)
function message = client(host, port, number_of_retries)
import java.net.Socket
import java.io.*
if (< number_of_retries =" 20;" style="color: rgb(0, 153, 0);">% set to -1 for infinitefprintf
end
retry = 0;
input_socket = [];
message = [];
while true
retry = retry + 1;
if ((number_of_retries > 0) && (retry > number_of_retries))
(1, 'Too many retries\n');fprintf
break;
end
try
(1, 'Retry %d connecting to %s:%d\n', ...getInputStream
retry, host, port);
% throws if unable to connect
input_socket = Socket(host, port);
% get a buffered data input stream from the socket
input_stream = input_socket.;DataInputStream
d_input_stream =(input_stream);fprintf
(1, 'Connected to server\n');fprintf
% read data from the socket - wait a short time first
pause(0.5);
bytes_available = input_stream.available;
(1, 'Reading %d bytes\n', bytes_available);isempty
data_reader = DataReader(d_input_stream);
message = data_reader.readBuffer(bytes_available);
message = char(message'); % Data comes out as a column vector
% cleanup
input_socket.close;
break;
catch
if ~(input_socket)
input_socket.close;
end
% pause before retrying
pause(1);
end
end
end
DataReader
Below is the Java source for the DataReader class (available in the MATLAB File Exchange also).
import java.io.*;
class DataReader
{
public DataReader(DataInput data_input)
{
m_data_input = data_input;
}
public byte[] readBuffer(int length)
{
byte[] buffer = new byte[length];
try
{
m_data_input.readFully(buffer, 0, length);
}
catch (StreamCorruptedException e)
{
System.out.println("Stream Corrupted Exception Occured");
buffer = new byte[0];
}
catch (EOFException e)
{
System.out.println("EOF Reached");
buffer = new byte[0];
}
catch (IOException e)
{
System.out.println("IO Exception Occured");
buffer = new byte[0];
}
return buffer;
}
private DataInput m_data_input;
}
It is very simple class that is given a DataInput to read from, and has a single public method readBuffer(int length) that returns a byte array.If you have the Java Software Development Kit installed, you can compile the class with:
C:\matlab\matlab_socket> javac data_reader.javaRunning the Client/Server example with DataReader
This is the same as the previous example, with one exception. You must tell MATLAB where to find the compiled DataReader class (In my case it is located in the C:\matlab\matlab_socket directory):
>> javaaddpath('C:\matlab\matlab_socket');Opening up two instances of Matlab:% Instance 1localhost
>> message = char(mod(1:1000, 255)+1);
>> server(message, 3000, 10)
Try 1 waiting for client to connect to this host on port : 3000
Try 2 waiting for client to connect to this host on port : 3000
Try 3 waiting for client to connect to this host on port : 3000
Try 4 waiting for client to connect to this host on port : 3000
Client connected
Writing 1000 bytes
% Instance 2 (simultaneously)
% NOTE: If the 'server' was runnning on a non local machine, substitute its IP address
% or host name here:
% data = client('10.61.1.200', 2666); % To connect to server at IP 10.61.1.200:2666
>> javaaddpath('C:\matlab\matlab_socket');
>> data = client('', 3000)localhost
Retry 1 connecting to:3000Connected to server
Reading 1000 bytes
data =
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Why not call readFully() from within client.m?
The more astute readers may be asking the above question. Why not do something like this in client.m:
At first glance that seems great. It runs without error, but hey, you don't seem to be getting any output?¿?message = zeros(1, bytes_available, 'uint8');readFully(message, 0 bytes_available)
d_input_stream.;
The problem is that DataInputStream.readFully() takes a reference to a byte array to populate.
When you pass things around in MATLAB that are to be modified, MATLAB supplies a copy. Thus the Java most likely sees a byte array reference, and populates it, but it is only the copy that is populated, not the original message array.
Until there exists some way in MATLAB to pass references to Java methods, I am stuck using helper Java classes (If anyone else has ideas on how this could be done better, feel free to comment!).
25 comments:
Can your server script be run continuously and not as a function?
I already have a client script that's emulating what one of my devices is sending using the instrument control tcpip functions but they dont have functions that can help with writing a script for a server.
I've tried modifying your server script but I'm getting java socket issues.
Thanks
Anonymous:
Yes, you could run the script 'continuously'.
Using my original post (http://iheartmatlab.blogspot.com/2008/08/tcpip-socket-communications-in-matlab.html) as a guide you can easily construct the same ServerSocket / DataOutputStream.
Then you can make repeated calls to d_output_stream.writeBytes() and flush() to send as much information as you want.
Hello Rodney,
i want to modify your server.m application so that the server receives and displays data from the client (and after that replies to the client with some data).
im not familiar with java-programming, but i haven't found a method similar to getInputStream (as it is used in the Socket-class) for the ServerSocket-class. So how can i receive data on the server-side?
Thanks for any help!
albert
Hello Rodney,
I'm trying to communicate Matlab with a Java server. It works very nicely if I send chars or strings (in both directions), but I haven't been able to send and read correctly a numeric Matlab matrix to compute in a method at the Java server. Any ideas on how should I proceed?
Thanks!
@jdjaramillo
I'm not sure what you are using to actually send the information to/from the Java server, but I'll assume you are using something similar to my scripts.
A couple of things:
#1 Be wary of the data types that you are sending. Do a 'whos' command to see.
The default data type in MATLAB is a double, that is it occupies 8 bytes. However the code in my script takes a character (1 byte) array so you will need to convert the double matrix to a byte matrix.
The best way to do this in MATLAB is to use the TYPECAST command:
"TYPECAST Convert datatypes without changing underlying data."
Now, the typecast command only takes vectors of data, so you will need to reshape your matrix to a 1D array (leading me onto point #2):
tmp = rand(2,2); % 32 bytes
tmp_bytes = typecast(reshape(tmp, 1, 4), 'int8'); % 32 bytes also
You can now send tmp_bytes using my scripts. You just need to know how to reconstruct it at the other end
#2 If you sent the raw data as a stream of bytes, you lose its context, the number of rows/columns, the data type the data was stored as (single, double, int16 etc)
So you might want to send a header that is a predefined size containing information such as:
[NumRows][NumCols][ElementSize][IntegerOrFloat]
That way you also know how much data you expect to read off the Socket.
Rodney,
So I've modified your server script to run continuously but I'm having issues with clients connecting to it.
I've been using the instrument toolbox tcpip function as the client and your script as the server.
I'm not sure where the issue is as I've managed to get a connection before but the sucessfulness has been inconsistent.
I noticed a line. server_socket=setSoTimeout(1000).
from your comments the 1000 is in ms. I've tried different combination of values for this function as well but still have issues with my client not connecting.
Do you have any suggestions?
Thanks
@WeiRen
i'd firstly test to see if its a problem with the client or the server.
Run the server up, possibly with a larger timeout (say 10 seconds) and then attempt to connect using telnet.
IE if the server is running on your local machine then:
telnet localhost 'port'
You should hopefully be able to establish a connection and see "Client connected" in your MATLAB command window.
If you can't connect to the server, then the only thing I can think of is something else is using that port. (try a different port for now?)
Most commonly this is a crashed/errored previous runing of the server script. Closing MATLAB completely and restarting should unbind the ServerSocket to that port. This is the reason the try/catch block is in place, to release the ServerSocket.
Hi,
It seems to be working now. Occasionally I see 'connection successful' on my client but not on the server side.
Also how would I go about to receive messages on the server side? I haven't had much luck implementing some of the functions from your client example to the server script.
Thanks
Hi again,
now it's not even connecting at all. Not even telnet nor changing the ports.
Just wondering what bandwidth/latency you get for transfers ?
@David
Using the Java class to read the data, i have successfully read 24 channels of 96kHz 16 bit PCM data in real time(which is 4.5 MB/second).
I've never used it for the purpose of transferring large amounts of data as fast as possible.
Hi Rodney
What do I need to change to allow the client to read other data types? lets say a float value for example?
Thanks!
@Anonymous
A float for example is made up of 4 bytes (typically).
So... read 4 bytes. Then convert them to a float:
data = data_reader.readBuffer(4);
data = typecast(data, 'float');
Hi Rod,
Thank you for your useful codes. I hava a question. I want to send a big size data about 120000 bytes from server to client. But by your codes, the client cannot receive all the data and it can only read 8192 bytes as maximum. What is your suggestion?
Thank you very much.
This might be helpful in avoiding the use of helper classes:
"Since MATLAB arrays are passed by value, any changes that a Java method makes to them are not visible to your MATLAB code. If you need to access changes that a Java method makes to an array, then, rather than passing a MATLAB array, you should create and pass a Java array, which is a reference. For a description of using Java arrays in MATLAB, see Working with Java Arrays."
Reference: http://www.mathworks.com/access/helpdesk/help/techdoc/matlab_external/f6425.html
To Anonymous above:
Unfortunately, Matlab's javaArray() function will not create arrays of Java primitive types. So it isn't possible to create a byte[] array.
The Java class seems to be the cleanest solution.
How can I make your server script to continuasly listen for a client to connect but not to block matlab , I've tried to make a loop so that it continuasly listens when a client is connected after the client connects to the server , it goes back to the loop and waits for other client to connect to the server, i have a GUI and I call your script in loop but it blocks my matlab and simulink so while in the loop for listening my simulink model that I run and modify some parameters from the GUI in it doesn't work any ideea how to put only the GUI to sleep for say about 10 seconds but the simulink model to continue to work ?
Please Help
With regards,
Catalin
Catalin:
Unfortunately MATLAB is single threaded (well, with the tools we have
available to program with it anyway).
I have made a Java class that handles the server duties by firing off
another thread to listen for clients. This might be more useful for
what you need. Unfortunately it is on my work machine. I'll have a
look Tuesday morning to see if I can find it.
hi rod,
your code is really helpfull,but im facing a little problem.I m running both server and client on one machine
on server side code is working and msg appears writing 1000 bytes
but on client side
it shows connected but it retries till total no of tries and msg appears too many tries and at the end data is empty matrix
plz help me out how to fix it ASAP!
thanx
Main,
Can i recommend you make the following changes to client.m to try and find out what the problem is:
catch exception
if ~isempty(input_socket)
input_socket.close;
end
% print out error message
fprintf(1, '%s\n', exception.message);
% show where it occured
exception.stack
% pause before retrying
pause(1);
end
That might point you in the right direction, ie show if its a socket error or a function not found error (check your Java path!)
Rod
Thanks Rod
i want to send complete MAT file to another compter of any size.
is it possible?if,how(so that complete data is received on rempte computer)
thanks!
if you need to send a complete mat file, you can use pnet, a matlab 3rd party function. I use it to send and receive matlab classes. In reality all it does is save the class to a mat file, then reads from it as a byte stream, on the other side it saves the byte stream to a mat file then loads it. it should be possible to do the same using the java functions...
I've a problem.
I tried your example, but It is not working. I tried your example with two computers. One computer is a Mac. Can this be the problem?
I became the following message:
Retry 1 connecting to 109.192.97.8:3000
s=
message: [1x545 char]
identifier: 'MATLAB:Java:GenericException'
stack: [1x1 struct]
Retry 2 connecting to 109.192.97.8:3000
s=
message: [1x545 char]
identifier: 'MATLAB:Java:GenericException'
stack: [1x1 struct]
....
I try it with the telnet and only one computer then it works, but when I try it with two computers, it doesn't work. Can you help me?
Post a Comment