Java Foundation Series: Socket programming

Earthly Wanderer: a procedural ape focusing on technical research

Speaking of the previous words

The theoretical basis without actual combat cases is playing hooligans, so today we mainly want to deepen our understanding of the past through the cases here

In this section, we will implement a point-to-point chat applet step by step

Socket implementation in Java

InetAddress

InetAddress is Java's encapsulation of IP address. This class is a basic class. The following ServerSocket and datagram socket are inseparable from this class

InetAddress cannot be initialized by new. It can only be called by providing its static method:

// Get local address
InetAddress localHost = InetAddress.getLocalHost();

Here are some methods of InetAddress:

// Host name: DESKTOP-ATG4KKE
System.out.println("Host name:" + localHost.getHostName());

// IP address: 192.168.87.1
System.out.println("IP Address:" + localHost.getHostAddress());

// Normal: true
System.out.println("Normal:" + localHost.isReachable(5000));

Here is the output of my test,

The isReachable() method is used to check whether the address can be accessed, so we can do some health check operations, such as:

// Get the InetAddress object through the host IP or domain name
InetAddress inetAddress = InetAddress.getByName("192.168.87.139");
System.out.println("Normal:" + inetAddress.isReachable(5000));

Try to connect to the host as much as possible within 5s. If not, the host is considered unavailable. This is limited by the firewall and server configuration

Of course, the method of doing health examination is still a little low, and it will certainly not be done in the production environment

PS: the network operation in the production environment will not use the things in this section. In most cases, Netty is used

ServerSocket

ServerSocket is a server socket, which is based on TCP/IP protocol

initialization

We usually build it this way:

ServerSocket serverSocket = new ServerSocket(9999);

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9999));

This completes the initialization of the server and binds the port 9999

Waiting for connection

If the client wants to establish a connection with ServerSocket, we need to do so

for(;;) {
    Socket socket = serverSocket.accpet();
    // Socket[addr=/0:0:0:0:0:0:0:1,port=62445,localport=9999]
    System.out.println(socket);
}

accpet() listens to the connection established with ServerSocket. This method is a blocking method and will wait for the connection to come in

If a connection is accessed, we can get the Socket currently accessed through the return value

signal communication

In fact, data transmission in the network is also carried out in the way of IO stream, but we can only obtain byte stream:

InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

InputStream reads data and OutputStream writes data. These basic operations have been introduced in previous IO streams, so I won't talk about them here

Here, in order to improve efficiency, we can use packaging flow or processing flow, which was also introduced earlier

Complete small example

In fact, the key introduction of ServerSocket is over here. Let's take a small example:

  • When a client connects, it returns Hello World to the client
public class _ServerSocket {
    // It is used to store the correspondence between the requesting client and the Socket
    static Map<String, Socket> MAP = new HashMap<>(); 
    
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(9999));

            for (; ; ) {
                String token = UUID.randomUUID().toString().replace("-", "").toLowerCase();
                
                Socket socket = serverSocket.accept();
                // corresponding
                MAP.put(token, socket);
                
                outHtml(socket);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void outHtml(Socket socket) {
        OutputStream outputStream = null;
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(("HTTP/1.1 200 OK\n\nHello World").getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

HTTP/1.1 200 OK\n\nHello World\n

This is the return type under the HTTP protocol. In front of it is the Response fixed format. Hello World is the real returned content, so that our ServerSocket can be accessed through the browser

Socket

Socket is a client socket. Other operations can be performed only after establishing a connection with the server socket. The use of socket is very simple

Establish connection

Socket socket = new Socket("127.0.0.1", 9999);

// Verify that the connection is successful
if (socket.isConnected()) {
    System.out.println("Successfully connected to the server");
}

This is one of the construction methods, which is more often used

After the connection with the server is established successfully, the subsequent operation is the same as the communication steps of ServerSocket. There is no more nonsense here

Let's consolidate it with a complete example

Case: TCP peer-to-peer chat

Server

public class Server {
    /**
     * Associate client ID with socket
     */
    private static final Map<String, Socket> SOCKET_MAP = new HashMap<>();
    /**
     * Reverse association, used to obtain identification
     */
    private static final Map<Socket, String> SOCKET_TOKEN_MAP = new HashMap<>();

    public static void main(String[] args) throws IOException {
        /**
         * Open ServerSocket and listen on port 9999
         */
        ServerSocket serverSocket = new ServerSocket(9999);

        for (;;) {
            /**
             * Waiting for client connection
             */
            Socket socket = serverSocket.accept();

            /**
             * IO Reading is a blocking method, so you need to start a new thread, which can be optimized into a thread pool
             */
            new Thread(() -> {
                try {
                    saveToMap(socket);
                    getClientMsg(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
     * Bind SOCKET
     */
    private static void saveToMap(Socket socket) throws IOException {
        String token = StringUtil.uuid();
        SOCKET_MAP.put(token, socket);
        SOCKET_TOKEN_MAP.put(socket, token);

        System.out.println("---Client connection succeeded, No.:" + token);
        System.out.println("Current user:" + SOCKET_MAP.size());

        /**
         * Because there is no login, the client should be informed of its own identity here
         */
        send(token, token, token);
    }

    /**
     * Get the message sent by the client and send it to the specified client
     */
    private static void getClientMsg(Socket socket) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = "";
        while ((line = reader.readLine()) != null) {
            // After reading a line, send it from here
            send(socket, line);
        }
    }

    /**
     * send message
     */
    private static void send(Socket socket, String line) throws IOException {
        String[] s = line.split("#");
        final String from = SOCKET_TOKEN_MAP.get(socket);
        send(s[0], s[1], from);
    }

    /**
     * send message
     * @param token
     * @param msg
     * @param from This is shown on the target client
     * @throws IOException
     */
    private static void send(String token, String msg, String from) throws IOException {
        Socket sk = SOCKET_MAP.get(token);

        if (null == sk)
            return;

        String s = from + ":" + msg;
        System.out.println("---Send to client:" + s );
        // Character stream output
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
        writer.write(s);
        writer.newLine();
        writer.flush();

    }
}

client

public class Client {

    public static void main(String[] args) throws IOException {
        /**
         * Connect to the server
         */
        Socket socket = new Socket("127.0.0.1", 9999);

        /**
         * Open a new thread to read messages, which can be optimized
         */
        new Thread(() -> {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = "";
                while (StringUtil.isNotBlank(line = reader.readLine())) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        /**
         * Write and send messages from the console
         */
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String next = scanner.next();
            send(next, socket);
        }
    }

    private static void send(String msg, Socket socket) throws IOException {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write(msg);
        writer.newLine();
        writer.flush();
    }
}

The code has passed the test and the comments are very clear. You can try it and chat point-to-point according to the format of the identification # message.

If you want group chat:

  • Save the Socket to the collection, and then cycle the collection. It's very simple

I haven't written a chat program with Socket for a long time. I almost gave up

Next time, use netty to write. Netty is much more convenient than Socket

DatagramSocket

Datagram socket is a socket used to send and receive datagram packets. It is based on UDP protocol. According to the official introduction in the category:

Datagram socket is the sending or receiving point of packet delivery service. Each packet sent or received on a datagram socket is individually addressed and routed. Multiple packets sent from one machine to another may be routed in different ways and may arrive in any order

We can also understand the characteristics of UDP protocol.

DatagramPacket

This class represents datagram packet. Data transmission and reception in datagram socket are completed by this class, such as:

  • receive data
byte[] buffer = new byte[1024];
DatagramPacket p = new DatagramPacket(buffer, buffer.length);
  • send data
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);

To send data out, datagram packet needs to specify the IP and port of the receiving end so that it can be sent out

Let's see how to use it

initialization

DatagramSocket socket = new DatagramSocket(9999);

DatagramSocket s = new DatagramSocket(null);
s.bind(new InetSocketAddress(9999));

Both methods can complete initialization without any difference

receive messages

byte[] buffer = new byte[1024];
DatagramPacket p = new DatagramPacket(buffer, buffer.length);

socket.receive(p);

System.out.println(new String(p.getData(), 0, p.getLength()));

According to the receiving parameters of DatagramPacket, a byte[] is constructed, and then receive() is called so that the message is received.

receive() is a blocking method. It will continue to execute only when there is a message

send message

DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);

socket.send(p);

The sending of data packets is then done, and then the send() method is used to complete the sending of packets.

UDP does not need to be connected. It can send data directly through IP+PORT

Case: UDP chat

public class _DatagramPacket {

    public static void main(String[] args) throws IOException {
        // Get the port to be bound and the port to send data from the command line
        DatagramSocket datagramSocket = new DatagramSocket(Integer.parseInt(args[0]));
        
        System.out.println("Started");

        new Thread(() -> {

            byte[] buffer = new byte[1024];
            DatagramPacket p = new DatagramPacket(buffer, buffer.length);
            try {
                for (;;) {
                    // Build receive data
                    datagramSocket.receive(p);
                    System.out.println(p.getPort() + ": " + new String(buffer, 0, p.getLength()));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        Scanner scanner = new Scanner(System.in);
        DatagramPacket p = new DatagramPacket(new byte[0], 0, new InetSocketAddress("127.0.0.1", Integer.parseInt(args[1])));
        while (scanner.hasNext()) {
            String next = scanner.next();
            // Build and send packets
            p.setData(next.getBytes());
            datagramSocket.send(p);
        }
    }

If there is a flaw, the space will wrap. I'll leave it to you to modify it

Last words

So far, I've finished talking about Socket programming. I didn't introduce many API methods. It's the same when I use them.

The following is Java Net directory document:

Click here to view

Tags: Java network socket Network Communications

Posted by ~n[EO]n~ on Thu, 31 Mar 2022 15:57:02 +0300