Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.net.Socket implementation #97

Closed
wants to merge 8 commits into from

Conversation

bigfatbrowncat
Copy link
Contributor

I created a basic clean-room implementation of java.net.Socket class (and improved some connected additional classes).

The implementation doesn't contain all the methods of the original one, but it is sufficient to connect to a remote server and send/receive data.

I tested it by connecting to an HTTP-server under Windows 7 and OS X 10.8. It works satisfactory.

As for now only TCP-socket is implemented for IPv4 addresses.

If you accept this pull-request I'll try to resolve all possible bugs connected to it. And in addition I'm going to implement ServerSocket soon.

@joshuawarner32
Copy link
Collaborator

@bigfatbrowncat , could you take a look at the travis build failures?

https://travis-ci.org/ReadyTalk/avian/builds/13552347

private final int port;

public InetSocketAddress(String host, int port) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you should not remove that constructor, but replace its body with something like

this(new InetAddress(host), port);

According to Travis, the presence of this constructor is required by this line. It is also in the official Java API...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@dscho
Copy link
Contributor

dscho commented Nov 6, 2013

Nice work, overall!

@bigfatbrowncat
Copy link
Contributor Author

Thank you for your attention. I'll fix that.

Strange, but it has builded on my site…

@bigfatbrowncat
Copy link
Contributor Author

I've fixed all the problems, now it builds well.

@dscho
Copy link
Contributor

dscho commented Nov 6, 2013

@bigfatbrowncat you probably built with make while Travis builds with make test? Although Traces.java should be built in both cases...

@joshuawarner32
Copy link
Collaborator

@bigfatbrowncat, could you squash the existing commits, and then add a test along the lines of SendFile and/or SendServer?

@bigfatbrowncat
Copy link
Contributor Author

Okay, I'll try to squash the commits.

I'm working on ServerSocket now. After that I'll make a test code - send a file from client Socket to a ServerSocket on localhost. Would it be a proper test?

Now it works on OS X

Small improvements

Added bufferes I/O

Small fixes

Added getHostName() function
@bigfatbrowncat bigfatbrowncat deleted the sockets branch November 6, 2013 18:17
@bigfatbrowncat bigfatbrowncat restored the sockets branch November 6, 2013 18:18
@bigfatbrowncat bigfatbrowncat deleted the sockets branch November 6, 2013 18:22
@bigfatbrowncat
Copy link
Contributor Author

I'm sorry for creating a mess, that was the first time I squashed commits.

I will recreate a new pull request with the new branch.

@bigfatbrowncat bigfatbrowncat reopened this Nov 6, 2013
}

extern "C" JNIEXPORT char* JNICALL
Java_java_net_Socket_allocateBuffer(JNIEnv*, jclass, int buf_size) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made specific methods for using with socket streams. The advantage of implementing everything necessary right here is good encapsulation. We don't call an outer class for a trivial operation. Do you really think we should depend on Unsafe here?

Which disadvantages do you see here (except of duplication of a trivial function)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @dscho for a number of reasons:

  • I'd rather avoid allocating memory not managed by the VM in a non-local fashion
  • The approach pointed out below (reading directly into the byte buffer) should be more performant (and not require any native allocations)
  • Java methods can easily be stripped out at build time by proguard, but there's no such tool for jni methods - so I'd like to minimize the latter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Ok, I will use Unsafe if you insist (you are the boss, after all ;) ).
  • About reading directly to a buffer - this is not JNI-style, it's Avian-specific. I just haven't known about it yet. Could you tell me more about using it? I could dig by myself, of course, but it would be great if you explain which functions/methods to use and I'll use them.
  • I understand your position well. I'll try to minimize the native code as tight as I could.

By the way (just an idea), there is method which could help you to shrink native binary. You could divide the default library into some parts (java classes + JNI code) and turn them on and off during compilation. For example if I don't want networking I would exclude the whole java.net package with java-net.cpp.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, the best solution is if you can avoid Unsafe completely ( - but there are lots of competing ideals, and we're unlikely to optimize them all.

(you are the boss, after all)

Technically, I'm just filling in for a while... :)

Could you tell me more about using it?

Sure. Avian supports two types of external functions: Java_ methods (jni) and Avian_ methods (avian-specific, referred to as the "fast" path in the code). Java_ methods should be compatible with every other jvm implementation (if they're not, it's a bug). They take arguments as normal C arguments. Avian_ methods take arguments in a single uintptr_t* array - which allows us to bypass a lot of platform-specific native code. The first argument will be at index 0 in that array, and so on.

The key with Avian_ methods in this context is that there's the unwritten assumption that, since they're already using avian-specific code, they might as well dig further into the internals. That gives us license to use byteArrayBody, an avian-specific utility method for retrieving a reference to an element in a Java-level byte array. Thus, we can ask for the zeroth element and take the address to yield a pointer directly to the buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you propose to implement send and recv as Avian_ native methods and read/write directly to the buffer?

Ok, I'll try to do that.

@bigfatbrowncat
Copy link
Contributor Author

Here is the new version.

  1. Native memory allocation for buffer removed and buffer-aware functions changed to Avian_ with the usage of internal API.
  2. Moved the socket API to a different file socket.cpp which can be used from different classes (for example, I'm going to make Socket.init() private in order to hide it from the user, so there should be a universal Socket API for, e.g., java.nio.
  3. Added a unit-test which simply requests www.google.com/. Unfortunately, there is no ServerSocket there (I'm going to add it), so we can't just connect one socket to another and check if data transferred correctly. But you could check that the response from Google site is correct.

Do you have any other comments?

@joshuawarner32
Copy link
Collaborator

So... I mentioned a " test along the lines of SendFile and/or SendServer" without actually having read over that code. Oops.

In particular, they live in the test/extra directory and don't actually get executed with the other tests. They must be manually executed. On the other hand, they also don't make any requests out of the local machine (well, unless you tell them to on the command line) - which I think is important for unit tests (even though they're not really unit tests).

If possible, I think the best test would be one that spins up an extra thread and sends a short string between that thread and the main one via your new socket implementation.

Also, see my comment(s) on the travis build failures.

@bigfatbrowncat
Copy link
Contributor Author

I have a problem. I tried to build this code under OS X (yes, you were right, I had to write reinterpret_cast), but it wouldn't compile.

The short story: I can't use "jni.h" and "avian/machine.h" included in the same file. They are conflicting.

But I need both. If I don't include the second, I can't use vm::byteArrayBody function. If I don't include the first, I can't call e->GetStringUTFChars(…) - there is just no such function in class Thread - it should be cast to JNIEnv in order to use this function.

I have some hacking ideas about how to make them friends, but I want to know the "official" way. Could you propose me how should I do?

@dscho
Copy link
Contributor

dscho commented Nov 8, 2013

@bigfatbrowncat are you sure you need to call GetStringUTFChars()? If you have a look here you will see how I implemented a native method accepting a String on the Java side and operating on an object using the stringLength() and stringChars() functions on the C++ side. (Hint: look for the native nextResourceURLPrefix() method.)

@joshuawarner32
Copy link
Collaborator

@bigfatbrowncat , I agree with @dscho that stringLength() and stringChars() is probably the way to go - but on a whim, I removed the incompatibility between jni.h and machine.h that you noted in 3c1afdd.

@bigfatbrowncat
Copy link
Contributor Author

I agree too (about stringLength() and stringChars()), but I think that changing it along with my modifications is too much for now. I haven't written the method which uses it and I don't want to break it accidentally. It works now. Let me leave it as it is, please.

I merged all the latest commits into my fork and fixed bugs. Could you look at the most recent version, please?

@bigfatbrowncat
Copy link
Contributor Author

Hmm… strange build error. I don't have such a bug under OS X. I'll check it under Win7...

@bigfatbrowncat
Copy link
Contributor Author

I found this bug in Win7 and fixed it. It's just a piece of C linker magic. avian/machine.h should be included before jni.h.

@bigfatbrowncat
Copy link
Contributor Author

Hmm... now it builds under Win7, but still doesn't build in Travis. I don't have a linux machine here. Could you check?

@joshuawarner32
Copy link
Collaborator

@bigfatbrowncat I removed another incompatibility between jni.h and avian's common.h in 2800ffe. That seems to fix the linux build. Could you pull and verify on your end?

@bigfatbrowncat
Copy link
Contributor Author

It builds on OS X and under Win7 x64. And all the tests work like a charm. Finally!

@joshuawarner32
Copy link
Collaborator

I've squashed the commits in this PR and merged them: 45ee25f
I've also modified the Sockets test a little: c3bbe55

Thanks, @bigfatbrowncat !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants