Socket & PrintWriter issue-- writing a float to a C client

J

Jim Bancroft

Hi everyone,

I have a Java class that opens a socket and connects with a client. The
client is written in C.

All goes well, but when it comes time to write a floating point value the
client has trouble reading it. I realize this isn't a C newsgroup but I was
hoping someone's seen what I'm experiencing and can help?

In a nutshell the java server looks like this:

SocketServer listener;
int portNumber;
Socket mySocket;
PrintWriter outp;

listener= new ServerSocket(portNumber);
mySocket=listener.accept();
outp = new PrintWriter(mySocket.getOutputStream(),true);
outp.println(85.6f);


The C client does this:

char[128] buffer;
int num;
float flt;

memset(buffer,'\0',sizeof(buffer));
n = read(sockfd,buffer,127);
flt=atof(buffer);
printf("You returned %lf from the server\n",flt);

The printf statement shows a value of 0.0000 no matter what floating point
value I send via the java server. If I send an integer from the server, I
have no problems using atoi() and printing it out.

Again, I know this is a java newsgroup but does anyone happen to know what
I'm doing wrong in my C code above?
 
G

Gordon Beaton

All goes well, but when it comes time to write a floating point
value the client has trouble reading it. I realize this isn't a C
newsgroup but I was hoping someone's seen what I'm experiencing and
can help?

You should be using strtod() or strtof() instead of atof() if you want
to catch conversion errors. I suggest you display the text received by
the C program and compare it with the format accepted by strtod().

Or if you use sscanf() you can be more specific about what gets
matched.

There are also Locale issues to consider. Does the float contain
commas, or decimals?

/gordon
 
J

Jean-Francois Briere

Rather do:

// Java:

SocketServer listener;
int portNumber;
Socket mySocket;
DataOutputStream outp;

listener= new ServerSocket(portNumber);
mySocket=listener.accept();
outp = new DataOutputStream(mySocket.getOutputStream());
outp.writeFloat(85.6f);

// C:

char buffer[4];
int num;
float flt;

num = read(sockfd, buffer, 4);
memcpy(&flt, buffer, 4);
printf("You returned %f from the server\n", flt);

Regards
 
D

Dale King

Jean-Francois Briere said:
Rather do:

// Java:

SocketServer listener;
int portNumber;
Socket mySocket;
DataOutputStream outp;

listener= new ServerSocket(portNumber);
mySocket=listener.accept();
outp = new DataOutputStream(mySocket.getOutputStream());
outp.writeFloat(85.6f);

// C:

char buffer[4];
int num;
float flt;

num = read(sockfd, buffer, 4);
memcpy(&flt, buffer, 4);
printf("You returned %f from the server\n", flt);


I hope that was meant as a joke as it is the *WORST* way to do this. It is
non-portable. You are assuming that Java and C use the same binary
representation for their floating point values, which is a completely
unfounded assumption.

Using text is the most portable to transfer floating point values between
heterogenous environments. You can use the standard print conversion, but
that is actually not the most accurate way. It assumes that the receiving
end uses the same size of float. If I were writing float on the Java side
and double on the C side this would actually be introducing inaccuracy. The
standard conversion on the Java side is not the exact value of the float or
double, but the minimum number of digits that will be converted back to that
value using the same precision.

The most portable way to send the exact value is to do:

system.out.println( new BigDecimal( 85.6f ).toString() );
 
C

Chris Uppal

Dale said:
char buffer[4];
int num;
float flt;

num = read(sockfd, buffer, 4);
memcpy(&flt, buffer, 4);
printf("You returned %f from the server\n", flt);


I hope that was meant as a joke as it is the *WORST* way to do this. It is
non-portable. You are assuming that Java and C use the same binary
representation for their floating point values, which is a completely
unfounded assumption.

However the assumption /could/ be true in the OP's environment. Or more likely
some related assumption, such as that a float is represented in C memory as 4
bytes in IEEE little-endian format -- in which case something like the above
code but with an additional byte-swapping step would be correct.

The key is to do one of three things.

1) Create an interchange format which is designed to be portable and
which is text based -- as Dale suggests.

2) Create an interchange format which is deigned to be portable and
which is binary based -- in which case the spec for the format must
lay down the exact layout at the bits and bytes level. For instance
"the next four bytes are a 32-bit IEEE floating point
number in little-endian format"
Obviously your C and Java code will reflect the specification.

3) Create an interchange format which is /not/ designed to be portable
and which is binary based. In that case you have no real control
over when it stops working unless you control completely the
- machines it runs on
- compiler (make and version) the compiles it
- compiler options

(3) is obviously irresponsible, but there's no general reason to prefer (1)
over (2) or vice versa. Of course, if you /do/ choose to use (2) then there's
nothing to stop you choosing the format to be one that you can easily implement
for your current /actual/ machines. In which case you might easily end up with
an implementation which looked like the above code on the 'C' end of the link.

To the OP: If you are writing the data out using a java.io.DataOutputStream,
then you are implicitly defining a spec like (2). So you should read the
javadoc for DataOutputStream to understand what it puts on the net at the
byte-level (actually it will write a float as 4 bytes in big-endian order).
Then you should write your C code to parse that. If you are running on
ordinary Intel-like machinery then the layout of a C float in memory is
little-endian, so you could use code like the above, but you'd have to reverse
the bytes before converting them to a float.

-- chris
 
D

Dale King

Chris Uppal said:
Dale said:
char buffer[4];
int num;
float flt;

num = read(sockfd, buffer, 4);
memcpy(&flt, buffer, 4);
printf("You returned %f from the server\n", flt);


I hope that was meant as a joke as it is the *WORST* way to do this. It
is
non-portable. You are assuming that Java and C use the same binary
representation for their floating point values, which is a completely
unfounded assumption.

However the assumption /could/ be true in the OP's environment.

Which is basically what I said when I said it was non-portable. It will work
on some environments, but will not work in many others. It could break when
you changed machines or could even break with just a new version of the
compiler.
The key is to do one of three things.

1) Create an interchange format which is designed to be portable and
which is text based -- as Dale suggests.

2) Create an interchange format which is deigned to be portable and
which is binary based -- in which case the spec for the format must
lay down the exact layout at the bits and bytes level. For
instance
"the next four bytes are a 32-bit IEEE floating point
number in little-endian format"
Obviously your C and Java code will reflect the specification.

3) Create an interchange format which is /not/ designed to be portable
and which is binary based. In that case you have no real control
over when it stops working unless you control completely the
- machines it runs on
- compiler (make and version) the compiles it
- compiler options

(3) is obviously irresponsible,

which is why I said it is the worst way to do it.
but there's no general reason to prefer (1)
over (2) or vice versa. Of course, if you /do/ choose to use (2) then
there's
nothing to stop you choosing the format to be one that you can easily
implement
for your current /actual/ machines. In which case you might easily end up
with
an implementation which looked like the above code on the 'C' end of the
link.

I think there is a reason to prefer 1 over 2. If you chose 2 then you are
locking down how precise the number will be in the data format. What if we
later decide float is not precise enough and we want to use double on both
sides. Or perhaps we even go to BigDecimal. You then either have to throw
away that extra precision when talking between the two or change your
protocol. With choice1 it supports any arbitrary precision on either end.
You can certainly design a binary protocol (I don't actually find much
importance in the distinction between "text" and "binary") that allowed
variable precision but that would be a lot more work to implement.
 
?

=?ISO-8859-1?Q?Arne_Vajh=F8j?=

I hope that was meant as a joke as it is the *WORST* way to do this. It is
non-portable. You are assuming that Java and C use the same binary
representation for their floating point values, which is a completely
unfounded assumption.

Actually the Java code is not that bad.

It sends binary floats using standard IEEE floating
point format in standard network byte order.

Which is a very well defined format that almost
all non embedded computer will understand.

The problem is that the C code did not use ntohl.
The most portable way to send the exact value is to do:

system.out.println( new BigDecimal( 85.6f ).toString() );

That constructor variant is general considered
bad practice.

Arne
 
J

Jim Bancroft

Hi,
There are also Locale issues to consider. Does the float contain
commas, or decimals?

This partcular float contains decimals but no floats.

I've changed things a bit since I posted. Now I'm just trying to read an
integer, not a float. Still having troubles. Here's what I have on the
java side of the fence:

int myBalance;
DataOutputStream output;
//....
output.writeBytes(Integer.toString(myBalance));


In my C client I do this:

char mybuf[4];
int myInt;

bzero(mybuf, sizeof(mybuf));
read(sockfd,mybuf,sizeof(mybuf));
myInt = atoi(mybuf);

printf("my value is: %d\n", myInt);


....For some reason, when myBalance equals 85, I oscillate: sometimes I
print out "8585" and other times zero. I have no idea what's going on
there? Is java writing out the string with two bytes per character, while C
only interprets things as one byte per char? If so, is there a way to tell
java to use simple character encoding?
 
D

David Lee Lambert

I have a Java class that opens a socket and connects with a client. The
client is written in C.

All goes well, but when it comes time to write a floating point value the
client has trouble reading it. [...] /* Java code ... */
outp.println(85.6f); /* C code... */
memset(buffer,'\0',sizeof(buffer));
n = read(sockfd,buffer,127);
flt=atof(buffer);
printf("You returned %lf from the server\n",flt);

The printf statement shows a value of 0.0000 no matter what floating point
value I send via the java server. If I send an integer from the server, I
have no problems using atoi() and printing it out.

I wrote some code (at http://www.lmert.com/download/gh.zip) that exhibits
a similar, but not the same, problem. With my particular C library
(Debian, libc6 2.3.6-7) the atof() and strtod() functions both parse the
output of java.lang.Float.toString(), but the strtof() function chokes.

IEEE 1003.1:2001 (which is supposed to be aligned with the ISO C standard)
says nothing about a maximum length that strtod() and strtof() can handle,
and basically says that atof() should be defined in terms of strtod().
Therefore the error you're seeing is probably a problem with your C
library, not with Java.

On the other hand, if you're using a sufficiently recent version of java,
you could use outp.printf("%.8f", ...) instead. Another easy workaround
should be to manually truncate the string to an interesting length, in
either the client or the server or both.
 
?

=?ISO-8859-1?Q?Arne_Vajh=F8j?=

Jim said:
I've changed things a bit since I posted. Now I'm just trying to read an
integer, not a float. Still having troubles. Here's what I have on the
java side of the fence:

int myBalance;
DataOutputStream output;
//....
output.writeBytes(Integer.toString(myBalance));


In my C client I do this:

char mybuf[4];
int myInt;

bzero(mybuf, sizeof(mybuf));
read(sockfd,mybuf,sizeof(mybuf));
myInt = atoi(mybuf);

printf("my value is: %d\n", myInt);


...For some reason, when myBalance equals 85, I oscillate: sometimes I
print out "8585" and other times zero.

I am pretty sure that your problem is on the C side.

Try test on how many bytes read actually read and what
is the content of the bytes.

Arne
 
E

EJP

Jim said:
In my C client I do this:

char mybuf[4];
int myInt;

bzero(mybuf, sizeof(mybuf));
read(sockfd,mybuf,sizeof(mybuf));
myInt = atoi(mybuf);

atoi() needs a trailing zero and it hasn't got one here.
 
S

Simon Biber

David said:
I have a Java class that opens a socket and connects with a client. The
client is written in C.

All goes well, but when it comes time to write a floating point value the
client has trouble reading it. [...] /* Java code ... */
outp.println(85.6f); /* C code... */
memset(buffer,'\0',sizeof(buffer));
n = read(sockfd,buffer,127);

println probably wrote ASCII text such as "85.6\n" to the socket.

Why are you reading 127 characters from the socket? You should read one
character at a time, until your reach the newline.

The first time you read 127 characters you probably included some of the
following data in what you read, which meant it was no longer available
next time you tried to read something.
 
M

Martin Gregorie

Simon said:
Why are you reading 127 characters from the socket? You should read one
character at a time, until your reach the newline.
I prefer to use structured messages with a count as the first value,
something like "n,value" in this case, where 'n' is a fixed length
number, e.g. four digits zero filled on the left, that gives the length
of the "value" string + 1 for the comma.

This makes reading the message simple:

read 4 bytes (the length of the rest of the message)
convert to binary (lth)
read lth bytes into a value buffer
append a null character as the string terminator
skip the first byte (the comma) and the rest of the string is the value
that was sent.

You can transfer any combination of bytes with this method (including
binary values and UTF-16 encoded byte strings) but IMO you're better off
sending a plain ASCII string because:

- the ability to display the whole message without decoding it
makes debugging a lot easier
- this allows the value to be retrieved with a single read (lower
overhead than byte by byte reading)
- using formatted messages of this sort enables you to spot corrupted
data and do something about it: in this case you could check that the
comma is the first byte after the length.
- by sending an ASCII byte string you avoid big endian / little endian
conflicts between hosts and other binary traps
- messages formatted this way are easy to assemble and parse in both
Java and C

You must check that you've read the required number of bytes in case the
message got split during transmission. Remember that TCP/IP can split
messages up if buffer sizes differ along the route and that a split
message isn't necessarily recombined at the destination host.
 
?

=?ISO-8859-1?Q?Arne_Vajh=F8j?=

EJP said:
Jim said:
In my C client I do this:

char mybuf[4];
int myInt;

bzero(mybuf, sizeof(mybuf));
read(sockfd,mybuf,sizeof(mybuf));
myInt = atoi(mybuf);

atoi() needs a trailing zero and it hasn't got one here.

Depends on how many bytes read actually reads.

If it reads 4 bytes then it does not have a trailing
nul byte.

Arne
 
C

Chris Uppal

Dale King wrote:

[me:]
but there's no general reason to prefer (1)
over (2) or vice versa. [...]

I think there is a reason to prefer 1 over 2. If you chose 2 then you are
locking down how precise the number will be in the data format.

Exactly the same thing happens with a textual representation. If one end of
the communications link recieves a floating point value represented with more
digits of precision than is supported by its own floating-point representation,
then what is it to do ? In general the only valid response would be to signal
some kind of error (though there may be certain /specific/ applications where
it would be understood that rounding/truncation is acceptable). If you are
communicating numbers between computers then there is, explicitly or
implicitly, a fixed /range/ associated with each. If the numbers are
floating-point then there is also, explicitly or implicitly, a fixed precision
associated. Any protocol which fails to recognise that is broken.

-- chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,044
Latest member
RonaldNen

Latest Threads

Top