Using Java.net Socket

Hey all,
I'm trying to use the java sockets for some TCP devices.

We're using the below code, our devices have a sort of API where they expect a byte array to be written.

from java.net import Socket as javaSocket
......

skt = javaSocket(ip_addr, port) #Java Socket declare, see imports at the top of module
skt.setSoTimeout(CONNECTION_TIMEOUT) #set timeout
	
out_stream = skt.getOutputStream() # set socket send data stream

out_stream.write(b_msg) # write data
out_stream.flush() # flush data down socket
	
....

In this section of the code, the skt.getOutputStream().write(...) keeps erroring saying it needs an integer, but the Java docs for this function says it can be overloaded with a byte array which our variable b_msg is. If I convert to an integer it doesn't work either.

How have people gotten these sockets to work on their system, I'm struggling to find documentation on the Jython for these IO functions

I'm not super familiar with Java and how it interacts with Jython, but the first thing that comes to my mind when reading this is that there might be a mismatch between jython's byte array (the one you're passing) and the one Java expects (failed conversion from one to the other maybe ?).
Getting a type it's not expecting, the call is dispatched to the integer version because it's the default, which is why you get an error saying it expects an int.

I might be entirely wrong about all this, but it's worth making sure that the types do match.

1 Like

I wonder what could be different between the data types, I've imported the 'Java Concurrent Lists' before for other systems, so I'll see if we can import some kind of "Java Byte Array Type" and try to put it in there.

It does make sense that it would return that default integer not found when the overriden function gets an exception.

Jython's jarray might be of use.

import jarray

b_msg = jarray.array(b_msg, 'b')
2 Likes

I'll look into using that then, getting errors that the value is too large, as we're encoding a JSON into a byte array before sending

e.g.

 b_msg = bytearray(raw_msg)
 if msg != {}:
     b_content = bytearray(json_str,'ascii')
     b_msg.extend(b_content)
etc....

b_msg = jarray.array(b_msg, 'b')
etc....

Maybe I need to use the jarray in this earlier declaration rather than type convert later? And create a Jarray of Jarray's?

Ah. Then this may be better for you.

from org.python.core.util import StringUtil

b_msg = StringUtils.toBytes(raw_msg)

EDIT: or more likely, since I don't know what all is going into this; :wink:

b_msg = StringUtils.toBytes(json_str)
1 Like

Last I looked, jython doesn't reliably handle overloads that have the same number of arguments. You probably will need to use reflection to pick out the correct method and call it indirectly. But try Jordan's suggestion first.

2 Likes

I'll have a test with this, and see how it works out then. I think we basically are uploading a JSON into bytes, but have some preamble bytes to tell the machine what the command is.

We previously used Python Sockets, but I'm needing to convert them over to Java modules.

I'll play with converting the JSON into bytes and appending that to a byte array of those premabled bytes and see if the out_stream accepts it

1 Like

Still had some issues with the same error, though the too large value error was fixed. So I had a mess with reflection where

from java.net import Socket as javaSocket

skt = javaSocket('8.8.8.8', 443) #Java Socket declare, see imports at the top of module

out_stream = skt.getOutputStream() # set socket send data stream

for i in out_stream.getClass().getMethods():
	if str(i).endswith('(byte[]) throws java.io.IOException') and i.getName() == 'write':
		print i
		print i.getName()
		i.invoke(?, ?)
skt.close()

In this instance I'm not sure what I should put as the instance name, is it anything really or should it be the out_stream variable?
I was able to see my overloaded method that I need public void java.net.SocketOutputStream.write(byte[]) throws java.io.IOException
But now not sure how to actually invoke it in a Jython environment.

I'm... not sure about this, but maybe it's worth a try:

write_func = next(m for m in out_stream.class.methods if m.name == "write" and m.parameterCount == 1 and not m.isDefault())

write_func.setAccessible(True)
write_func.invoke(out_stream, your_byte_array)

There's probably a better way to target the function, maybe based on parameter type, but java types are.... nnnggg
Or just use out_stream.class.methods[0], they should always be in the same order. I think.

When trying to access the function using the

I get a nice new error:
org.python.core.PyReflectedFunction cannot access a member of class java.net.SocketOutputStream (in module java.base) with modifiers "public"

Seeing as the Method I need is public void java.net.SocketOutputStream.write(byte[]... so a public method, does that mean this whole method is not accessible with reflection at all? Or is using the out_stream an incorrect instancing for this method?

Interestinly this also just ignored my try: except: in my script and had the exception throw like I had no error handling, rather interesting.

Did you use except Exception ?
This is likely going to throw a java error, not a python error. You'll need to catch this instead, either by importing the exact error (java.io.IOException) or using a general one (from java.lang import Throwable).

You should not be iterating through all of the methods. Simply request the correct method with .getMethod(java.lang.String,java.lang.Class...). It takes the string name of the method and additional parameters matching the data types of the desired overload. Get the type() of an empty jarray of bytes to get byte[] to supply to the function.

That's what suprised me, I tend to use except java.io.IOException as e: but it seems to be another error I guess. I'll add a generic except to the catch any outliers, wanting my finally: to fire to prevent any leaking sockets.

I tried adding the below code as a test

b_msg = jarray.zeros(5, 'b')
print type(b_msg)
writeMethod = out_stream.getClass().getMethod('write',type(b_msg))
print writeMethod

When I use the above code to get the type I always get <type 'array.array'> am I using the Jarray incorrectly?

Seems whatever I put into the string name it errors saying that the method doesn't exist, when I use the i.getName() from my above loop, or if I use any part of that name. What kind of naming do I need to use for the string, or is the datatype throwing it. I tested using an int datatype as well but same issue

I've been doing some testing and resolved the issue of the invoke. I found an equivalent function which takes the length and starting bit of write bytes, so I don't need to worry about this. So out_stream.write(b_msg,0,len(b_msg)) # Write Data

However I continue to have issues with the Jarray.array to get my data into this java byte array format. In general I use the below script. Where:

def someFunc(...):
    raw_msg = struct.pack(some_packed data)
    b_msg = bytearray(raw_msg)
  
    if msg != {}:
        b_content = StringUtil.toBytes(json_str)
        b_msg.extend(b_content)
		
    return b_msg

skt = javaSocket(ip_addr, port) #Java Socket declare, see imports at the top of module
	
skt.setSoTimeout(CONNECTION_TIMEOUT) #set timeout
	
out_stream = skt.getOutputStream() # set socket send data stream
	
b_msg = jarray.array(b_msg,'b') # Create Java array object for data transmission
...

I continue to get an error with the jarray.array of the error OverflowError: value too large for byte at that line, when I print the byte value I get a random two characters, and the len(b_msg) is 16. It should just be an array of bytes, so why is this occuring?

When constructing bytes with jarray, each byte must be in the range -128..127, not 0..255. Bytes are signed in java.

ah right, we're using the struct package to make this object, so I wonder if there's some formatting we can do in there before putting in the jarray, or is it as simple as:

for i in myList:
     i = int(i) - 128 # shift my byte
jarray.array(mylist, 'b')

No, shifting the values by 128 is wrong. You want to leave 0..127 alone, and subtract 256 from -128..-1. I typically use ((i + 128) % 256) - 128.

1 Like

i usually use this for error handling, this worked for me when cathcing some exotic java errors

from java.lang import Exception as JavaExpection
try:
  ...
except (JavaExpection, Exception), e: