When we speak of networking, we cannot fail to mention the OSI model.

In terms of this model, today we are most interested in the transport layer (4).

This is the level at which we work with data moving "from point A to point B". The main task of the transport layer is to ensure that a message is delivered to the destination, while maintaining the correct sequence. The two most common transport layer protocols are: TCP and UDP. They work conceptually in different ways, but each has its own advantages that allow them to solve specific problems.

Layer Data type (PDU) Functions Examples
Transport Segment / Datagram Direct communication between endpoints and reliability TCP, UDP, SCTP, Ports

First, let's look at how TCP works.

TCP (Transmission Control Protocol) is a network protocol that ensures that a connection between hosts is established before data is exchanged.

This is a very reliable protocol, because every time it sends another data packet, it must check that the previous packet was received.

The transmitted packets are ordered, and if there are problems with a certain packet (i.e. the receiving party does not confirm that the packet has arrived), then the packet is sent again. As a result, the transfer rate is relatively low, because more time is required for the strict monitoring and for ensuring the correct ordering.

This is where its "brother", the UDP protocol, comes in. Unlike TCP, UDP doesn't really care about the order and status of each packet. It simply sends data without delivery confirmation. What's more, it doesn't establish a connection and doesn't depend on a connection status in any way.

Its purpose is simply to send data to an address. And this gives rise to the protocol's main disadvantage, low reliability, since it can simply lose pieces of data. Additionally, the recipient must be prepared for the fact that the data may arrive out of order. That said, the protocol also has an advantage, a higher transfer rate, due to the fact that the protocol is limited to sending data.

There are also differences in how the data itself is transmitted. In TCP, data is streamed, which means that the data has no boundaries. In UDP, data is transmitted as datagrams and has boundaries, and the recipient checks the integrity of the data, but only if the message is successfully received.

Let's summarize:

TCP is a reliable and accurate protocol that prevents out data loss. A message will always be delivered with maximum accuracy, or not delivered at all. The recipient doesn't need logic for ordering data, since incoming data will already be ordered. UDP is not as reliable, but it is a faster data transfer protocol. The sending and receiving parties need some additional logic in order to work with this protocol. But let's take a look at how it works using the example of a computer game or mobile game played over the network. We may no longer care about what should have arrived 5 seconds ago, and we can skip a couple of packets if they don't arrive on time — the game may lag, but you can still play!

In Java, to work with datagrams transmitted over UDP, we use objects of the DatagramSocket and DatagramPacket classes.

To exchange data, the sender and receiver create datagram sockets, i.e. instances of the DatagramSocket class. The class has several constructors. The difference between them is where the created socket will connect:

DatagramSocket () Connects to any available port on the local machine
DatagramSocket (int port) Connects to the specified port on the local machine
DatagramSocket(int port, InetAddress addr) Connects to the specified port at an address on the local machine (addr)

The class contains many methods for accessing and managing the socket parameters (we will look at them a little later), as well as methods for receiving and sending datagrams:

send(DatagramPacket pack) Sends datagrams packed into packets
receive (DatagramPacket pack) Receives datagrams packed into packets

DatagramPacket is a class that represents a datagram package. Datagram packets are used to implement a connectionless packet delivery service. Each message is routed from one machine to another based solely on the information contained in that packet. Multiple packets sent from one machine to another may be routed differently and may arrive in any order. Delivery of packets is not guaranteed.

Constructors:

DatagramPacket(byte[] buf, int length) Creates a DatagramPacket to accept packets of length length.
DatagramPacket(byte[] buf, int length, InetAddress address, int port) Creates a datagram packet to send packets of length length to the specified port number on the specified host.
DatagramPacket(byte[] buf, int offset, int length) Creates a DatagramPacket to accept packets of length length, specifying an offset in the buffer.
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) Creates a datagram packet to send packets of length length with offset offset to the specified port number on the specified host.
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) Creates a datagram packet to send packets of length length with offset offset to the specified port number on the specified host.
DatagramPacket(byte[] buf, int length, SocketAddress address) Creates a datagram packet to send packets of length length to the specified port number on the specified host.

We recall that the UDP approach does not establish a connection. The packets are sent off on the hopes that the recipient is expecting them. But you can establish a connection using the connect(InetAddress addr, int port) method of the DatagramSocket class.

A one-way connection is established with the host based on an address and port: either to send or receive datagrams. The connection can be terminated using the disconnect() method.

Let's try to write server code based on DatagramSocket to receive data:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

class Recipient {

   public static void main(String[] args) {
       try {
           DatagramSocket ds = new DatagramSocket(1050);

           while (true) {
               DatagramPacket pack = new DatagramPacket(new byte[5], 5);
               ds.receive(pack);
               System.out.println(new String(pack.getData()));
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

We create a DatagramSocket object to listen on port 1050. When it receives a message, it prints it to the console. We will transmit the word "Hello", so we limit the buffer size to five bytes.

Now we'll create the sender class:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class Sender {
   private String host;
   private int port;

   Sender(String host, int port) {
       this.host = host;
       this.port = port;
   }

   private void sendMessage(String mes) {
       try {
           byte[] data = mes.getBytes();
           InetAddress address = InetAddress.getByName(host);
           DatagramPacket pack = new DatagramPacket(data, data.length, address, port);
           DatagramSocket ds = new DatagramSocket();
           ds.send(pack);
           ds.close();
       } catch (IOException e) {
           System.err.println(e);
       }
   }

   public static void main(String[] args) {
   Sender sender = new Sender("localhost", 1050);
   String message = "Hello";

   Timer timer = new Timer();
   timer.scheduleAtFixedRate(new TimerTask() {
       @Override
       public void run() {
           sender.sendMessage(message);
       }
   }, 1000, 1000);
}

}

In the sendMessage method, we create a DatagramPacket and DatagramSocket, and send our message. Note that the close() method is used to close the DatagramSocket after the message is sent.

Each second the recipient's console displays the incoming "Hello" message sent by the sender. This means that our communication is all working correctly.