Open In App

Legacy Socket API in Java

Improve
Improve
Like Article
Like
Save
Share
Report

The Java Socket API has been around for more than two decades. It has been maintained and updated over that period, but even the most well-kept code ultimately has to be upgraded to stay up with contemporary technologies. The fundamental classes that handle Socket interaction in Java 13 have been re-implemented to take advantage of the present state of Java while also preparing for future developments.

What is the Socket API, and how does it work?

Objects in the Java socket API (java.net.ServerSocket and java.net.Socket) provide you direct control over sockets that a server listens to and sockets that deliver data.

ServerSocket may be used to wait for connection requests on a port and, if accepted, return a Socket object that can be used to read and write data. Both of these classes employ a SOCKS-based implementation, and the grunt work is done by an internal implementation of SocketImpl. This code is straightforward to learn and use, yet it has been around since Java 1.0, as seen by its age. This combination of older Java and C code has necessitated increases in the default thread stack size since its inception, as well as the emergence of stability and concurrency difficulties over time. We’ve arrived to a stage where the only viable option is a thorough rebuild.

Implement the Legacy Socket API

This JEP proposes to replace the SocketImpl implementation with a new NioSocketImpl implementation. This solution uses the same architecture as the NIO (New I/O) implementation and connects with current buffer cache methods, eliminating the need for a thread stack. Several additional beneficial modifications, such as java.lang.ref, are included with these modifications. Should the SocketImpl implementation be garbage collected on sockets that have not been closed and timeout actions taking place with the Socket in non-blocking mode when being polled, a cleaner approach will be utilized to close sockets.

A system property has been created to utilize the original implementation in order to reduce the chance of difficulties while re-implementing techniques that have been in use for over 20 years.

The previous implementation will be used if usePlainSocketImpl=true is set.

It should be noted that SocketImpl is a historical SPI technique that was under-specified in the past; the current version strives to simulate undefined behavior when possible. There are, however, a few edge situations that may fail when utilizing the new implementation, which may be seen here. All but two of these may be fixed by using the system attribute mentioned above. The prior implementation of FileInputStream and FileOutputStream returned input and output streams, which were used to expand them. This is not the case with the current implementation.

Connections that return Sockets with the other (custom or platform) kind of SocketImpl cannot be accepted by ServerSockets that use a custom or platform SocketImpl.

Figure 1. Legacy Sockets Overview

So, what are the advantages of this?

The prior implementation was difficult to maintain and enhance; however, the Java Socket API will be easier to maintain now that these improvements have been made. The socket code’s dependability should improve as a result of better maintenance. The NIO implementation is also done at a lower level, which allows the Socket and ServerSocket classes to stay the same.

These modifications not only make it easier to maintain this code, but they also make the implementation future-proof. A project dubbed Project Loom is presently running in the JDK. This project introduces the notion of fibers, which is a novel way of looking at threads (find out more in our previous Project Loom article). When Project Loom is published, the NIO implementation would be able to take advantage of it, whereas the prior implementation will be unfit for purpose. Because the NIO implementation utilizes java.util.concurrent locks rather than synchronised methods, this is conceivable.

This debug output may be seen by running a class that instantiates Socket and ServerSocket. Here’s what the default (new) looks like:

Java




java -XX:+TraceClassLoading JEP353  | grep Socket
[0.033s][info   ][class,load] java.net.Socket source: jrt:/java.base
[0.035s][info   ][class,load] java.net.SocketOptions source: jrt:/java.base
[0.035s][info   ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.039s][info   ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl
[0.042s][info   ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.042s][info   ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.043s][info   ][class,load] sun.nio.ch.SocketDispatcher source: jrt:/java.base
[0.044s][info   ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base
[0.044s][info   ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.044s][info   ][class,load] java.net.ServerSocket source: jrt:/java.base
[0.045s][info   ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base
[0.045s][info   ][class,load] java.net.ServerSocket$1 source: jrt:/java.base


Using the Switch Expressions for the Socket

Example #1

Java




System.out.println(switch (args[0]) {
    // a simple switch case
        case "1" -> 1;
    case "2" -> 2;
    default -> args[0].length();
});


Output:

//This Switch case prints accordingly
//User entered 1
->1

Example #2

Java




System.out.println(switch (args[0]) {
case "1":
    yield 1;
case "2":
    yield 2;
default: {
    int len = args[0].length();
    yield len;
}
});


Output:

//This Switch case prints accordingly
//User entered 1
-> yield 1


Last Updated : 23 Jun, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads