Monday, September 7, 2009

TCP/IP Socket Communications in MATLAB - Part 2

So my little blog post about TCP/IP Socket Communications in MATLAB was rather popular despite being a very simple example.

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:
message = zeros(1, bytes_available, 'uint8');
for i = 1:bytes_available
message(i) = d_input_stream.
readByte;
end
For each individual byte within the message, there was the overhead of a function call.

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
% CLIENT connect to a server and read a message
%
% Usage - message = client(host, port, number_of_retries)
function message = client(host, port, number_of_retries)

import java.net.Socket
import java.io.*

if (
nargin < number_of_retries =" 20;" style="color: rgb(0, 153, 0);">% set to -1 for infinite
end

retry = 0;
input_socket = [];
message = [];

while true

retry = retry + 1;
if ((number_of_retries > 0) && (retry > number_of_retries))
fprintf(1, 'Too many retries\n');
break;
end

try
fprintf(1, 'Retry %d connecting to %s:%d\n', ...
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.
getInputStream;
d_input_stream =
DataInputStream(input_stream);

fprintf(1, 'Connected to server\n');

% read data from the socket - wait a short time first
pause(0.5);
bytes_available = input_stream.available;
fprintf(1, 'Reading %d bytes\n', bytes_available);

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 ~
isempty(input_socket)
input_socket.close;
end

% pause before retrying
pause(1);
end
end
end

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.

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
>> 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('
localhost', 3000)
Retry 1 connecting to
localhost: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:
message = zeros(1, bytes_available, 'uint8');
d_input_stream.
readFully(message, 0 bytes_available);
At first glance that seems great. It runs without error, but hey, you don't seem to be getting any output?¿?

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!).

52 comments:

  1. 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

    ReplyDelete
  2. 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.

    ReplyDelete
  3. 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

    ReplyDelete
  4. 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!

    ReplyDelete
  5. @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.

    ReplyDelete
  6. 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

    ReplyDelete
  7. @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.

    ReplyDelete
  8. 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

    ReplyDelete
  9. Hi again,
    now it's not even connecting at all. Not even telnet nor changing the ports.

    ReplyDelete
  10. Just wondering what bandwidth/latency you get for transfers ?

    ReplyDelete
  11. @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.

    ReplyDelete
  12. 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!

    ReplyDelete
  13. @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');

    ReplyDelete
  14. 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.

    ReplyDelete
  15. This comment has been removed by a blog administrator.

    ReplyDelete
  16. 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

    ReplyDelete
  17. 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.

    ReplyDelete
  18. 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

    ReplyDelete
  19. 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.

    ReplyDelete
  20. 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

    ReplyDelete
  21. 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

    ReplyDelete
  22. 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!

    ReplyDelete
  23. 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...

    ReplyDelete
  24. 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?

    ReplyDelete
  25. This comment has been removed by a blog administrator.

    ReplyDelete
  26. Hi Rodney,
    Thanks 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

    ReplyDelete
  27. Hi Rodney,

    I 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

    ReplyDelete
  28. Hi Juan,

    Can 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).

    ReplyDelete
  29. Hi Rodney, I have the same error than Juan testing the script on Matlab R2014b Win 7 64bits. Here's the full error message

    :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

    ReplyDelete
  30. Well this is slightly odd. In regard to @Juan and @Anonymous' errors:

    I 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"

    ReplyDelete
  31. Thank You,
    It 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.

    ReplyDelete
  32. OK, so it is an issue with client.m.

    You 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

    ReplyDelete
  33. Ok I figured what was wrong, It works perfectly now.
    Thanks

    ReplyDelete
  34. Hi , I'm having trouble reading my data

    s =

    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

    ReplyDelete
    Replies
    1. 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');

      Delete
    2. Ah yep , just got it.

      Sorry 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

      Delete
    3. Actually I'm still getting the error , even though I have added the compiled class.

      javaaddpath('C:\Program Files\MATLAB\design');
      data = client('192.168.1.13',6000,10);

      Delete
    4. To constantly stream you will want to modify client.m to loop internally:

      while 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

      Delete
    5. 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?

      Other 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.

      Delete
    6. This comment has been removed by the author.

      Delete
    7. Hi, have I done this correctly?
      while 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

      Delete
    8. With the above code, It reads the first retry then loops and stops at second retry as such :
      Retry 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

      Delete
    9. 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.

      Otherwise 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.

      Delete
    10. I will give it a go!

      And no that's fine, I appreciate all the help you've given.

      Delete
    11. I just realised I commented out the lasterror line, when I re-add it, the Undefined DataReader function is back.

      What could be causing it?

      Delete
    12. 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.

      And 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.

      Delete
    13. Hi,
      The 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?

      Delete
    14. 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!

      Delete
    15. This comment has been removed by the author.

      Delete
    16. This comment has been removed by the author.

      Delete
  35. Hi Rod, how do I get the code to save the values into workspace at the end of the function?

    ReplyDelete