JAVA network programming

Posted by vh3r on Sat, 29 Jan 2022 12:31:45 +0100

Network programming

1. Network foundation

TCP/IP protocol generally refers to the internet protocol. Only computers using TCP/IP protocol can connect to the Internet.

1.1 IP address

In the Internet, an IP address is used to uniquely identify a Network Interface. A computer connected to the Internet must have one IP address, but it may also have multiple IP addresses.

IP addresses are divided into IPv4 and IPv6. IPv4 adopts 32-bit address, similar to 101.202.99.12, while IPv6 adopts 128 bit address, similar to 2001:0DA8:100A:0000:0000:1020:F2F3:1428

IP address is divided into public IP address and Intranet IP address. The public IP address can be accessed directly, while the intranet IP address can only be accessed in the intranet. The intranet IP address is similar to:

  • 192.168.x.x
  • 10.x.x.x

There is a special IP address, called the native address, which is always 127.0.0.1.

If a computer has only one network card and is connected to the network, it has a local address 127.0.0.1 and an IP address, such as 101.202.99.12, which can be used to access the network.

If a computer has two network cards, it can have two IP addresses in addition to the local address, which can be connected to two networks respectively. Usually, the equipment connecting the two networks is a router or switch. It has at least two IP addresses, which are connected to different networks to connect the networks.

If two computers are on the same network, they can communicate directly, because the front segment of their IP address is the same, that is, the network number is the same. The network number is obtained by filtering the IP address through the subnet mask. For example:

If the IP of a computer is 101.202.99.2 and the subnet mask is 255.255.255.0, the network number of the computer is calculated as:

IP = 101.202.99.2
Mask = 255.255.255.0
Network = IP & Mask = 101.202.99.0

Each computer needs to correctly configure the IP address and subnet mask. The network number can be calculated according to the two. If the network numbers calculated by the two computers are the same, it means that the two computers are on the same network and can communicate directly.

If the network numbers calculated by the two computers are different, the two computers are not in the same network and cannot communicate directly. They must communicate indirectly through network devices such as routers or switches. We call this kind of device gateway.

The function of gateway is to connect multiple networks and send data packets from one network to another. This process is called routing.

One network card of a computer has three key configurations:

1.2. domain name

It is very difficult to remember IP addresses directly, so we usually use domain names to access a specific service. The domain name resolution server DNS is responsible for translating the domain name into the corresponding IP, and then the client accesses the server according to the IP address.

Use nslookup to view the IP address corresponding to the domain name:

$ nslookup www.liaoxuefeng.com
Server:  xxx.xxx.xxx.xxx
Address: xxx.xxx.xxx.xxx#53

Non-authoritative answer:
Name:    www.liaoxuefeng.com
Address: 47.98.33.223

There is a special local domain name localhost, and its corresponding IP address is always the local address 127.0.0.1.

1.3 network model

OSI reference model

  • Application layer, which provides communication between applications;
  • Presentation layer: processing data format, encryption and decryption, etc;
  • Session layer: responsible for establishing and maintaining sessions;
  • Transport layer: responsible for providing reliable end-to-end transmission;
  • Network layer: responsible for selecting the route to transmit data according to the target address;
  • The link layer and physical layer are responsible for dividing the data and transmitting it through the physical network, such as wireless network, optical fiber, etc.

The TCP/IP model actually used by the Internet does not correspond to the 7-layer model of OSI, but roughly corresponds to the 5-layer model of OSI:

OSITCP/IP
application layerapplication layer
Presentation layer
Session layer
Transport layerTransport layer
network layerIP layer
link layerNetwork interface layer
physical layer

1.4 common protocols

IP protocol is a packet switching, which does not guarantee reliable transmission. TCP protocol is a transmission control protocol, which is a connection oriented protocol and supports reliable transmission and two-way communication. TCP protocol is based on IP protocol. In short, IP protocol is only responsible for sending data packets without ensuring the sequence and correctness. TCP protocol is responsible for controlling the transmission of data packets. It needs to establish a connection before transmitting data, and then it can transmit data. After transmission, it also needs to disconnect. The reason why TCP protocol can ensure the reliable transmission of data is through the mechanisms of receiving confirmation and timeout retransmission. Moreover, TCP protocol allows two-way communication, that is, both sides of the communication can send and receive data at the same time.

TCP protocol is also the most widely used protocol. Many advanced protocols are based on TCP protocol, such as HTTP, SMTP and so on.

UDP (User Datagram Protocol) is a data message protocol. It is a connectionless protocol and unreliable transmission. Because UDP protocol does not need to establish a connection before communication, its transmission efficiency is higher than TCP, and UDP protocol is much simpler than TCP protocol.

When UDP protocol is selected, the transmitted data can usually tolerate loss. For example, some voice and video communication applications will choose UDP protocol.

2. TCP programming

2.1 Socket

When developing network applications, we will encounter the concept of socket. Socket is an abstract concept. An application establishes a remote connection through a socket, and the socket transmits data to the network through TCP/IP protocol:

When the operating system receives a packet, if it only has an IP address, it cannot judge which application should be sent. Therefore, the operating system abstracts the Socket interface. Each application needs to correspond to different sockets, and the packet can be sent to the corresponding application correctly according to the Socket.

Socket is equal to the IP address plus the end address.

The port number is always assigned by the operating system. It is a number between 0 and 65535. Among them, ports less than 1024 belong to privileged ports and require administrator privileges. Ports greater than 1024 can be opened by any user's application.

Therefore, when the Socket connection is successfully established between the server and the client:

  • For the server side, its Socket is the specified IP address and port number;
  • For the client, its Socket is the IP address of its computer and a random port number assigned by the operating system.

2.2 server side

The Java standard library provides ServerSocket to monitor the specified IP and port. The typical implementation code of ServerSocket is as follows:

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(6666); // Listen to the specified port
        System.out.println("server is running...");
        for (;;) {
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        }
    }
}

class Handler extends Thread {
    Socket sock;

    public Handler(Socket sock) {
        this.sock = sock;
    }

    @Override
    public void run() {
        try (InputStream input = this.sock.getInputStream()) {
            try (OutputStream output = this.sock.getOutputStream()) {
                handle(input, output);
            }
        } catch (Exception e) {
            try {
                this.sock.close();
            } catch (IOException ioe) {
            }
            System.out.println("client disconnected.");
        }
    }

    private void handle(InputStream input, OutputStream output) throws IOException {
        var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        writer.write("hello\n");
        writer.flush();
        for (;;) {
            String s = reader.readLine();
            if (s.equals("bye")) {
                writer.write("bye\n");
                writer.flush();
                break;
            }
            writer.write("ok: " + s + "\n");
            writer.flush();
        }
    }
}

Server side pass code:

ServerSocket ss = new ServerSocket(6666);

Listen on the specified port 6666. Here, we do not specify an IP address, which means listening on all network interfaces of the computer.

If the ServerSocket listens successfully, we will use an infinite loop to handle the connection of the client:

for (;;) {
    Socket sock = ss.accept();
    Thread t = new Handler(sock);
    t.start();
}

ss.accept() means that every time a new client connects, it returns a Socket instance, which is used to communicate with the newly connected client. Because there are many clients, to realize concurrent processing, we must create a new thread for each new Socket. In this way, the role of the main thread is to receive new connections. Whenever a new connection is received, a new thread will be created for processing.

You can also use thread pools for processing.

If no client connects, the accept() method blocks and waits. If multiple clients are connected at the same time, ServerSocket will throw the connections into the queue and process them one by one. For Java programs, you just need to call accept() repeatedly through a loop to get a new connection.

2.3 client

public class Client {
    public static void main(String[] args) throws IOException {
        Socket sock = new Socket("localhost", 6666); // Connect to the specified server and port
        try (InputStream input = sock.getInputStream()) {
            try (OutputStream output = sock.getOutputStream()) {
                handle(input, output);
            }
        }
        sock.close();
        System.out.println("disconnected.");
    }

    private static void handle(InputStream input, OutputStream output) throws IOException {
        var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        Scanner scanner = new Scanner(System.in);
        System.out.println("[server] " + reader.readLine());
        for (;;) {
            System.out.print(">>> "); // Print tips
            String s = scanner.nextLine(); // Read one line of input
            writer.write(s);
            writer.newLine();
            writer.flush();
            String resp = reader.readLine();
            System.out.println("<<< " + resp);
            if (resp.equals("bye")) {
                break;
            }
        }
    }
}

The client program passes:

Socket sock = new Socket("localhost", 6666);

Connect to the server side. Note that the server address of the above code is "localhost", indicating the local address, and the port number is 6666. If the connection is successful, a Socket instance will be returned for subsequent communication.

2.4 Socket flow

When the Socket connection is created successfully, we use the Socket instance for network communication, both on the server side and on the client side. Because TCP is a stream based protocol, the Java standard library uses InputStream and OutputStream to encapsulate the Socket data stream. In this way, we use the Socket stream, which is similar to the ordinary IO stream:

// For reading network data:
InputStream in = sock.getInputStream();
// For writing network data:
OutputStream out = sock.getOutputStream();

If we don't call flush(), we may find that neither the client nor the server can receive data. This is not a design problem of the Java standard library, but when we write data in the form of stream, we don't send it to the network immediately as soon as we write it, but write it to the internal buffer first, and don't really send it to the network at one time until the buffer is full, The purpose of this design is to improve the transmission efficiency. If there is little data in the buffer and we want to force these data to be sent to the network, we must call flush() to force the buffer data to be sent out.

3. UDP programming

Compared with TCP programming, UDP programming is much simpler, because UDP does not create a connection, and data packets are sent and received one at a time, so there is no concept of flow.

When using UDP programming in Java, you still need to use Socket, because the application must specify the network interface (IP) and port number when using UDP. Note: Although both UDP port and TCP port use 0 ~ 65535, they are two sets of independent ports, that is, one application uses TCP to occupy port 1234, which does not affect the other application using UDP to occupy port 1234.

3.1 server side

Java provides datagram socket to realize this function. The code is as follows:

DatagramSocket ds = new DatagramSocket(6666); // Listen to the specified port
for (;;) { // Infinite loop
    // Data buffer:
    byte[] buffer = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    ds.receive(packet); // Receive a UDP packet
    // The received data is stored in the buffer and stored by packet getOffset(), packet. Getlength() specifies the starting position and length
    // Convert it into String according to UTF-8 encoding:
    String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
    // Send data:
    byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
    packet.setData(data);
    ds.send(packet);
}

The server first uses the following statement to listen for UDP packets on the specified port:

DatagramSocket ds = new DatagramSocket(6666);

When the server receives a datagram packet, it usually must reply to one or more UDP packets immediately, because the client address is in the datagram packet, and the datagram packet received each time may be a different client. If it does not reply, the client will not receive any UDP packets.

3.2 client

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666); // Connect to the specified server and port
// send out:
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);
// receive:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
ds.disconnect();

DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);

The client does not need to specify a port when creating a datagram socket instance, but the operating system automatically specifies a port that is not currently used. Next, call setSoTimeout(1000) to set the timeout of 1 second, which means that when receiving UDP packets later, the waiting time will not exceed 1 second at most. Otherwise, when no UDP packets are received, the client will wait indefinitely. this

ds.connect(InetAddress.getByName("localhost"), 6666);

This connect() method is not a true connection. It is to save the IP and port number of the server in the datagram socket instance of the client to ensure that the datagram socket instance can only send UDP packets to the specified address and port, not to other addresses and ports.

If the client wants to send UDP packets to two different servers, it must create two datagram socket instances.

ds.disconnect();

Note that disconnect() does not really disconnect. It just clears the remote server address and port number recorded by the client datagram socket instance, so that the datagram socket instance can connect to another server.

4. Send Email

4.1 basic definition

Mail software is called MUA: Mail User Agent, which means mail agent serving users;

The mail server is called MTA: Mail Transfer Agent, which means the agent for mail transfer;

The mail server that finally arrives is called MDA: Mail Delivery Agent, which means the agent of mail arrival.

E-mail is usually stored on the hard disk of MDA server, and then wait for the recipient to view the e-mail through software or browser.

To send mail, we are concerned about how to write a MUA software to send mail to MTA.

The protocol for sending mail from MUA to MTA is SMTP protocol, which is the abbreviation of Simple Mail Transport Protocol. It uses standard port 25 or encryption port 465 or 587.

SMTP protocol is a protocol based on TCP. Any program sending mail must abide by SMTP protocol. When using Java programs to send mail, we don't need to care about the underlying principle of SMTP protocol. We can send mail directly by using JavaMail, a standard API.

4.2 SMTP login information

Before sending mail, we must first determine the mail server address and port number as MTA. The mail server address is usually SMTP example. COM, the port number is determined by the mail service provider to use 25, 465 or 587.

For example:

  • QQ mailbox: SMTP server is SMTP qq. COM, the port is 465 / 587;
  • 163 mailbox: SMTP server is SMTP 163.com, port 465;
  • Gmail mailbox: SMTP server is SMTP gmail. COM, the port is 465 / 587.

With the domain name and port number of the SMTP server, we also need the login information of the SMTP server. Usually, we use our own email address as the user name, and the login password is the user password or an independently set SMTP password.

4.4 how to send mail

  1. Dependency import required

    In Maven project, add in pom configuration:

    <dependencies>
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>javax.mail-api</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.6.2</version>
        </dependency>
    
  2. Connect to SMTP server via JAVAMail API

    // Server address:
    String smtp = "smtp.office365.com";
    // Login user name:
    String username = "jxsmtp101@outlook.com";
    // Login password:
    String password = "********";
    // Connect to SMTP server port 587:
    Properties props = new Properties();
    props.put("mail.smtp.host", smtp); // SMTP host name
    props.put("mail.smtp.port", "587"); // Host port number
    props.put("mail.smtp.auth", "true"); // Is user authentication required
    props.put("mail.smtp.starttls.enable", "true"); // Enable TLS encryption
    // Get Session instance:
    Session session = Session.getInstance(props, new Authenticator() {
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }
    });
    // Set debug mode for debugging:
    session.setDebug(true);
    

    Take port 587 as an example. When connecting to the SMTP server, you need to prepare a Properties object and fill in relevant information. Finally, when obtaining the Session instance, if the server needs authentication, it also needs to pass in an Authenticator object and return the specified user name and password.

  3. Send mail

    Construct a Message object and then call Transport.. Send (message):

    MimeMessage message = new MimeMessage(session);
    // Set sender address:
    message.setFrom(new InternetAddress("me@example.com"));
    // Set receiver address:
    message.setRecipient(Message.RecipientType.TO, new InternetAddress("xiaoming@somewhere.com"));
    // Set message subject:
    message.setSubject("Hello", "UTF-8");
    // Set message body:
    message.setText("Hi Xiaoming...", "UTF-8");
    // send out:
    Transport.send(message);
    

    Most mail servers require that the sender's address and login user name must be consistent

4.5 HTML mail

Send HTML mail

Sending HTML mail is similar to sending text mail. You only need to:

message.setText(body, "UTF-8");

Replace with:

message.setText(body, "UTF-8", "html");

4.6 sending attachments

To carry an attachment in an email, we can't call message directly Settext() method, but to construct a Multipart object:

Multipart multipart = new MimeMultipart();
// Add text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// Add image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "application/octet-stream")));
multipart.addBodyPart(imagepart);
// Set the mail content as multipart:
message.setContent(multipart);

A Multipart object can add several bodyparts. The first BodyPart is the text, that is, the message body, and the following BodyPart is the attachment.

BodyPart relies on setContent() to decide what to add. If you add text, use setcontent ("..."), "Text / plain; charset = UTF-8") to add plain text, or use setContent("...", "text/html;charset=utf-8") to add HTML text.

To add an attachment, you need to set the file name (not necessarily the same as the real file name), and add a DataHandler(), the MIME type of the incoming file. Binary files can use application / octet stream, and Word documents can use application/msword.

How to embed pictures?

Multipart multipart = new MimeMultipart();
// Add text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent("<h1>Hello</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// Add image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/jpeg")));
// Association with < img SRC = "CID: img01" > of HTML:
imagepart.setHeader("Content-ID", "<img01>");
multipart.addBodyPart(imagepart);

4.7 frequently asked questions

If the user name or password is wrong, it will cause 535 login failure:

DEBUG SMTP: AUTH LOGIN failed
Exception in thread "main" javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful [HK0PR03CA0105.apcprd03.prod.outlook.com]

If the login user is inconsistent with the sender, it will lead to 554 reject sending error:

DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied;

Sometimes, if the subject and body of the message are too simple, it will cause 554 to be recognized as spam:

DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 DT:SPM

5. Receive Email

Receiving mail is the process in which the recipient grabs the mail from the MDA server to the local with his own client.

The most widely used protocol for receiving mail is POP3: Post Office Protocol version 3, which is also a protocol based on TCP connection. The standard port of POP3 server is 110. If the whole session needs to be encrypted, use the encrypted port 995.

Another protocol for receiving mail is IMAP: Internet Mail Access Protocol, which uses standard port 143 and encryption port 993.

The main difference between IMAP and POP3 is that all local operations of IMAP protocol will be automatically synchronized to the server, and IMAP can allow users to create folders in the inbox of the mail server.

JavaMail also provides support for the IMAP protocol. Because POP3 and IMAP are used in a very similar way, we will only introduce the usage of POP3.

When using POP3 to receive Email, we don't need to care about the bottom layer of POP3 protocol, because JavaMail provides a high-level interface. First, you need to connect to the Store object:

// Prepare login information:
String host = "pop3.example.com";
int port = 995;
String username = "bob@example.com";
String password = "password";

Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3"); // Agreement name
props.setProperty("mail.pop3.host", host);// POP3 host name
props.setProperty("mail.pop3.port", String.valueOf(port)); // Port number
// Start SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", String.valueOf(port));

// Connect to Store:
URLName url = new URLName("pop3", host, post, "", username, password);
Session session = Session.getInstance(props, null);
session.setDebug(true); // Display debugging information
Store store = new POP3SSLStore(session, url);
store.connect();

A Store object represents the storage of the whole mailbox. To receive mail, we need to access the specified Folder through the Store. Usually, INBOX represents the INBOX:

// Get inbox:
Folder folder = store.getFolder("INBOX");
// Open in read-write mode:
folder.open(Folder.READ_WRITE);
// Total number of printed messages / number of new messages / number of unread messages / number of deleted messages:
System.out.println("Total messages: " + folder.getMessageCount());
System.out.println("New messages: " + folder.getNewMessageCount());
System.out.println("Unread messages: " + folder.getUnreadMessageCount());
System.out.println("Deleted messages: " + folder.getDeletedMessageCount());
// Get each message:
Message[] messages = folder.getMessages();
for (Message message : messages) {
    // Print each message:
    printMessage((MimeMessage) message);
}

When we get a Message object, we can forcibly transform it into MimeMessage, and then print out the Message subject, sender, recipient and other information:

void printMessage(MimeMessage msg) throws IOException, MessagingException {
    // Email subject:
    System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
    // From:
    Address[] froms = msg.getFrom();
    InternetAddress address = (InternetAddress) froms[0];
    String personal = address.getPersonal();
    String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
    System.out.println("From: " + from);
    // Continue printing to:
    ...
}

The trouble is to get the body of the email. A MimeMessage object is also a Part object. It may contain only one text or a Multipart object, that is, it is composed of several parts. Therefore, it is necessary to recursively parse the complete body:

String getBody(Part part) throws MessagingException, IOException {
    if (part.isMimeType("text/*")) {
        // Part is text:
        return part.getContent().toString();
    }
    if (part.isMimeType("multipart/*")) {
        // Part is a Multipart object:
        Multipart multipart = (Multipart) part.getContent();
        // Circular parsing of each sub Part:
        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bodyPart = multipart.getBodyPart(i);
            String body = getBody(bodyPart);
            if (!body.isEmpty()) {
                return body;
            }
        }
    }
    return "";
}

Finally, remember to close the Folder and Store:

folder.close(true); // Passing in true means that the deletion operation will be synchronized to the server (that is, delete the mail in the server inbox)
store.close();

6. HTTP programming

HTTP is the abbreviation of HyperText Transfer Protocol, which is translated into HyperText Transfer Protocol. It is a request response protocol based on TCP protocol.

When the browser wants to access a website, a TCP connection is first established between the browser and the website server, and the server always uses port 80 and encryption port 443. Then, the browser sends an HTTP request to the server. After receiving it, the server returns an HTTP response, and the HTML web page content is included in the response, After the browser parses the HTML, it can display the web page to the user.

The format of HTTP request is fixed. It consists of HTTP Header and HTTP Body. The first line always requests the HTTP version of the method path. For example, GET / HTTP/1.1 means to use GET request. The path is /, and the version is HTTP/1.1.

Each subsequent line is in a fixed Header: Value format, which is called HTTP Header. The server relies on some specific headers to identify client requests, such as:

  • Host: requested domain name

  • User agent: client ID (judging from what browser or whether from a crawler)

    The crawler can modify this information and disguise itself. I don't know what else to use.

  • Accept: the HTTP response format that the client can handle, * / * indicates any format, text / * indicates any text, and image/png indicates pictures in png format.

  • Accept language: indicates the language acceptable to the client.

If it is a GET request, the HTTP request has only an HTTP Header and no HTTP Body. If it is a POST request, the HTTP request has a Body separated by a blank line. A typical HTTP request with Body is as follows:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

username=hello&password=123456

In a POST request, the content type indicates the type of the Body, and the content length indicates the length of the Body, so that the server can respond correctly according to the Header and Body of the request.

The parameters of the GET request must be attached to the URL and encoded in URLEncode, for example: http://www.example.com/?a=1&b=K%26R , the parameters are a = 1 and B = K & R respectively.

The parameters of the POST request are not necessarily URL codes, but can be coded in any format, which only needs to be set correctly in the content type. Common POST requests for sending JSON are as follows:

POST /login HTTP/1.1
Content-Type: application/json
Content-Length: 38

{"username":"bob","password":"123456"}

Typical HTTP responses are as follows:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 133251

<!DOCTYPE html>
<html><body>
<h1>Hello</h1>
...

The first line of the response is always the HTTP version response code response description. For example, HTTP/1.1 200 OK indicates that the version is HTTP/1.1, the response code is 200, and the response description is OK.

Response code:

  • 1xx: indicates a prompt response. For example, 101 indicates that the protocol will be switched, which is common in WebSocket connection;
  • 2xx: indicates a successful response. For example, 200 indicates success and 206 indicates that only part of the content has been sent;
  • 3xx: indicates a redirect response. For example, 301 indicates permanent redirection, 303 indicates that the client should resend the request according to the specified path;
  • 4xx: indicates an error response caused by a client problem. For example, 400 indicates an invalid request caused by various reasons such as content type, and 404 indicates that the specified path does not exist;
  • 5xx: indicates an error response caused by a server problem. For example, 500 indicates an internal failure of the server, and 503 indicates that the server cannot respond temporarily.

When the browser receives the first HTTP response, it parses the HTML and sends a series of HTTP requests, such as get / logo Jpg http / 1.1 requests a picture. After the server responds to the picture request, it will directly send the picture of binary content to the browser:

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 18391

????JFIFHH??XExifMM?i&??X?...(Binary JPEG picture)

HTTP/1.0 protocol. Each time an HTTP request is sent, the client needs to create a new TCP connection first.

The HTTP/1.1 protocol allows you to send - response repeatedly in a TCP connection.

HTTP/2.0 allows the client to send multiple HTTP requests without receiving a response. When the server returns a response, it does not necessarily return in order. As long as both parties can identify which response corresponds to which request, they can send and receive in parallel:

Starting from Java 11, a new HttpClient was introduced. It uses the API of chain call, which can greatly simplify the processing of HTTP.

First, you need to create a global HttpClient instance, because HttpClient uses thread pool to optimize multiple HTTP connections, which can be reused:

static HttpClient httpClient = HttpClient.newBuilder().build();

Use the GET request to obtain the text content code as follows:

import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Version;
import java.time.Duration;
import java.util.*;

public class Main {
    // Global HttpClient:
    static HttpClient httpClient = HttpClient.newBuilder().build();

    public static void main(String[] args) throws Exception {
        String url = "https://www.sina.com.cn/";
        HttpRequest request = HttpRequest.newBuilder(new URI(url))
            // Set Header:
            .header("User-Agent", "Java HttpClient").header("Accept", "*/*")
            // Set timeout:
            .timeout(Duration.ofSeconds(5))
            // Set version:
            .version(Version.HTTP_2).build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        // HTTP allows duplicate headers, so one Header can correspond to multiple values:
        Map<String, List<String>> headers = response.headers().map();
        for (String header : headers.keySet()) {
            System.out.println(header + ": " + headers.get(header).get(0));
        }
        System.out.println(response.body().substring(0, 1024) + "...");
    }
}

If we want to get binary content such as pictures, we just need to put httpresponse BodyHandlers. Replace ofstring() with httpresponse BodyHandlers. Ofbytearray(), you can get an httpresponse < byte [] > object. If the content of the response is large and you don't want to load it all into memory at once, you can use httpresponse BodyHandlers. Ofinputstream() gets an InputStream stream.

To use the POST request, we need to prepare the Body data to be sent and set the content type correctly:

String url = "http://www.example.com/login";
String body = "username=bob&password=123456";
HttpRequest request = HttpRequest.newBuilder(new URI(url))
    // Set Header:
    .header("Accept", "*/*")
    .header("Content-Type", "application/x-www-form-urlencoded")
    // Set timeout:
    .timeout(Duration.ofSeconds(5))
    // Set version:
    .version(Version.HTTP_2)
    // Use POST and set Body:
    .POST(BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String s = response.body();

7. RMI remote call

RMI remote call of Java refers to that the code in one JVM can remotely call a method of another JVM through the network. RMI is the abbreviation of Remote Method Invocation.

The party that provides the service is called the server, and the party that implements the remote call is called the client.

Let's first implement the simplest RMI: the server will provide a WorldClock service that allows the client to obtain the time in the specified time zone, that is, it allows the client to call the following methods:

LocalDateTime getLocalDateTime(String zoneId);

To implement RMI, the server and client must share the same interface. We define a WorldClock interface with the following code:

public interface WorldClock extends Remote {
    LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

The RMI of Java stipulates that this interface must be derived from Java rmi. Remote and throw RemoteException in each method declaration.

The next step is to write the implementation class of the server, because the calling method getLocalDateTime() requested by the client will eventually return the result through this implementation class. The code of the implementation class WorldClockService is as follows:

public class WorldClockService implements WorldClock {
    @Override
    public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
        return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    }
}

We need to expose the services written above to the network in the form of RMI through a series of underlying support interfaces provided by Java RMI, and the client can call:

public class Server {
    public static void main(String[] args) throws RemoteException {
        System.out.println("create World clock remote service...");
        // Instantiate a WorldClock:
        WorldClock worldClock = new WorldClockService();
        // Convert this service to a remote service interface:
        WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
        // Register RMI service to port 1099:
        Registry registry = LocateRegistry.createRegistry(1099);
        // Register this service called "WorldClock":
        registry.rebind("WorldClock", skeleton);
    }
}

The main purpose of the above code is to register our own WorldClock instance to the RMI service through the relevant classes provided by RMI. The default port of RMI is 1099. In the last step of registering the service, rebind() is used to specify the service name as "WorldClock".

Client code:

RMI requires that the server and client share the same interface, so we need to put worldclock Copy the java interface file to the client, and then implement RMI call on the client:

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        // Connect to the server localhost, port 1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // Find a service named "WorldClock" and forcibly transform it into a WorldClock interface:
        WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
        // Call interface method normally:
        LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
        // Print call results:
        System.out.println(now);
    }
}

The WorldClock interface held by the client actually corresponds to an "implementation class", which is dynamically generated within Registry and is responsible for passing method calls to the server through the network. The service that the server receives network calls is not the WorldClockService written by ourselves, but the code automatically generated by Registry. We call the "implementation class" of the client as stub, and the network service class of the server as skeleton. It will really call the WorldClockService of the server, obtain the results, and then pass the results to the client through the network.

Copy the file to the client, and then implement RMI call on the client:

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        // Connect to the server localhost, port 1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // Find a service named "WorldClock" and forcibly transform it into a WorldClock interface:
        WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
        // Call interface method normally:
        LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
        // Print call results:
        System.out.println(now);
    }
}

The WorldClock interface held by the client actually corresponds to an "implementation class", which is dynamically generated within Registry and is responsible for passing method calls to the server through the network. The service that the server receives network calls is not the WorldClockService written by ourselves, but the code automatically generated by Registry. We call the "implementation class" of the client as stub, and the network service class of the server as skeleton. It will really call the WorldClockService of the server, obtain the results, and then pass the results to the client through the network.

[external chain picture transferring... (IMG covazsuh-1623835194070)]

When using RMI, both parties must be machines trusted by each other in the intranet. Do not expose port 1099 on the public network as an external service.

Topics: Java network Network Protocol http