-
Book Overview & Buying
-
Table Of Contents
Java Web Internals
By :
Sockets are operating system resources for communicating information between remote applications. They abstract the addressing of an application within the operating system and provide a bidirectional channel for this information exchange. To do this, we need to understand that there are two types of sockets:
The focus of this chapter is not to exhaust the subject of network communication using Java. It is just to show an example of how we can create a way for two entities to communicate through a connected protocol (TCP). If you want to delve deeper into network programming concepts, an excellent reference is the book Learning Network Programming with Java: Harness the hidden power of Java to build network-enabled applications with lower network traffic and faster processes, by Richard M. Reese, available in the Packt catalog. Also, if you are interested in Java fundamentals, a great reference is the book Learn Java 17 Programming: Learn the fundamentals of Java Programming with this updated guide with the latest features, Second Edition, by Nick Samoylov, also available in the Packt catalog.
In Java, the use of TCP sockets is simplified, allowing you to create very robust applications.
Observe the following figure, where we apply the concept of connected sockets to a client/server application.

Figure 1.6: Socket and server socket
For us to correctly identify a process on a machine, we need two fundamental pieces of information – the machine's IP address (or its hostname) and the port:
1 to 65535) uniquely identifies the process on the machine)In our example, CalcServer (as we will call our server application) creates a ServerSocket (step 1 in the preceding figure) by assigning it the port on which it will wait for connections. This function is exclusive to this class. The client application (which we will call CalcClient) also creates a socket (step 2) and assigns the remote server address (IP address and port) for connection (step 3). CalcServer receives the connection request and accepts it (step 4), creating another socket, which will be responsible for the effective exchange of messages (step 5), exactly as in the following scheme. Finally, both entities can send and receive messages by reading from and writing to sockets (step 6).
So, let's implement the code, starting with our communication protocol.
In this case, two classes are necessary: the Request class and the Response class to enable communication operations. Note that both classes implement the java.io.Serializable interface so that objects are converted to byte arrays when transmitted over the network. This is because, in this example, we are using a Java application to communicate with another Java application. If we eventually want something more flexible, allowing clients developed in other languages to communicate with our server made in Java, we will choose to serialize the objects to text (for example, a JSON format) rather than to bytes, to increase compatibility.
These conversions (from objects to byte arrays, and vice versa) are called marshaling and unmarshaling. We have the following details:
Let us explore the definition of our Request class in this code snippet:
Class Request.java
public record Request(Double op1, Double op2, String oper) {
}
Now, we can explore our Response class definition in the following code:
Class Response.java
public record Response(String status, Double value){
}
Now, let us understand how CalcServer works. In general terms, every server has a common behavior: it works in an infinite loop, accepting requests, manipulating their data, and generating responses, exactly like in the request/response model we saw earlier. The following algorithm depicts its behavior.
Create server socket to listen and bind it to a port number
while (true) {
Accepts new connections to a new socket
Reads data from new socket and assign to a request object
Creates a response object
Handle data (in this case analyzing operator and operands
If operator is supported and operands are correct, performs the operation and sets the response status to "OK" and the operation result
If an operand is incorrect (e.g., division by zero), sets an "Invalid Operand error" on response and result is null
If an operand is not supported, sets an "Unsupported Operation" status and result is null
Sends the Response object to sender
}
The complete source code of this server is available in the following GitHub repository: https://github.com/PacktPublishing/Java-Web-Internals/tree/main/Chapter01.
If we analyze the source code, we can quickly identify each algorithm step. Every step is commented in the source code, and you can identify them easily:
// step 1
ServerSocket serverSocket = new ServerSocket(8350);
// step 2
while (true) {
// step 2.1
Socket socket = serverSocket.accept();
// step 2.2
ObjectInputStream in = new ObjectInputStream(
socket.getInputStream());
Request req = (Request)in.readObject();
// step 2.3
Response rep;;
// step 2.4 - handling data
switch(req.getOper()) {
// other cases to handle ...
case "/":
// step 2.5
if (req.getOp2() != 0) {
rep = new Response("Ok",req.op1()/req.op2());
rep.setStatus("Ok");
rep.setValue(req.getOp1()/req.getOp2());}
// step 2.6
else {
rep = new Response("Invalid", null);
}
break;
default:
// step 2.7
rep = new Response("Unsupported", null);
}
// step 2.8
ObjectOutputStream out = new ObjectOutputStream(
socket.getOutputStream());
out.writeObject(rep);
}
Finally, let's explore our CalcClient class code. Its objective is only to interact with the user, receiving (according to the desired operation) the value of each operand, assembling the request, sending it, and waiting for the response to finally display it. The following algorithm describes the Client application:
Request object with that data.Server application.Request object.The complete source code is available on GitHub at https://github.com/PacktPublishing/Java-Web-Internals/tree/main/Chapter01/CalcClient/, and we can identify each step as follows:
void main(){
// step 1
String oper;
Double op1, op2=null;
oper = scanner.nextLine();
op1 = Double.parseDouble(scanner.nextLine());
// step 1.1
switch(oper) {
case "+":
case "-":
case "*":
case "/":
case "^":
op2 = Double.parseDouble(scanner.nextLine());
break;
}
// step 2
Request req = new Request(op1, op2, oper);
// step 3 – If you know your IP Address, you can use
// it. It proves the packet actually hits
// the whole network stack
Socket socket = new Socket("localhost", 8350);
// step 4
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(req);
// step 5
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Response rep = (Response)in.readObject();
// step 6
System.out.println(rep);
}
Okay, the code is understandable. But how do we implement this in practice?
In Java, if you use an IDE such as Eclipse, NetBeans, or IntelliJ, I suggest you create three different projects:
CalcServerCalcClientThe key point is to reference, in both projects (CalcServer and CalcClient), the project (or library) that contains your protocol classes. This way, you maintain a single code base (without duplication) and can make communication between the server and the client compatible.
You must import each project separately in your Eclipse IDE (protocol, server, and client). To make the server and client use the protocol classes (Request and Response), you configure the build path:
Project Properties → Build Path → Configure Build Path
On the screen, go to the Projects tab, and in the Classpath item, add the CalcProtocol project, as depicted in the following figure.

Figure 1.7: Configuring the project build path on Eclipse
Remember that you must do the same operation on the CalcServer project.

Figure 1.8: Project structure on Eclipse
If you are not using an IDE to develop your applications, you can do it manually using command-line tools. It is necessary for you to have practice with CLI tools (bash, Windows command line, PowerShell, and others).
The protocol project (which contains Request and Response classes) is a common module for the other two projects (Server and Client). If you want to reuse it, the best alternative is to generate a JAR file for this project as follows. Step 1: you must go to your project.
javac -d bin *.java
jar –create –file protocol.jar -C bin
javac -cp <your protocol jar file> -d bin src/*.java
java -cp <your protocol jar file> bin/ServerClass
Previously, open another terminal window.
javac -cp <your protocol jar file> -d bin src/*.java
java –cp <your protocol jar file> bin/ClientClass
Now, regardless of which option you choose, let us run our applications. You must first run the server project (because it creates the socket that listens for connections), followed by the client project to interact with the user and send/receive messages.
The following figures show both application behaviors. Figure 1.9 depicts the server showing no console interaction, waiting for the client's request:

Figure 1.9: Server running showing no messages waiting for the client's request
Figure 1.10 shows the client interacting with the user, who inputs the operation (+) and two operands (3 and 8).

Figure 1.10: Client interacting with the user
Figure 1.11 depicts the server receiving the client's request.

Figure 1.11: Server receiving client's request and printing messages on the console
Figure 1.12 depicts the client printing the result output.

Figure 1.12: Client receiving server's response and outputting the result
Change the font size
Change margin width
Change background colour