Monday, February 7, 2022

Java Tutorial: Networking

Chapters

Overview

In this tutorial, we're going to discuss networking basics in java. java.net package provides the classes for implementing networking applications. We're going to discuss some classes in that package. In this overview, I gonna give short definition about components that are important in creating a program that requires networking.

There are two types of transport protocols that we need to learn: TCP(Transmission Control Protocol) and UDP(User Datagram Protocol). These two protocols and other networking related components are explained in this article. Protocol is simply a set of rules on how two or more entities are communicating to one another. Examples of protocols: TCP, UDP, FTP, etc.

URL or Uniform Resource Locator, colloquially termed a web address, is a reference to a web resource that specifies its location on a computer network and a mechanism for retrieving it. A URL is a specific type of Uniform Resource Identifier (URI). Example of URL: https://www.google.com

URI or Uniform Resource Identifier is a unique sequence of characters that identifies a logical or physical resource used by web technologies. URIs may be used to identify anything, including real-world objects, such as people and places, concepts, or information resources such as web pages and books.

Port Number is a 16-bit unsigned integer, thus ranging from 0 to 65535. For TCP, port number 0 is reserved and cannot be used, while for UDP, the source port is optional and a value of zero means no port. Port numbers are like identifiers for port in computer networking. Ports are divided into three sections:
Port numbers from 0 to 1023 are system or well-known ports.
Port numbers from 1024 to 49151 are user or registered ports.
Port numbers from 49152 to 65535 are dynamic / private / ephemeral ports.
For beginners, registered ports are preferrable to be used.

Internet Protocol address(IP address) is a numerical label such as 192.0.2.1 that is connected to a computer network that uses the Internet Protocol for communication. An IP address serves two main functions: network interface identification and location addressing. There are two types of IP address: IPv4 and IPv6

media access control address(MAC address) is a unique identifier assigned to a network interface controller (NIC) for use as a network address in communications within a network segment. MAC addresses are recognizable as six groups of two hexadecimal digits, separated by hyphens, colons, or without a separator.

Network Socket is a software structure within a network node of a computer network that serves as an endpoint for sending and receiving data across the network. The structure and properties of a socket are defined by an application programming interface (API) for the networking architecture.

Working With URLs

URL is an acronym for Uniform Resource Locator and is a reference (an address) to a resource on the Internet. URL has two main components: Protocol Identifier and Resource Name. For example, this URL: http://www.example.com, has two main components: http or Hypertext Transfer Protocol is a protocol identifier. :// is a separator and www.example.com is a resource name.

Resource name can have these components depend entirely on the protocol used: Host Name, Filename, Port Number, Reference.
Take a look at this URL:
http://www.example.com:4444/showstats.html#delivery

www.example.com is a host name.
4444 is a port number.
showstats.html is a filename.
#delivery is a reference.
Most websites don't need port numbers in their URLs. In this topic about URLs, we're going to use the URL class.
Creating URL

The easiest way to create a URL object is from a String that represents the human-readable form of the URL address. We already know the components of URL, now let's discuss the type types of URL: absolute and relative URLs. Absolute URL contains complete information about URL like protocol identifier, host name, etc.

Relative URL is like a shortcut. It omits the left portion of an absolute URL. For example, http://www.example.com/delivery/stats.html is an absolute URL. delivery/stats.html is a relative URL. Relative URL is mostly used within HTML files.

Now that we know absolute and relative URLs, we can now create a URL. URL class provides constructors for creating URLs.

This example demonstrates creating URLs using URL class.
import java.net.URL;
import java.net.MalformedURLException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException{
    
    URL url1 = new URL("http://www.example.com");
    URL url2 = new URL(
    "https", "www.example.com", 
    "/delivery/stats.html");
    URL url3 = new URL(url1, "register.html");
    
    System.out.println("url1: " + url1);
    System.out.println("url2: " + url2);
    System.out.println("url3: " + url3);
  }
}

Result:
url1: http://www.example.com
url2: http://www.example.com/delivery/stats.html
url3: http://www.example.com/register.html
This constructor URL(String spec) creates a URL using spec String. url1 uses this constructor.

This constructor URL(String protocol, String host, String file) create a URL by combining multiple Strings that respectively denotes protocol identifier, host name and filename. We can use this constructor to make a relative URL become absolute URL. url2 uses this constructor.

This constructor URL(URL context, String spec) create a URL by getting another URL and then combine it with another String. This is another constructor used for making a relative URL become absolute. url3 uses this constructor.

URL class throws MalformedURLException. Make sure to explicitly handle it. Also remember that URL objects are immutable.

Parsing URL

URL class has accessor methods that parse URL. This example demonstrates some accessor methods that I'm talking about.
import java.net.URL;
import java.net.MalformedURLException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException{
    
    URL url = new URL("https://www.example.com:4444");
    
    System.out.println("Port: " + url.getPort());
    System.out.println("Authority: " + url.getAuthority());
    System.out.println("Protocol: " + url.getProtocol());
    System.out.println("Host: " + url.getHost());
    
  }
}

Result
Port: 4444
Authority: www.example.com:4444
Protocol: https
Host: www.example.com
More methods can be seen in the documentation.

Reading from a URL Object

We can read the content of a an HTML file represented by a URL. We can read HTML commands and textual content from the HTML file. To do this, we're going to use openStream() method provided by URL class.

This example demonstrates openStream() method.
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SampleClass{

  public static void main(String[] args)
                throws IOException, 
                MalformedURLException{
  
    URL url = new URL("https://www.google.com");
    
    try(BufferedReader bf = 
        new BufferedReader(
          new InputStreamReader(
            url.openStream()))){
        
        String content = null;
        while((content = bf.readLine()) != null)
          System.out.println(content);
    }
  }
}
Make sure you're connected to the internet and the website is online before executing the example above. Otherwise, an exception will be thrown. The example above might also hang or you might see an exception stack trace. If either of the latter two events occurs, you may have to set the proxy host.

If you want to try this example offline then you need to create a local server on your computer. You can use XAMPP to easily create a local server. Once you created a local server, run the server and change the URL in the example above to the URL of your local server. For example, if you're using XAMPP default settings, change the URL in the example above to this: http://localhost.

URLConnection Class

URLConnection is the superclass of all classes that represent a communications link between the application and a URL. This class is an abstract class so we can't instantiate it. However, we can use openConnection() method from URL class to instantiate an object related to this abstract class.

In URL class, we can parse the URL itself. In URLConnection we can parse headers of a document linked to a URL. We can also use URLConnection to read from and write to a server. More information on the request properties and header fields of an http connection can be found in this article.

This example demonstrates URLConnection accessor methods.
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       MalformedURLException{
    URL url = new URL("https://www.google.com");
    URLConnection uc = url.openConnection();
    
    System.out.println("Encoding: " +
    uc.getContentEncoding());
    System.out.println("content-type: " +
    uc.getContentType());
    System.out.println("cache-control: " +
    uc.getHeaderField("Cache-Control"));
    
  }
}

Result
Encoding: null
content-type: text/html; charset=ISO-8859-1
cache-control: private, max-age=0
In the result above, getContentEncoding() because content-encoding of the URL is not known.

URLConnection has subclasses: HttpURLConnection and JarURLConnection. HttpURLConnection is a URLConnection that specialized in HTTP documents. This class supports HTTP-specific features which are discussed in this article.

JarURLConnection that works on Jar URLs. HttpsURLConnection is a subclass of HttpURLConnection. HttpsURLConnection supports HTTPS-specific features.

Using URLConnection to Read from and Write to a URL

To read from and write to a URL using URLConnection, we need to use getOutputStream() to write data and getInputStream() to read data. We can also check if a URLConnection is set to do input and output by using getDoInput() and getDoOutput() methods.

Before testing the example below, assuming you're using apache server, create a directory in your document root directory and name it test. Then, create a php file and name it url_con_test.php. Then, put this in your php file.
<?php
echo str_shuffle($_POST['text']);
?>
This example demonstrates reading from and writing to a URL using URLConnection.
import java.net.*;
import java.io.*;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       MalformedURLException{
    URL url = new URL("http://localhost/test/url_con_test.php");
    
    URLConnection uc = url.openConnection();
    uc.setDoOutput(true);
    uc.setConnectTimeout(5000);
    uc.setReadTimeout(3000);
    
    uc.addRequestProperty
    ("Accept-Language","en-US, en-BR; q=0.8");
    
    String stringToShuffle = 
    new String("Brainy Ghosts".getBytes(), "UTF-8");
    
    OutputStreamWriter out =
    new OutputStreamWriter(uc.getOutputStream());
    out.write("text="+stringToShuffle);
    out.close();
    
    BufferedReader in = 
    new BufferedReader(
      new InputStreamReader(
        uc.getInputStream()));
    
    String result = null;
    while ((result = in.readLine()) != null)
      System.out.println
      (new String(result.getBytes(), "UTF-8"));
    in.close();
  }
}

Result(may vary)
Gnhsosy iatrB
By default, getDoOuput() of URLConnection returns false. If we want a URLConnection to write to a URL, we need to invoke setDoOuput() and set it to true. First off, we need to write to a URL. That's why we need to get the underlying output stream of a URLConnection first and then write a String.

setConnectTimeout method sets the connection timeout between client and server. Connection timeout happens when a server takes time to respond to client's request. In the example above, I set the maximum waiting time to 5000 milliseconds of 5 seconds.

If the server is not responding to client's request for more than 5 seconds, the connection will time out or in other words, client can't connect to server. By default, java sets connection timeout to 0. It means that a client will wait for the server indefinitely.

setReadTimeout method sets the read timeout between client and server. Read timeout happens when a client is blocked for a long time while reading to a server. In the example above, I set the maximum waiting time to 3000 milliseconds of 3 seconds.

If the client is blocked while reading for more than 3 seconds, client will stop reading to the server. By default, java sets read timeout to 0. It means that a client will indefinitely wait for the input stream to be unblocked.

addRequestProperty method adds a general request property specified by a key-value pair. This method will not overwrite existing values associated with the same key. If you want to overwrite existing values of a request property, use setRequestProperty method. You can find more HTTP headers in this article.

The written String will be shuffled in the server and returns the shuffled String. To get the shuffled String, we need to get the underlying input stream of URLConnection. Take note that some URLs may not support input/output. If the URL does not support output, getOutputStream() method throws an UnknownServiceException.

URL Encoding

URLs can only be sent over the Internet using the ASCII character-set. Characters outside of this character set are encoded by replacing character with a "%" followed by two hexadecimal digits. This type of encoding is called Percent-encoding.

Some characters in the ASCII set are not allowed in some part of a URL. For example, "#" character has special meaning in URL. Using it as part of a filename will make a malfunctioned URL that can't locate a specified resource. For example: http://localhost/myspace#1.html.

These characters will be replaced with valid ASCII format.
For example, for space character, it will be replaced with "+" or "%20". More information about URL encoding can be found in this article.

To encode and decode URLs in java, we're going to use URLEncoder and URLDecoder. Encoded URL is often used in the submission of HTML form data in HTTP requests.

This example demonstrates URLEncoder and URLDecoder.
import java.net.*;
import java.io.UnsupportedEncodingException;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException,
                       UnsupportedEncodingException{
  
    URL url1 = new URL
    ("http://localhost/action.php?text="+
    URLEncoder.encode("my jelly#jam","UTF-8"));
    
    URL url2 = new URL("http://localhost/my%20space%231.html");
    
    System.out.println
    ("Encoded: " + url1);
    
    System.out.println
    ("Decoded: " + 
    URLDecoder.decode(url2.toString(),"UTF-8"));
  }
}

Result
Encoded: http://localhost/action.php?text=my+jelly%23jam
Decoded: http://localhost/my space#1.html

URI Class

A URI is a uniform resource identifier while a URL is a uniform resource locator. Hence every URL is a URI, abstractly speaking, but not every URI is a URL. This is because there is another subcategory of URIs, uniform resource names (URNs), which name resources but do not specify how to locate them. This is an example of URN: mailto:java-net@www.example.com

The conceptual distinction between URIs and URLs is reflected in the differences between this class and the URL class. An instance of this class represents a URI reference in the syntactic sense defined by RFC 2396. A URI may be either absolute or relative. A URI string is parsed according to the generic syntax without regard to the scheme, if any, that it specifies.

No lookup of the host, if any, is performed, and no scheme-dependent stream handler is constructed. Equality, hashing, and comparison are defined strictly in terms of the character content of the instance. In other words, a URI instance is little more than a structured string that supports the syntactic, scheme-independent operations of comparison, normalization, resolution, and relativization.

An instance of the URL class, by contrast, represents the syntactic components of a URL together with some of the information required to access the resource that it describes.

A URL must be absolute, that is, it must always specify a scheme. A URL string is parsed according to its scheme. A stream handler is always established for a URL, and in fact it is impossible to create a URL instance for a scheme for which no handler is available.

Equality and hashing depend upon both the scheme and the Internet address of the host, if any; comparison is not defined. In other words, a URL is a structured string that supports the syntactic operation of resolution as well as the network I/O operations of looking up the host and opening a connection to the specified resource. More information about this class can be found in the documentation.

This example demonstrates URI class.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                throws MalformedURLException,
                       URISyntaxException{
    URL url = 
    new URL("http://localhost/my%20space/file%231.html");
    
    URI uri = url.toURI();
    
    System.out.println
    ("Is Absolute: " + uri.isAbsolute());
    
    System.out.println("Path: " + uri.getPath());
    
    System.out.println
    ("Raw Path: " + uri.getRawPath());
    
    URI uri2 = URI.create
    ("http://localhost/test1/test2/../testa/file.html");
    
    URL url2 = uri2.normalize().toURL();
    System.out.println("Normalized URL: " + url2);
    
    URI uri3 = URI.create
    ("http://localhost/test1");
    URI uri3a = 
    URI.create("http://localhost/test1/testb/fileb.html");
    System.out.println
    ("Relativize: "+uri3.relativize(uri3a));
    
    URI uri4 = URI.create
    ("http://localhost/");
    System.out.println
    ("Resolve: "
    +uri4.resolve(uri3.relativize(uri3a).toString()));
  }
}

Result
Is Absolute: true
Path: /myspace/file#1.html
Raw Path: /my%20space/file%231.html
Normalized URL: http://localhost/test1/testa/file.html
Relativize: testb/fileb.html
Resolve: http://localhost/testb/fileb.html

InetAddress Class

InetAddress class represents an Internet Protocol (IP) address. IP address has two versions: IPv4 and IPv6. InetAddress has Inet4Address and Inet6Address the represent IPv4 and IPv6 respectively.

This example demonstrates InetAddress, Inet4Address and Inet6Address.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                throws UnknownHostException{
    
    InetAddress inet = InetAddress.getByName("www.google.com");
    byte[] raw_ip = inet.getAddress();
    
    System.out.print("IP address(raw): ");
    
    for(int i = 0; i < raw_ip.length;i++)
      if(i < raw_ip.length-1)
        System.out.print(raw_ip[i]+".");
      else
        System.out.print(raw_ip[i]);
    System.out.println();
    
    Inet4Address inet4 = null;
    if(inet instanceof Inet4Address)
      inet4 = (Inet4Address)inet;
    else
      throw new NullPointerException("inet4 is null!");
      
    System.out.println
    ("IP Address(IPv4): " + inet4.getHostAddress());
    
    byte sixteen_octets[] =
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    Inet6Address inet6 = 
    Inet6Address.getByAddress("local",sixteen_octets,0);
      
    System.out.println
    ("IP Address(IPv6): " + inet6.getHostAddress());
    
    System.out.println("Is inet4 a loopback address? "
    + inet4.isLoopbackAddress());
    System.out.println("Is inet4 a multicast address? "
    + inet4.isMulticastAddress());
    
    System.out.println("Is inet6 a loopback address? "
    + inet6.isLoopbackAddress());
    System.out.println("Is inet6 a multicast address? "
    + inet6.isMulticastAddress());
    
  }
}

Result(may vary)
IP address(raw): -84.-39.25.4
IP address(IPv4): 172.217.25.4
IP address(IPv6): 0:0:0:0:0:0:0:1%0
Is inet4 a loopback address? false
Is inet4 a multicast address? false
Is inet6 a loopback address? true
Is inet6 a multicast address? false
In the result above, the raw IP address uses signed bytes. the IPv4 IP address uses unsigned bytes. IPv4 octets are separated by dot(.). IPv6 consists of eight(16-bits hexadecimal) pairs. Each pair is separated by colon(:). %0 is the scope_id which is explained in the documentation.

This IPv6 address in the example above is a localhost address that is also called as loopback address. IPv6 address that is not localhost may look like this: 1080:0:0:0:8:800:200C:417A

Operational characteristics of an IP address like unicast, multicast, anycast and others determine how an IP address transmits data between entities. Multicast address has scopes like Link-local, Global scope, etc. You can read some information about those scopes in this article.

NetworkInterface

NetworkInterface represents a Network Interface made up of a name, and a list of IP addresses assigned to this interface. A network interface is the point of interconnection between a computer and a private or public network. A network interface is generally a network interface card (NIC), but does not have to have a physical form.

Instead, the network interface can be implemented in software. For example, the loopback interface (127.0.0.1 for IPv4 and ::1 for IPv6) is not a physical device but a piece of software simulating a network interface. The loopback interface is commonly used in test environments.
Source: What Is a Network Interface?.

This example gets all available network interface in our machine and display their names.
import java.net.*;
import java.util.Enumeration;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    Enumeration<NetworkInterface> interfaces =
    NetworkInterface.getNetworkInterfaces();
    
    while(interfaces.hasMoreElements()){
      NetworkInterface netInterface =
      interfaces.nextElement();
      System.out.println(netInterface.getDisplayName()
      + " | " + netInterface.getName());
      
    }
  }
}
One of the interfaces in the example above is this: Software Loopback Interface 1 | lo
This network interface refers to localhost. localhost is not physical network interface. The interfaces in the example above are collections of physical and non-physical network interfaces.

getDisplayName() returns the display name of a network interface. The display name can be its name shown in our operating system. getName() returns the name of a network interface. The name is the "network terminology" name of the network interface.

We can use NetworkInterface to select particular network interface in our machine. Take a look at this example.
import java.net.*;

public class SampleClass{

  public static void main(String[] args)
                        throws Exception{
    NetworkInterface loopback = 
    NetworkInterface.getByName("lo");
    
    System.out.println
    ("Is lo loopback? " + loopback.isLoopback());
    
    System.out.println
    ("IP addresses of lo network interface");
    loopback.inetAddresses()
    .forEach((t) -> 
     System.out.println(t.getHostAddress()));
  }
}

Result
Is lo loopback? true
IP addresses of lo network interface
127.0.0.1
0:0:0:0:0:0:0:1
Assume we don't know the IP addresses of localhost. By using NetworkInterface, we can get the IPv4 and IPv6 IP addresses of localhost. More information about the methods we used in the example above and other information regarding NetworkInterface can be found in the documentation.

Programming TCP Sockets

URL and URLConnection provide a relatively high-level mechanism for accessing resources on the Internet. Sometimes your programs require lower-level network communication, for example, when you want to write a client-server application.

Socket and ServerSocket are TCP sockets that we're going to use in this topic. More information about java TCP Sockets can be found in this article.

Reading from and Writing to a Socket

To create a server-client app, we need two classes: Server and Client classes. Server class handles server codes and Client class handles client codes. In this topic, we're going use loopback address or the localhost in order to test this server-client app only using a single computer. Also, we're going to use port 4444 as our port. You can use different port if you want and make sure the port that you're using is not being used.

This example requires two JVMs. If you're on windows, open two cmds and run Server.java on one cmd and run Client.java on another. Explanation about this topic can be seen below the examples.

Codes for Server.java
import java.net.*;
import java.io.*;

public class Server{

  public static void main(String[] args)
                     throws IOException,
                            ClassNotFoundException{
                            
    System.out.println
    ("Connecting server to port 4444...");      
    
    ObjectInputStream reader = null;
    ObjectOutputStream writer = null;
    try(ServerSocket ss = new ServerSocket(4444)){
      System.out.println
      ("Server is connected to port 4444!");
    
      System.out.println("Waiting for a client...");
      Socket s = ss.accept();
      System.out.println("Client is connected!");
    
      System.out.println("Getting client output...");
      StringBuilder reversed = new StringBuilder();
      reader = new ObjectInputStream(s.getInputStream());
    
      String clientOutput = (String)reader.readObject();
      System.out.println("Client Output");
      System.out.println(clientOutput);
      System.out.println();
      
      System.out.println("Sending reversed text...");
      writer = new ObjectOutputStream(s.getOutputStream());
      writer.writeObject(
      new String(new StringBuilder(clientOutput)
      .reverse().toString().getBytes(),
      "UTF-8"));
      writer.flush();
    
      System.out.println("Requested output is sent!");
      System.out.println("Client feedback");
      System.out.println(reader.readObject());
    }
    finally{
      if(reader != null)
        reader.close();
      if(writer != null)
        writer.close();
    }
  }
}
Codes for Client.java
import java.net.*;
import java.io.*;

public class Client{

  public static void main(String[] args)
                     throws IOException,
                            ClassNotFoundException{
                            
    System.out.println
    ("Connecting client to localhost via"+
     " port 4444...");
    ObjectOutputStream writer = null;
    ObjectInputStream reader = null;
    try(Socket s = new Socket("localhost",4444)){
      System.out.println
      ("Client is connected to localhost!");
    
      System.out.println
      ("Writing text to client socket...");
      String content = "dog\nrat\nkit";
      
      writer = new ObjectOutputStream(s.getOutputStream());
      writer.writeObject
      (new String(content.getBytes(),"UTF-8"));
      writer.flush();
      
      System.out.println("Writing complete!");
    
      System.out.println
      ("Waiting for server response...");
      reader = new ObjectInputStream(s.getInputStream());
          
      String serverOutput = (String)reader.readObject();
    
      System.out.println("Reversed Text");
      System.out.println(serverOutput);
      
      writer.writeObject
      (new String("Great Service!".getBytes(),"UTF-8"));
      writer.flush();
    }
    finally{
      if(reader != null)
        reader.close();
      if(writer != null)
        writer.close();
    }
  }
}
Result for Server.java
Connecting server to port 4444...
Server is connected to port 4444!
Waiting for a client...
Client is connected!
Getting client output...
Client Output
dog
rat
kit

Sending reversed text...
Requested output is sent!
Client feedback
Great Service!
Result for Client.java
Connecting client to localhost via port 4444...
Client is connected to localhost!
Writing text to client socket...
Writing complete!
Waiting for server response...
Reversed Text
tik
tar
god
Execute Server.java first before Client.java to make this example work. In Server.java, I instantiated ServerSocket "ss". ServerSocket is just like server's ear, listening to any socket that will try to communicate with the server.

In Client.java, I instantiated Socket "s". To establish a connection between client and server, client needs to instantiate a Socket class with location(IP address) of the server and the port where the server is listening to incoming connection.

In the example above, I use the "localhost" hostname. localhost is a known hostname for a loopback address. Thus, java will have no problem translating that hostname to an IP address behind the scene. In IPv4 format, localhost IP address is 127.0.0.1. Once the socket is bound to the port where the server is listening, It will try to connect and identify itself to the server via ServerSocket. Once the server accepts the socket, a new socket will be created.

This new socket is bound to the same local port and also has its remote endpoint set to the address and port of the client. It needs a new socket so that it can continue to listen to the original socket for connection requests while tending to the needs of the connected client. accept() method returns the new socket.

Once the server has the socket that is linked to the client, we can now read from and write to their sockets. InputStream and OutputStream that we get from getInputStream() and getOutpuStream are continuous streams. It means that they will be active unless our program is terminated or we explicitly close the streams.

Continuous streams don't give EOL(End of Line) unless they're closed via close() method. Thus, methods that rely on EOL like readLine() method from BufferedReader won't return null even there are no more data in the input stream.

If the client or server writes to or read from its socket, it will be blocked and will wait for the other to read from or write to its socket. When the other one is reading from or writing to its socket, it will be also blocked. Once the data transmission is complete, the client and the server will be unblocked.

Remember that Socket and ServerSocket are TCP(Transmission Control Protocol) sockets. TCP provides reliable, ordered, and error-checked delivery of a stream of octets (bytes) between applications running on hosts communicating via an IP network.

If you just wanna transmit text(String), I think it's better to use DataInputStream and DataOutputStream and use readUTF() and writeUTF() methods. In this way, you will encounter less data compatibility issues than using ObjectOutputStream and ObjectInputStream

ServerSocket can accept multiple client requests. Although, these requests are processed in sequential manner. However, the server can process requests simultaneously through the use of threads—one thread for each client connection. The basic flow of logic in such a server is this:
while (true) {
    accept a connection;
    create a thread to deal with the client;
}
Programming UDP Sockets

The UDP protocol provides a mode of network communication whereby applications send packets of data, called datagrams, to one another. A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed. The DatagramPacket and DatagramSocket classes in the java.net package implement system-independent datagram communication using UDP.
Source: Lesson: All About Datagrams

In terms of performance, UDP is much faster than TCP because UDP doesn't need a dedicated connection between two entities. Thus, UDP is often called connection-less protocol. The downside of UDP is that it's unreliable. During transmission using UDP, Some data packets may be lost and data packets may not be ordered.

UDP is often useful for streaming like video and audio streaming. These services may tolerate the unreliability of UDP. For example, during video call with UDP, a few lost data packets are not very noticeable. If our video call app uses TCP, the video stream may hang because TCP needs to ensure that data packets are sent from one socket to another.

Reading from and Writing to a DatagramSocket

DatagramSocket represents a socket for sending and receiving datagram packets. DatagramPacket represents a datagram packet.

So, to create data transmission using UDP, we need DatagramSocket. This socket receives from and sends to another DatagramSocket. Then, DatagramPacket holds bytes of data that are going to be read from or write to DatagramSocket. Before executing the example below, make sure that the ports that you're using are not being used. In the example below, ports 5555 and 4444 are used. You can use different ports if you want.

Codes for Client.java
import java.net.*;

public class Client{

  public static void main(String[] args)
                        throws Exception{
    
    System.out.println("Creating Datagram Socket...");
    try(DatagramSocket ds = new DatagramSocket(4444)){
      System.out.println("Datagram Socket Created!");
      System.out.println();
      
      String clientMessage = 
      "Hello! Hope you're doing great!";
      
      System.out.println
      ("Creating Datagram Packet to be Sent...");
      DatagramPacket sender = 
      new DatagramPacket(
      clientMessage.getBytes(),
      clientMessage.length(),
      InetAddress.getByName("localhost"),
      5555);
      System.out.println("Datagram Packet Created!");
      System.out.println();
      
      System.out.println("Sending Datagram Packet...");
      ds.send(sender);
      System.out.println("Datagram Packet Sent!");
      System.out.println();
      
      System.out.println("Receive Server Feedback...");
      byte[] buffer = new byte[1024];
      DatagramPacket receiver =
      new DatagramPacket(buffer, 1024);
      ds.receive(receiver);
      String feedback = 
      new String(receiver.getData(),"UTF-8").trim();
      
      System.out.println("Server Feedback");
      System.out.println(feedback);
    }
    
  }
}
Codes for Server.java
import java.net.*;

public class Server{

  public static void main(String[] args)
                        throws Exception{
    System.out.println("Creating Datagram Socket...");
    try(DatagramSocket ds = new DatagramSocket(5555)){
      System.out.println("Datagram Socket Created!");
      System.out.println();
      
      System.out.println("Receive Client Message...");
      byte[] buffer = new byte[1024];
      DatagramPacket receiver =
      new DatagramPacket(buffer, 1024);
      ds.receive(receiver);
      String message = 
      new String(receiver.getData(),"UTF-8").trim();
      System.out.println("Client Message");
      System.out.println(message);
      System.out.println();
      
      String serverMessage = 
      "Hi! I'm doing great!";
      
      System.out.println
      ("Creating Datagram Packet to be Sent...");
      DatagramPacket sender = 
      new DatagramPacket(
      serverMessage.getBytes(),
      serverMessage.length(),
      InetAddress.getByName("localhost"),
      4444);
      System.out.println("Datagram Packet Created!");
      System.out.println();
      
      System.out.println("Sending Datagram Packet...");
      ds.send(sender);
      System.out.println("Datagram Packet Sent!");
      
      ds.close();
    }
  }
}
Result for Client.java
Creating Datagram Socket...
Datagram Socket Created!

Creating Datagram Packet to be Sent...
Datagram Packet Created!

Sending Datagram Packet...
Datagram Packet Sent!

Receive Server Feedback...
Server Feedback
Hi! I'm doing great!
Result for Server.java
Creating Datagram Socket...
Datagram Socket Created!

Receive Client Message...
Client Message
Hello! Hope you're doing great!

Creating Datagram Packet to be Sent...
Datagram Packet Created!

Sending Datagram Packet...
Datagram Packet Sent!
Execute Server.java first before Client.java to make this example work. First off, in Client.java, I created a DatagramSocket that is bound to port 4444. In Server.java, the DatagramSocket there is bound to port 5555. We don't need to bind a client program if it's only a sender. In the example above, the client program is both sender and receiver. If a DatagramSocket receives data, we need to bind it to a port.

DatagramPacket constructors determine if the DatagramPacket receives data from or sends data to DatagramSocket. For example, this constructor DatagramPacket(byte[] buf, int length) Constructs a DatagramPacket for receiving packets. buf is the byte container where received bytes are stored. length is the maximum number of packets to be sent to buf. length must be less than or equal to buf.length.

This constructor DatagramPacket(byte[] buf, int length, InetAddress address, int port) constructs a datagram packet for sending packets. buf is the byte container where bytes that are gonna be sent are stored. length is the maximum numbers of bytes to be sent. address is the location of the receiver. port is the port where the receiver is bound to.

More information can be found in the documentation. We use send(DatagramPacket packet) method of DatagramSocket to send datagram packet. We use receive(DatagramPacket packet) to receive the sent datagram packet. To retrieve the bytes of data in DatagramPacket, we use getData() method. Lastly, don't forget to close DatagramSocket objects once you're done using them.

For broadcasting server response to multiple clients. We can use MulticastSocket class. In this article, There's a guide on how to broadcast server response to multiple clients.

Handling Cookies

A CookieHandler object provides a callback mechanism to provide an HTTP state management policy implementation in the HTTP protocol handler.

That is, URLs that use HTTP as the protocol, new URL("http://example.com") for example, will use the HTTP protocol handler. This protocol handler calls back to the CookieHandler object, if set, to handle the state management.

Java Web Start and Java Plug-in have a default CookieHandler installed. However, if you are running a stand-alone application and want to enable HTTP state management, you must set a system-wide handler. We will use CookieManager to set a system-wide cookie handler.

CookieManager provides a concrete implementation of CookieHandler, which separates the storage of cookies from the policy surrounding accepting and rejecting cookies. A CookieManager is initialized with a CookieStore which manages storage, and a CookiePolicy object.

CookiePolicy implementations decide which cookies should be accepted and which should be rejected. Three pre-defined policy implementations are provided, namely ACCEPT_ALL, ACCEPT_NONE and ACCEPT_ORIGINAL_SERVER.

ACCEPT_ALL accepts all cookies.
ACCEPT_NONE accepts no cookies.
ACCEPT_ORIGINAL_SERVER only accepts cookies from the original server. This is the default cookie policy of a CookieManager instance.

This example demonstrates handling of cookies in java.
import java.net.*;
import java.util.List;

public class SampleClass{

  public static void main(String[] args)
                      throws Exception{
    CookieManager cm = new CookieManager();
    CookieHandler.setDefault(cm);
    
    URL url = new URL("https://www.google.com");
    
    URLConnection uc = url.openConnection();
    System.out.println("Domain content-type");
    System.out.println(uc.getContentType());
    System.out.println();
    
    CookieStore store = cm.getCookieStore();
    List<HttpCookie> cookieList =
    store.getCookies();
    
    for(HttpCookie cookie : cookieList){
      System.out.println
      ("Domain: " + cookie.getDomain());
      System.out.println
      ("Cookie Name: " + cookie.getName());
      System.out.println
      ("Max Age: " + cookie.getMaxAge());
      System.out.println();
    }
  }
}

Result(may vary)
Domain content-type
text/html; charset-ISO-8859-1

Domain: .google.com
Cookie Name: NID
Max Age: 15811199

Domain: .google.com
Cookie Name: 1P_JAR
Max Age: 2591999
First off, we instantiate a CookieManager instance. This constructor CookieManager() creates a CookieManager instance with CookieStore and CookiePolicy. Moreover, The default CookiePolicy of this new CookieManager instance is ACCEPT_ORIGINAL_SERVER, which only accepts cookies from the original server. So, the Set-Cookie response from the server must have a “domain” attribute set, and it must match the domain of the host in the URL. For more information, see HttpCookie.domainMatches().

Static method setDefault(CookieHandler cHandler) of CookieHander sets a system-wide cookie handler. "system-wide" means throughout our application. Now that we have a system-wide handler, the specified CookieManager will call CookieStore.add to save cookies for every incoming HTTP response, and call CookieStore.get to retrieve cookie for every outgoing HTTP request.

To get an HTTP response from the domain, we need to send a request to the domain. URLConnection methods like getContent, getContentType() and some methods produce a request that can be sent to a domain.

To get accepted cookies from CookieManager, we need to invoke getCookieStore method of CookieManager. This method returns the underlying CookieStore instance of CookieManager. Then, Once we have CookieStore instance, we invoke getCookies() method.

getCookies() method returns a list of HttpCookie. HttpCookie represents an HTTP cookie, which carries state information between server and user agent. In the example above, I just used getter methods like getDomain(), getName() and getMaxAge(). We can also modify values in a cookie by using some setter methods in the HttpCookie class.

In the example above, cookies are stored in memory. It means that those cookies will be gone once our application is terminated. If you want cookies in your application to be persistent, you need to write those cookies in a persistent storage like in a hard drive. This article gives some idea on how to store cookies in a persistent storage. It also discusses about custom CookiePolicy, which gives some idea on how to blacklist certain domains.

No comments:

Post a Comment