Difference between ServerSocketChannel and ServerSocket (SpringBoot version)

ServerSocketChannel and ServerSocket are two different classes dealing with network programming in Java, and they have some differences in function and usage

1. ServerSocketChannel:
(1) ServerSocketChannel is a channel in the Java NIO library, which is used to monitor and accept client connections.

(2) ServerSocketChannel provides support for non-blocking mode, which can be set to non-blocking mode through the configureBlocking(false) method.

(3) ServerSocketChannel uses a selector (Selector) to manage multiple channels, and can process connection requests of multiple channels in one thread.

(4) After ServerSocketChannel accepts the connection, it returns a SocketChannel instance for communicating with the client.

2. ServerSocket:

(1) ServerSocket is a class in the Java standard library, which is used to monitor and accept client connections.

(2) ServerSocket is based on the blocking I/O model, and each connection request needs to be processed in a separate thread.

(3) ServerSocket uses the accept() method to accept client connections and returns a Socket instance for communicating with the client.

Main differences and uses:

(1) ServerSocketChannel is a non-blocking channel provided in the Java NIO library, while ServerSocket is a blocking class in the Java standard library.

(2) ServerSocketChannel uses a selector (Selector) to manage multiple channels, and can handle multiple connection requests in one thread, while ServerSocket usually needs to create an independent thread for each connection request.

(3) ServerSocketChannel provides more flexibility and can be used with other NIO components, such as buffers, selectors, etc. ServerSocket is mainly used for traditional blocking I/O programming.

(4) In terms of performance, ServerSocketChannel usually has better performance and scalability than ServerSocket, especially in high concurrency situations.

ServerSocketChannel best practice:

Using NIO (Non-blocking I/O) to process ServerSocket can realize asynchronous non-blocking I/O operations and improve server performance and scalability. NIO uses Selector to manage multiple channels, each channel corresponds to a client connection. Following is a simple example that demonstrates how to use NIO to handle ServerSocket

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @ClassName NIOServer
 * @Description TODO
 * @Author Yu Chunwang
 * @Company RBGT
 * @Date May 24, 2023 0024 09:26:26 AM
 * @Version 1.0
 */
public class NIOServer {

    private ServerSocketChannel serverChannel;
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer. allocate(1024);

    /**
     * Operation - Execute creating a ServerSocketChannel
     *
     * @param port port number
     * @return void
     * @Author Yu Chunwang
     * @Date 09:34:04 AM May 24, 2023 0024
     **/
    public void start(int port) throws IOException {
        // Create a ServerSocketChannel and set it to non-blocking mode
        serverChannel = ServerSocketChannel. open();
        serverChannel. configureBlocking(false);

        // bind port
        serverChannel.socket().bind(new InetSocketAddress(port));

        // Create Selector and register ServerSocketChannel
        selector = Selector. open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIOServer started. Listening on port " + port);

        // handle event loop
        while (true) {
            // Get - wait for the event to occur
            selector. select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys. iterator();

            // Loop - handle the data processing logic from the link
            while (iterator. hasNext()) {
                // Get - SelectionKey where the event occurred
                SelectionKey key = iterator. next();
                iterator. remove();

                // branch - whether this channel is ready to accept new socket connections
                if (key. isAcceptable()) {
                    // setup - prepare interface data
                    handleAccept(key);
                } else if (key. isReadable()) {
                    // Operation - process the data reported by the link
                    handleRead(key);
                }
            }
        }
    }

    /**
     * Operations - register iterators and label them with readable data
     *
     * @param key The SelectionKey where the event occurred (per link ID)
     * @return void
     * @Author Yu Chunwang
     * @Date 09:32:06 AM May 24, 2023 0024
     **/
    private void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key. channel();
        SocketChannel clientChannel = serverChannel. accept();
        clientChannel. configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("New client connected: " + clientChannel.getRemoteAddress());
    }

    /**
     * Operation - read the data reported by the link
     *
     * @param key The SelectionKey where the event occurred (per link ID)
     * @return void
     * @Author Yu Chunwang
     * @Date 09:32:55 AM May 24, 2023 0024
     **/
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key. channel();
        buffer. clear();
        int bytesRead = clientChannel. read(buffer);
        if (bytesRead > 0) {
            buffer. flip();
            byte[] data = new byte[buffer. limit()];
            buffer. get(data);
            String message = new String(data);
            System.out.println("Received message from client: " + message);
            // process the received message

            // reply message to client
            String response = "Response from server";
            buffer. clear();
            buffer. put(response. getBytes());
            buffer. flip();
            clientChannel.write(buffer);
        } else if (bytesRead == -1) {
            // client disconnected
            clientChannel. close();
            System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());
        }
    }

    /**
     * Action - test link
     *
     * @param args
     * @return void
     * @Author Yu Chunwang
     * @Date 09:33:43 AM May 24, 2023 0024
     **/
    public static void main(String[] args) {
        // Specify the server port
        int port = 8080;
        try {
            NIOServer nioServer = new NIOServer();
            nioServer.start(port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

In the above code, we use ServerSocketChannel to create a server-side channel and configure it in non-blocking mode. Then, we bind the port and register the server channel with the Selector to listen for connection events (OP_ACCEPT).

In the event loop, we call selector.select() to wait for an event to occur. Once an event occurs (such as a new connection arrives or data is readable), selector.select() will return, and we can get the SelectionKey collection of the event through selector.selectedKeys(). Then, we iterate through the SelectionKey collection and process it according to different event types.

If it is an OP_ACCEPT event, it means that a new client connection arrives, and we call the handleAccept() method to handle the connection. In the handleAccept() method, we get a new client channel and register it in the selector to listen to the data read event (OP_READ).

If it is an OP_READ event, it means that there is data to read, and we call the handleRead() method to process the read data. In the handleRead() method, we read data from the client channel into the buffer and process it accordingly. In this example, we print out the received message and send a simple response back to the client.

If bytesRead is a negative value, it means that the client is disconnected, and we close the client channel and handle accordingly.

Finally, in the main method, we create a NIOServer object and call the start method to start the server. Please note that the above example only demonstrates how to use NIO to process ServerSocket, and the processed message content and business logic may need to be adjusted and expanded according to actual needs.

In addition, NIO programming involves more concepts and operations, such as channels (Channel), buffers (Buffer), selectors (Selector), and so on. It is important to have a deep understanding of the NIO programming model and related APIs in order to handle I/O operations correctly and efficiently.