From 45ee25f68cc191a934be63675d06047969c42fc5 Mon Sep 17 00:00:00 2001 From: Ilya Mizus Date: Tue, 5 Nov 2013 00:07:43 +0300 Subject: [PATCH] Implement socket API --- classpath/java-net.cpp | 90 ++++++--- classpath/java/lang/AutoCloseable.java | 15 ++ classpath/java/lang/Throwable.java | 3 + classpath/java/net/InetAddress.java | 62 ++++-- classpath/java/net/InetSocketAddress.java | 17 +- classpath/java/net/Socket.java | 187 +++++++++++++++++- .../java/nio/channels/SocketChannel.java | 12 +- classpath/sockets.cpp | 184 +++++++++++++++++ classpath/sockets.h | 72 +++++++ test/Sockets.java | 37 ++++ 10 files changed, 623 insertions(+), 56 deletions(-) create mode 100644 classpath/java/lang/AutoCloseable.java create mode 100644 classpath/sockets.cpp create mode 100644 classpath/sockets.h create mode 100644 test/Sockets.java diff --git a/classpath/java-net.cpp b/classpath/java-net.cpp index c03f6dc771..b92ed16935 100644 --- a/classpath/java-net.cpp +++ b/classpath/java-net.cpp @@ -9,33 +9,70 @@ details. */ #include "jni.h" -#include "jni-util.h" +#include "avian/machine.h" -#ifdef PLATFORM_WINDOWS -# include -# define ONLY_ON_WINDOWS(x) x -#else -# include -# include -# include -# define ONLY_ON_WINDOWS(x) -#endif +#include "sockets.h" + +using namespace avian::classpath::sockets; extern "C" JNIEXPORT void JNICALL -Java_java_net_Socket_init(JNIEnv* ONLY_ON_WINDOWS(e), jclass) -{ -#ifdef PLATFORM_WINDOWS - static bool wsaInitialized = false; - if (not wsaInitialized) { - WSADATA data; - int r = WSAStartup(MAKEWORD(2, 2), &data); - if (r or LOBYTE(data.wVersion) != 2 or HIBYTE(data.wVersion) != 2) { - throwNew(e, "java/io/IOException", "WSAStartup failed"); - } else { - wsaInitialized = true; - } - } -#endif +Java_java_net_Socket_init(JNIEnv* e, jclass) { + init(e); +} + +extern "C" JNIEXPORT SOCKET JNICALL +Java_java_net_Socket_create(JNIEnv* e, jclass) { + return create(e); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_connect(JNIEnv* e, jclass, SOCKET sock, long addr, short port) { + connect(e, sock, addr, port); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_bind(JNIEnv* e, jclass, SOCKET sock, long addr, short port) { + bind(e, sock, addr, port); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_abort(JNIEnv* e, jclass, SOCKET sock) { + abort(e, sock); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_close(JNIEnv* e, jclass, SOCKET sock) { + close(e, sock); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_closeOutput(JNIEnv* e, jclass, SOCKET sock) { + close_output(e, sock); +} + +extern "C" JNIEXPORT void JNICALL +Java_java_net_Socket_closeInput(JNIEnv* e, jclass, SOCKET sock) { + close_input(e, sock); +} + +extern "C" JNIEXPORT void JNICALL +Avian_java_net_Socket_send(vm::Thread* t, vm::object, uintptr_t* arguments) { /* SOCKET s, object buffer_obj, int start_pos, int count */ + SOCKET& s = *(reinterpret_cast(&arguments[0])); + vm::object buffer_obj = reinterpret_cast(arguments[2]); + int32_t& start_pos = *(reinterpret_cast(&arguments[3])); + int32_t& count = *(reinterpret_cast(&arguments[4])); + char* buffer = reinterpret_cast(&vm::byteArrayBody(t, buffer_obj, start_pos)); + avian::classpath::sockets::send((JNIEnv*)t, s, buffer, count); +} + +extern "C" JNIEXPORT int64_t JNICALL +Avian_java_net_Socket_recv(vm::Thread* t, vm::object, uintptr_t* arguments) { /* SOCKET s, object buffer_obj, int start_pos, int count */ + SOCKET& s = *(reinterpret_cast(&arguments[0])); + vm::object buffer_obj = reinterpret_cast(arguments[2]); + int32_t& start_pos = *(reinterpret_cast(&arguments[3])); + int32_t& count = *(reinterpret_cast(&arguments[4])); + char* buffer = reinterpret_cast(&vm::byteArrayBody(t, buffer_obj, start_pos)); + return avian::classpath::sockets::recv((JNIEnv*)t, s, buffer, count); } extern "C" JNIEXPORT jint JNICALL @@ -49,7 +86,7 @@ Java_java_net_InetAddress_ipv4AddressForName(JNIEnv* e, hostent* host = gethostbyname(chars); e->ReleaseStringUTFChars(name, chars); if (host) { - return htonl(reinterpret_cast(host->h_addr_list[0])->s_addr); + return ntohl(reinterpret_cast(host->h_addr_list[0])->s_addr); } else { fprintf(stderr, "trouble %d\n", WSAGetLastError()); } @@ -67,7 +104,7 @@ Java_java_net_InetAddress_ipv4AddressForName(JNIEnv* e, if (r != 0) { address = 0; } else { - address = htonl + address = ntohl (reinterpret_cast(result->ai_addr)->sin_addr.s_addr); freeaddrinfo(result); @@ -78,3 +115,4 @@ Java_java_net_InetAddress_ipv4AddressForName(JNIEnv* e, } return 0; } + diff --git a/classpath/java/lang/AutoCloseable.java b/classpath/java/lang/AutoCloseable.java new file mode 100644 index 0000000000..b3785fb21f --- /dev/null +++ b/classpath/java/lang/AutoCloseable.java @@ -0,0 +1,15 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.lang; + +public interface AutoCloseable { + void close() throws Exception; +} diff --git a/classpath/java/lang/Throwable.java b/classpath/java/lang/Throwable.java index e25abc126f..887e3bb888 100644 --- a/classpath/java/lang/Throwable.java +++ b/classpath/java/lang/Throwable.java @@ -123,4 +123,7 @@ public class Throwable implements Serializable { trace = trace(0); return this; } + + public void addSuppressed(Throwable exception) { + } } diff --git a/classpath/java/net/InetAddress.java b/classpath/java/net/InetAddress.java index affd095752..d0030d0469 100644 --- a/classpath/java/net/InetAddress.java +++ b/classpath/java/net/InetAddress.java @@ -13,41 +13,61 @@ package java.net; import java.io.IOException; public class InetAddress { - private final String address; + private final String name; + private final int ip; - private InetAddress(String address) { - this.address = address; + private InetAddress(String name) throws UnknownHostException { + this.name = name; + this.ip = ipv4AddressForName(name); + if (ip == 0) { + throw new UnknownHostException(name); + } } + public String getHostName() { + return name; + } + public String getHostAddress() { - return address; + try { + return new InetAddress(name).toString(); + } catch (UnknownHostException e) { + return null; // Strange case + } } - public static InetAddress getByName(String name) - throws UnknownHostException - { + public static InetAddress getByName(String name) throws UnknownHostException { try { Socket.init(); + return new InetAddress(name); } catch (IOException e) { UnknownHostException uhe = new UnknownHostException(name); uhe.initCause(e); throw uhe; } - - int address = ipv4AddressForName(name); - if (address == 0) { - throw new UnknownHostException(name); - } else { - return new InetAddress(ipv4AddressToString(address)); - } } - private static String ipv4AddressToString(int address) { - return (((address >>> 24) ) + "." + - ((address >>> 16) & 0xFF) + "." + - ((address >>> 8 ) & 0xFF) + "." + - ((address ) & 0xFF)); + public byte[] getAddress() { + byte[] res = new byte[4]; + res[0] = (byte) ( ip >>> 24); + res[1] = (byte) ((ip >>> 16) & 0xFF); + res[2] = (byte) ((ip >>> 8 ) & 0xFF); + res[3] = (byte) ((ip ) & 0xFF); + return res; } - - private static native int ipv4AddressForName(String name); + + @Override + public String toString() { + byte[] addr = getAddress(); + return (int)((addr[0] + 256) % 256) + "." + + (int)((addr[1] + 256) % 256) + "." + + (int)((addr[2] + 256) % 256) + "." + + (int)((addr[3] + 256) % 256); + } + + int getRawAddress() { + return ip; + } + + static native int ipv4AddressForName(String name); } diff --git a/classpath/java/net/InetSocketAddress.java b/classpath/java/net/InetSocketAddress.java index b5d83825b0..ea8151aa1e 100644 --- a/classpath/java/net/InetSocketAddress.java +++ b/classpath/java/net/InetSocketAddress.java @@ -11,16 +11,25 @@ package java.net; public class InetSocketAddress extends SocketAddress { - private final String host; + private final InetAddress address; private final int port; - public InetSocketAddress(String host, int port) { - this.host = host; + public InetSocketAddress(String host, int port) throws UnknownHostException { + this.address = InetAddress.getByName(host); + this.port = port; + } + + public InetSocketAddress(InetAddress address, int port) { + this.address = address; this.port = port; } + public InetAddress getAddress() { + return address; + } + public String getHostName() { - return host; + return address.getHostName(); } public int getPort() { diff --git a/classpath/java/net/Socket.java b/classpath/java/net/Socket.java index e8fe3d5423..166add6dca 100644 --- a/classpath/java/net/Socket.java +++ b/classpath/java/net/Socket.java @@ -10,10 +10,191 @@ package java.net; +import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; -public abstract class Socket { - public static native void init() throws IOException; +public class Socket implements Closeable, AutoCloseable { - public abstract void setTcpNoDelay(boolean on) throws SocketException; + private static final int SD_RECEIVE = 0x00; + private static final int SD_SEND = 0x01; + private static final int SD_BOTH = 0x02; + + private static final int BUFFER_SIZE = 65535; + + /** + * This method is called from all routines that depend on winsock in windows, + * so it has public visibility + * @throws IOException + */ + public static native void init() throws IOException; + + /** + * Creates the native socket object + * @return Handle to the native object + * @throws IOException + */ + private static native /* SOCKET */long create() throws IOException; + + /** + * Connects the native socket object to an address:port + * @param sock Native socket handler + * @param addr Address to connect to + * @param port Port to connect to + * @throws IOException + */ + private static native void connect(/* SOCKET */long sock, long addr, short port) throws IOException; + private static native void bind(/* SOCKET */long sock, long addr, short port) throws IOException; + + private static native void send(/* SOCKET */long sock, byte[] buffer, int start_pos, int count) throws IOException; + private static native int recv(/* SOCKET */long sock, byte[] buffer, int start_pos, int count) throws IOException; + + private static native void abort(/* SOCKET */long sock); + private static native void close(/* SOCKET */long sock); + private static native void closeOutput(/* SOCKET */long sock); + private static native void closeInput(/* SOCKET */long sock); + + private class SocketInputStream extends InputStream { + + private boolean closed = false; + + @Override + public void close() throws IOException { + if (!closed) { + closeInput(sock); + closed = true; + } + super.close(); + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + @Override + public int read() throws IOException { + byte[] buffer = new byte[1]; + int size = recv(sock, buffer, 0, 1); + if (size == 0) { + return -1; + } + return buffer[0]; + } + + @Override + public int read(byte[] buffer) throws IOException { + int fullSize = buffer.length; + int index = 0; + int size; + do { + size = recv(sock, buffer, index, Math.min(fullSize, Socket.BUFFER_SIZE)); + fullSize -= size; + index += size; + } while (fullSize != 0 && size != 0); + return index; + } + + + } + + private class SocketOutputStream extends OutputStream { + + private boolean closed = false; + + @Override + public void close() throws IOException { + if (!closed) { + closeOutput(sock); + closed = true; + } + super.close(); + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + @Override + public void write(int c) throws IOException { + byte[] res = new byte[1]; + res[0] = (byte)c; + send(sock, res, 0, 1); + } + + @Override + public void write(byte[] buffer) throws IOException { + int fullSize = buffer.length; + int index = 0; + int size; + do { + size = Math.min(fullSize, Socket.BUFFER_SIZE); + send(sock, buffer, index, size); + fullSize -= size; + index += size; + } while (fullSize != 0 && size != 0); + } + + } + + private long sock; + private SocketInputStream inputStream; + private SocketOutputStream outputStream; + + public Socket() throws IOException { + Socket.init(); + sock = create(); + inputStream = new SocketInputStream(); + outputStream = new SocketOutputStream(); + } + + public SocketInputStream getInputStream() { + return inputStream; + } + + public SocketOutputStream getOutputStream() { + return outputStream; + } + + public Socket(InetAddress address, int port) throws IOException { + this(); + connect(sock, address.getRawAddress(), (short)port); + } + + public Socket(String host, int port) throws UnknownHostException, IOException { + this(InetAddress.getByName(host), port); + } + + public void bind(SocketAddress bindpoint) throws IOException { + if (bindpoint instanceof InetSocketAddress) { + InetSocketAddress inetBindpoint = (InetSocketAddress)bindpoint; + bind(sock, inetBindpoint.getAddress().getRawAddress(), (short) inetBindpoint.getPort()); + } + } + + public void setTcpNoDelay(boolean on) throws SocketException {} + + @Override + public void close() throws IOException { + close(sock); + } + + public void shutdownInput() throws IOException { + inputStream.close(); + } + + public void shutdownOutput() throws IOException { + outputStream.close(); + } + + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } } diff --git a/classpath/java/nio/channels/SocketChannel.java b/classpath/java/nio/channels/SocketChannel.java index 2e9cabaf93..966a4f0c57 100644 --- a/classpath/java/nio/channels/SocketChannel.java +++ b/classpath/java/nio/channels/SocketChannel.java @@ -50,7 +50,11 @@ public class SocketChannel extends SelectableChannel } public Socket socket() { - return new Handle(); + try { + return new Handle(); + } catch (IOException e) { + return null; + } } public boolean connect(SocketAddress address) throws IOException { @@ -165,7 +169,11 @@ public class SocketChannel extends SelectableChannel } public class Handle extends Socket { - public void setTcpNoDelay(boolean on) throws SocketException { + public Handle() throws IOException { + super(); + } + + public void setTcpNoDelay(boolean on) throws SocketException { natSetTcpNoDelay(socket, on); } } diff --git a/classpath/sockets.cpp b/classpath/sockets.cpp new file mode 100644 index 0000000000..f3cbc8e4a2 --- /dev/null +++ b/classpath/sockets.cpp @@ -0,0 +1,184 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +/* + * This file implements a simple cross-platform JNI sockets API + * It is used from different classes of the default Avian classpath + */ + +#include "sockets.h" + +namespace avian { +namespace classpath { +namespace sockets { + +int last_socket_error() { +#ifdef PLATFORM_WINDOWS + int error = WSAGetLastError(); +#else + int error = errno; +#endif + return error; +} + + +void init(JNIEnv* ONLY_ON_WINDOWS(e)) { +#ifdef PLATFORM_WINDOWS + static bool wsaInitialized = false; + if (not wsaInitialized) { + WSADATA data; + int r = WSAStartup(MAKEWORD(2, 2), &data); + if (r or LOBYTE(data.wVersion) != 2 or HIBYTE(data.wVersion) != 2) { + throwNew(e, "java/io/IOException", "WSAStartup failed"); + } else { + wsaInitialized = true; + } + } +#endif +} + +SOCKET create(JNIEnv* e) { + SOCKET sock; + if (INVALID_SOCKET == (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) { + char buf[255]; + sprintf(buf, "Can't create a socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return 0; // This doesn't matter cause we have risen an exception + } + return sock; +} + +void connect(JNIEnv* e, SOCKET sock, long addr, short port) { + sockaddr_in adr; + adr.sin_family = AF_INET; +#ifdef PLATFORM_WINDOWS + adr.sin_addr.S_un.S_addr = htonl(addr); +#else + adr.sin_addr.s_addr = htonl(addr); +#endif + adr.sin_port = htons (port); + + if (SOCKET_ERROR == ::connect(sock, (sockaddr* )&adr, sizeof(adr))) + { + char buf[255]; + sprintf(buf, "Can't connect a socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return; + } +} + +void bind(JNIEnv* e, SOCKET sock, long addr, short port) { + sockaddr_in adr; + adr.sin_family = AF_INET; +#ifdef PLATFORM_WINDOWS + adr.sin_addr.S_un.S_addr = htonl(addr); +#else + adr.sin_addr.s_addr = htonl(addr); +#endif + adr.sin_port = htons (port); + + if (SOCKET_ERROR == ::bind(sock, (sockaddr* )&adr, sizeof(adr))) + { + char buf[255]; + sprintf(buf, "Can't bind a socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return; + } +} + +SOCKET accept(JNIEnv* e, SOCKET sock, long* client_addr, short* client_port) { + sockaddr_in adr; + SOCKET client_socket = ::accept(sock, (sockaddr* )&adr, NULL); + if (INVALID_SOCKET == client_socket) { + char buf[255]; + sprintf(buf, "Can't accept the incoming connection. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return INVALID_SOCKET; + } + + if (client_addr != NULL) { + #ifdef PLATFORM_WINDOWS + *client_addr = ntohl(adr.sin_addr.S_un.S_addr); + #else + *client_addr = ntohl(adr.sin_addr.s_addr); + #endif + } + + if (client_port != NULL) { + *client_port = ntohs (adr.sin_port); + } + + return client_socket; +} + +void send(JNIEnv* e, SOCKET sock, const char* buff_ptr, int buff_size) { + if (SOCKET_ERROR == ::send(sock, buff_ptr, buff_size, 0)) { + char buf[255]; + sprintf(buf, "Can't send data through the socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return; + } +} + +int recv(JNIEnv* e, SOCKET sock, char* buff_ptr, int buff_size) { + int length = ::recv(sock, buff_ptr, buff_size, 0); + if (SOCKET_ERROR == length) { + char buf[255]; + sprintf(buf, "Can't receive data through the socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + return 0; // This doesn't matter cause we have risen an exception + } + return length; +} + +void abort(JNIEnv* e, SOCKET sock) { + if (SOCKET_ERROR == ::closesocket(sock)) { + char buf[255]; + sprintf(buf, "Can't close the socket. System error: %d", last_socket_error()); + throwNew(e, "java/io/IOException", buf); + } +} + +void close(JNIEnv* e, SOCKET sock) { + if (SOCKET_ERROR == ::shutdown(sock, SD_BOTH)) { + int errcode = last_socket_error(); + if (errcode != ENOTCONN) { + char buf[255]; + sprintf(buf, "Can't shutdown the socket. System error: %d", errcode); + throwNew(e, "java/io/IOException", buf); + } + } +} + +void close_input(JNIEnv* e, SOCKET sock) { + if (SOCKET_ERROR == ::shutdown(sock, SD_RECEIVE)) { + int errcode = last_socket_error(); + if (errcode != ENOTCONN) { + char buf[255]; + sprintf(buf, "Can't shutdown the socket. System error: %d", errcode); + throwNew(e, "java/io/IOException", buf); + } + } +} + +void close_output(JNIEnv* e, SOCKET sock) { + if (SOCKET_ERROR == ::shutdown(sock, SD_SEND)) { + int errcode = last_socket_error(); + if (errcode != ENOTCONN) { + char buf[255]; + sprintf(buf, "Can't shutdown the socket. System error: %d", errcode); + throwNew(e, "java/io/IOException", buf); + } + } +} + +} +} +} diff --git a/classpath/sockets.h b/classpath/sockets.h new file mode 100644 index 0000000000..538aa50fa5 --- /dev/null +++ b/classpath/sockets.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + + +/* + * This file represents a simple cross-platform JNI sockets API + * It is used from different classes of the default Avian classpath + */ + +#ifndef SOCKETS_H_ +#define SOCKETS_H_ + +#include "avian/common.h" +#include "jni.h" +#include "jni-util.h" + +#ifdef PLATFORM_WINDOWS +# include + +# define ONLY_ON_WINDOWS(x) x +#else +# include +# include +# include +# include + +# define ONLY_ON_WINDOWS(x) +# define SOCKET int +# define INVALID_SOCKET -1 +# define SOCKET_ERROR -1 +# define closesocket(x) close(x) + +# define SD_RECEIVE SHUT_RD +# define SD_SEND SHUT_WR +# define SD_BOTH SHUT_RDWR + +#endif + +namespace avian { +namespace classpath { +namespace sockets { + +// Library initialization +void init(JNIEnv* ONLY_ON_WINDOWS(e)); + +// Socket initialization +SOCKET create(JNIEnv* e); +void connect(JNIEnv* e, SOCKET sock, long addr, short port); +void bind(JNIEnv* e, SOCKET sock, long addr, short port); +SOCKET accept(JNIEnv* e, SOCKET sock, long* client_addr, short* client_port); + +// I/O +void send(JNIEnv* e, SOCKET sock, const char* buff_ptr, int buff_size); +int recv(JNIEnv* e, SOCKET sock, char* buff_ptr, int buff_size); + +// Socket closing +void abort(JNIEnv* e, SOCKET sock); +void close(JNIEnv* e, SOCKET sock); +void close_input(JNIEnv* e, SOCKET sock); +void close_output(JNIEnv* e, SOCKET sock); + +} +} +} +#endif /* SOCKETS_H_ */ diff --git a/test/Sockets.java b/test/Sockets.java new file mode 100644 index 0000000000..4a4b53e282 --- /dev/null +++ b/test/Sockets.java @@ -0,0 +1,37 @@ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.net.UnknownHostException; + +public class Sockets { + + /** + * @param args + * @throws IOException + * @throws UnknownHostException + */ + public static void main(String[] args) throws UnknownHostException, + IOException { + System.out.print("Requesting...\n"); + try (Socket sock = new Socket("www.google.com", 80)) { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())); + String request = "GET /?gws_rd=cr HTTP/1.1\r\n" + + "Host: www.google.com\r\n" + "Accept: */*\r\n" + + "User-Agent: Java\r\n" + "Connection: close\r\n" + "\r\n"; + bw.write(request); + bw.flush(); + + BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream())); + String read = null; + while ((read = br.readLine()) != null) { + System.out.println(read); + } + bw.close(); + sock.close(); + } + } + +} \ No newline at end of file