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 message
nargin
%
% 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 infinite
fprintf
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.java
Running 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 1
localhost
>> 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:3000
Connected 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!).
Can your server script be run continuously and not as a function?
ReplyDeleteI 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:
ReplyDeleteYes, 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.
I have no idea how to program in java, bet I found some code that opens a socket, I need the latest data on that socket, could you pl show some example code that opens a socket then flushes it so that I can understand hoe it works. Thanks.
DeleteHello Rodney,
ReplyDeletei 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,
ReplyDeleteI'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
ReplyDeleteI'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,
ReplyDeleteSo 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
ReplyDeletei'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,
ReplyDeleteIt 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,
ReplyDeletenow it's not even connecting at all. Not even telnet nor changing the ports.
Just wondering what bandwidth/latency you get for transfers ?
ReplyDelete@David
ReplyDeleteUsing 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
ReplyDeleteWhat do I need to change to allow the client to read other data types? lets say a float value for example?
Thanks!
@Anonymous
ReplyDeleteA 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,
ReplyDeleteThank 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 comment has been removed by a blog administrator.
ReplyDeleteThis might be helpful in avoiding the use of helper classes:
ReplyDelete"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:
ReplyDeleteUnfortunately, 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 ?
ReplyDeletePlease Help
With regards,
Catalin
Catalin:
ReplyDeleteUnfortunately 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, I'm really interested in your helper Java class for spawning of a separate Thread within Matlab, did you manage to find it? Published anywhere?
DeleteBR Johan
hi rod,
ReplyDeleteyour 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,
ReplyDeleteCan 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
ReplyDeletei want to send complete MAT file to another compter of any size.
ReplyDeleteis 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...
ReplyDeleteI've a problem.
ReplyDeleteI 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?
This comment has been removed by a blog administrator.
ReplyDeleteHi Rodney,
ReplyDeleteThanks a lot for your codes, they have been really helpful. I have a problem when transporting data it is usually modified at the receiving end. For example when I transfer char(191)=¿ I receive a blank string on the client end. Please how can I solve this problem.
Also,how can you adapt the code for large amounts of data
Hi Rodney,
ReplyDeleteI was testing your scripts on Matlab R2015a running on Mac OS C 10.9.5 and followed the same steps as in the example (including compiling of data_reader.java) but got the following error:
message: 'Error using <a href="matlab:matlab.internal.language.introspective.errorDocCallbac...'
identifier: 'MATLAB:Java:GenericException'
Any idea of what may be going wrong?.
Thanks for your help,
Juan
Hi Juan,
ReplyDeleteCan you give me a little more information? Which line is the error coming from, what is the full errors message (your string above is truncated).
Hi Rodney, I have the same error than Juan testing the script on Matlab R2014b Win 7 64bits. Here's the full error message
ReplyDelete:s =
message: 'Error using <a href="matlab:matlab.internal.language.introspective.errorDocCallback('client', 'C:\Users\*-...'
identifier: 'MATLAB:Java:GenericException'
stack: [1x1 struct]
*script's folder path
Well this is slightly odd. In regard to @Juan and @Anonymous' errors:
ReplyDeleteI have just installed MATLAB 2015a on my Win 7 64-bit machine. I ran the server / client as per above, and I received the same error.
The full error (put a breakpoint on the s = lasterror line and look at s.message) is a Java Socket ConnectionRefused error.
When I ran it a second time, it worked fine. And since then I've had it running in a loop for 30 minutes without any connection issues.
*Server*
while true
server(message, 3000, 10);
end
*Client*
while true
data = client('localhost', 3000)
end
I'd recommend checking that your Windows firewall (or Mac equivalent) is disabled, or set to allow MATLAB to accept connections.
To check that the server.m code is working, run server as you would normally:
server(message, 3000, 10);
Then use telnet to connect to the server and verify the strings of data are received (below instructions are for Windows):
- Open a command window
- "telnet localhost 3000"
Thank You,
ReplyDeleteIt works perfectly when using telnet to connect to server, but when using the client.m file, it gives the same error. I will try to check more toroughly, but It is probably not a firewall issue since Matlab is allowed to use the network, and I can run a TCP client /server session by using Mathworks ICT toolbox.
OK, so it is an issue with client.m.
ReplyDeleteYou can try modifying the catch statement to catch the error:
catch ex
And put a break point in there so you can look at 'ex' and see exactly what the exception being thrown is. It might give some clues
Ok I figured what was wrong, It works perfectly now.
ReplyDeleteThanks
Hi , I'm having trouble reading my data
ReplyDeletes =
message: 'Undefined function 'DataReader' for input arguments of type 'java.io.DataInputS...'
identifier: 'MATLAB:UndefinedFunction'
stack: [1x1 struct]
I'm trying to send sensor information from an android device into MATLAB as such:
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(s.getOutputStream())), true);
out.printf(String.valueOf(d.format(accel[0]))+"\t"+String.valueOf(d.format(accel[1]))+"\t"+String.valueOf(d.format(accel[2])) + "\n");
Is the error because the data_reader class isn't formatted to read my input?
How would I set it to read it?
Regards
You must tell MATLAB where to find the compiled DataReader class (In my case it is located in the C:\matlab\matlab_socket directory):
Delete>> javaaddpath('C:\matlab\matlab_socket');
Ah yep , just got it.
DeleteSorry I'm abit new to MATLAB, but would you know how to format the output so that they each get a column, and for it to constantly stream?
Currently I'm getting :
data(1x60 bytes):
-0.182 -0.010 9.874 0.062 -0.024 0.034 8.580 11.400 47.940
It only shows 9 values
Actually I'm still getting the error , even though I have added the compiled class.
Deletejavaaddpath('C:\Program Files\MATLAB\design');
data = client('192.168.1.13',6000,10);
To constantly stream you will want to modify client.m to loop internally:
Deletewhile true
bytes_available = input_stream.available;
if (bytes_available == 0)
pause(1.0); % wait before trying again
else
fprintf(1, 'Reading %d bytes\n', bytes_available);
data_reader = DataReader(d_input_stream);
message = data_reader.readBuffer(bytes_available);
end
end
It looks like the data you are sending is a string with the values encoded in it. Thus you will want to do something like:
message_str = char(message); % convert to a string
parsed_data = sscanf(message_str, '%f'); % convert to doubles - will be a column vector
columned_data = reshape(parsed_data, 3, []); % if your data is in groups of 3, each column is 1 set
The error suggests that MATLAB cannot find the DataReader class. Maybe try moving the DataReader.class out of Program Files just in case Windows is preventing access/execution of the .class file and update the java path with javaaddpath accordingly?
DeleteOther than that, make sure none of your code makes a call to 'clear all' as certain MATLAB versions used to clear the java path with that.
This comment has been removed by the author.
DeleteHi, have I done this correctly?
Deletewhile true
bytes_available = input_stream.available;
if (bytes_available == 0)
pause(0.2); % wait before trying again
else
fprintf(1, 'Reading %d bytes\n', bytes_available);
data_reader = DataReader(d_input_stream);
message = data_reader.readBuffer(bytes_available);
end
end
message = char(message');
message_str = char(message); % convert to a string
parsed_data = sscanf(message_str, '%f'); % convert to doubles - will be a column vector
columned_data = reshape(parsed_data, 3, []); % if your data is in groups of 3, each column is 1 set
% cleanup
input_socket.close;
break;
if ~isempty(input_socket)
input_socket.close;
end
s = lasterror
% pause before retrying
pause(1);
end
end
end
With the above code, It reads the first retry then loops and stops at second retry as such :
DeleteRetry 1 connecting to 192.168.1.13:6000
Connected to server
Reading 59 bytes
Retry 2 connecting to 192.168.1.13:6000
Connected to server
Not quite. You'll never get to the code where the data is parsed (check matching ends). Remove one of the 'end's after the data_reader.readBuffer call and put it after the 'columned_data' line.
DeleteOtherwise the code will just run forever in a loop reading data into 'message'.
The socket code appears to be working for you. The rest is mostly just learning MATLAB! Unfortunately I can't spare the time to help you too much with that. :)
One thing I'd suggest is to set a breakpoint in the inner loop (ie the readBuffer() call) and then use F10 to step through the code and look at what is happening each line, look at the MATLAB 'workspace' tab to see what the data types and their contents are.
That will be the best way to learn how to get the data into a format you want. Particularly as you can execute code in the Command Window during a breakpoint to test.
I will give it a go!
DeleteAnd no that's fine, I appreciate all the help you've given.
I just realised I commented out the lasterror line, when I re-add it, the Undefined DataReader function is back.
DeleteWhat could be causing it?
It isn't back. lasterror returns funnily enough, the last error triggered in MATLAB. Given the last error that happened was the undefined DataReader function, it keeps return that same error.
DeleteAnd you want to make sure that your code only connects once, then reads multiple times, then closes once at the end. If you are calling 'input_socket.close' within your inner loop, then each time you try and read, an exception will occur.
Step through the code in the debugger and make sure it is doing what you logically want it to do.
Hi,
DeleteThe input_socket.close is in the outer while loop.
However I've realised after debugging it a few times, that after the first iteration of the while loop and during the first connect, the catches an error after the line : data_reader = DataReader(d_input_stream); is called. It then proceeds to close the socket, and reconnect. It then runs the while loop indefinitely.
Sorry to bother you again, but I have no idea what is the issue.
What could be wrong?
Solved it! The DataReader class wasn't being read, due to an MATLAB issue. Added the static path to the classpath docu and it worked!
DeleteThis comment has been removed by the author.
DeleteThis comment has been removed by the author.
DeleteHi Rod, how do I get the code to save the values into workspace at the end of the function?
ReplyDeleteHi Rodney,
ReplyDeleteI would contact you for publishing the code. I've made a connection between their code (client) to a python code (server). The connection is made quickly and easily. My question is: I am a beginner in programming;)
how to send a message with your client code? if I connect to server, what the code is written with python, connect server does not receive message (message is empty)
Retry 8 connecting to localhost:3000
ReplyDeletes =
struct with fields:
message: 'Error using client (line 25)↵Java exception occurred: ↵java.net.ConnectException: Connection refused: connect←↵→at java.net.DualStackPlainSocketImpl.connect0(Native Method)←↵→at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)←↵→at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)←↵→at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)←↵→at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)←↵→at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)←↵→at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)←↵→at java.net.Socket.connect(Socket.java:589)←↵→at java.net.Socket.connect(Socket.java:538)←↵→at java.net.Socket.(Socket.java:434)←↵→at java.net.Socket.(Socket.java:211)←'
identifier: 'MATLAB:Java:GenericException'
stack: [1×1 struct]
I get this error. Any help?
Hey Anon,
DeleteThat simply means that your server is not accepting a connection on port 3000.
One simple way to check is to use Telnet to attempt to connect (instead of using the MATLAB code). If you're running Windows:
- Bring up a command window (Press Windows button + r, then type 'cmd')
- Type 'telnet localhost 3000'
If you get something like below, then your server is NOT accepting connections.
C:\Users\Rodney>telnet localhost 3000
Connecting To localhost...Could not open connection to the host, on port 3000: Connect failed
Hey Rodney,
ReplyDeletethanx for your reply. I tried it and the server is not accepting connections. I got same error. How can i fix it ? tats would be very helpful! thanx
Well... it depends on what the server is? The server will be a program that listens / accepts on a socket. As an example you can use the MATLAB code here https://iheartmatlab.blogspot.com/2008/08/tcpip-socket-communications-in-matlab.html
Delete