Installing the protoc
compiler was discussed in the last section.
It can then be run on the example specification file personv3.proto
by
protoc --java_out=java-src-dir personv3.proto
where java-src-dir
is where the generated files will be placed.
As the specification file gave the package name as person
,
a subdirectory will be created java-src-dir/person
with contents
Personv3.java
(note the capitalisation of the class name).
This file contains a definition of the class Personv3
in package
person
. For each message type in the prototype specification there
is an inner class, here
Personv3.Person
Personv3.Person.Name
Personv3.Person.Email
The
Introduction to Creational Design Patterns: Builder
design pattern has been adopted for the generated Java code.
So instead of e.g. a Person()
constructor,
you use a PersonBuilder()
to construct a builder,
which is then populated by its attributes using methods
such as setName()
and addEmail()
.
The Person
object is then created by calling
build()
on the builder.
Method chaining
(see
Method Chaining In Java with Examples) is used
to avoid repeated calls on the same object.
The advantage is that code such as
Personv3.Person.Name.Builder nameBldr = Personv3.Person.Name.newBuilder();
nameBldr.setPersonal("Jan");
nameBldr.setFamily("Newmarch");
Personv3.Person.Name name = nameBldr.build();
can be reduced to
Personv3.Person.Name name = Personv3.Person.Name.newBuilder()
.setPersonal("Jan")
.setFamily("Newmarch")
.build();
The methods to populate a NameBuilder
are
setPersonal()
and setFamily()
.
The methods to populate an EmailBuilder
are
setKind()
and setAddress()
,
while the methods to populate a PersonBuilder
are setName()
and for the repeated email field,
addEmail()
.
In addition, a Person
object has methods
byte[] toByteArray()
static Person parseFrom(byte[] data)
void writeTo(OutputStream output
static Person parseFrom(InputStream input)
The client creates the example Person
using the various builders.
It then connects to a server and sends the serialized binary form
on an OutputStream
to the server. Then it terminates.
The code is PersonClient.java:
import person.*;
import java.util.Arrays;
import java.io.*;
import java.net.*;
public class PersonClient {
public static final int SERVER_PORT = 2001;
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: Client address");
System.exit(1);
}
Personv3.Person.Name name = Personv3.Person.Name.newBuilder()
.setPersonal("Jan")
.setFamily("Newmarch")
.build();
Personv3.Person.Email email1 = Personv3.Person.Email.newBuilder()
.setKind("private")
.setAddress("jan@newmarch.name")
.build();
Personv3.Person.Email email2 = Personv3.Person.Email.newBuilder()
.setKind("work")
.setAddress("j.newmarch@boxhill.edu.au")
.build();
Personv3.Person person = Personv3.Person.newBuilder()
.setName(name)
.addEmail(email1)
.addEmail(email2)
.build();
String str = person.toString();
System.out.println("Sending: " + str);
InetAddress address = null;
try {
address = InetAddress.getByName(args[0]);
} catch(UnknownHostException e) {
e.printStackTrace();
System.exit(2);
}
Socket sock = null;
try {
sock = new Socket(address, SERVER_PORT);
System.out.println("Connected");
} catch(IOException e) {
e.printStackTrace();
System.exit(3);
}
OutputStream out = null;
try {
out = sock.getOutputStream();
} catch(IOException e) {
e.printStackTrace();
System.exit(5);
}
try {
person.writeTo(out);
} catch(IOException e) {
e.printStackTrace();
System.exit(6);
}
}
}
To build the client, you need to include the source for the client,
the generated Java files and the jar file for the Protobuf support.
With the person
directory, the jar file and the client file in the
same directory, the Linux compile command is
javac PersonClient.java person/Personv3.java -cp protobuf-java-3.12.2.jar
and the run command is
java -cp .:protobuf-java-3.12.2.jar PersonClient localhost
The server listens for connections and then reads a single Person
on the connection's InputStream
. It then prints the
Person
to stdout and terminates the connection. It is
PersonServer.java:
import person.*;
import java.util.Arrays;
import java.io.*;
import java.net.*;
public class PersonServer {
public static final int SERVER_PORT = 2001;
public static void main(String[] args){
ServerSocket s = null;
try {
s = new ServerSocket(SERVER_PORT);
} catch(IOException e) {
System.out.println(e);
System.exit(1);
}
while (true) {
Socket incoming = null;
try {
incoming = s.accept();
System.out.println("Connected");
} catch(IOException e) {
System.out.println(e);
continue;
}
handleSocket(incoming);
}
}
public static void handleSocket(Socket incoming) {
InputStream in;
try {
in = incoming.getInputStream();
} catch(IOException e) {
System.err.println(e.toString());
return;
}
Personv3.Person person;
try {
person = Personv3.Person.parseFrom(in);
System.out.println("Receiving: " + person.toString());
} catch(IOException e) {
System.err.println(e.toString());
return;
}
}
}
the Linux compile command is
javac PeersonServer.java person/Personv3.java -cp protobuf-java-3.12.2.jar
and the run command is
java -cp .:protobuf-java-3.12.2.jar PersonServer
Both client and the server print the Person
to stdout.
The output from each should be
name {
family: "Newmarch"
personal: "Jan"
}
email {
kind: "private"
address: "jan@newmarch.name"
}
email {
kind: "work"
address: "j.newmarch@boxhill.edu.au"
}
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.