Table of contents
Golang is one of the best languages known for its efficient performance and multithreading features which enable its users to use it while implementing various performance-driven features in their applications.
Created by Google in 2009 by its engineers after reviewing problems faced in the real world like waiting for another program to get compiled first to work on hte another one, as an initiative to solve these problems in a much more efficient way which eventually lead them in building a language that supports multithreading, concurrency, and performance under stress. Gaining a lot of importance in the Cloud-native world it became the popular choice for developers in building microservices. As a result of which several companies as well are heavily using it because of its seamless functionality and advantage. Some of the biggest companies which use it in their production work are:
Golang helps in writing distributed applications in a relatively straightforward way which helps in the frictionless building of web servers and web services. Here in this blog, we will be looking at the common approaches to communicating between multiple computers: TCP servers, HTTP servers and RPC.
TCP
Most of the time we are communicating on the internet it is directed by TCP protocol whether you are streaming video or movies on the internet or having a video chat with another person it is all governed by TCP (Transfer Control Protocol).
To create a TCP server we need to include the net package's listen function which takes a network type and an address and port to bind and returns a net. listener:
type Listener interface {
//Accept waits for and retruns the next connection to the listener.
Accept() (c Conn, err error)
//Close closes the listener.
//Any blocked Accept operations will be unlocked and retrun errors.
Close() error
//Addr returns the listener's network address.
Addr() Addr
}
We then after having the Listener, call Accept, which waits for a client to connect and returns a net. Conn. A net.Conn implements the io.Reader and io.Writer interfaces, so we can read from it and write it to just like a file.
package main
import(
"encoding/gob"
"fmt"
"net"
)
fucn server(){
//listen on a port
ln, err := net.listen("tcp", "8080")
if err != nil{
fmt.Println(err)
return
}
for {
//accept a connection
c, err := ln.Accept()
if err != nil{
fmt.Println(err)
continue
}
//handle the connection
go handServerConnection(c)
}
}
func handleServerConnection(c net.Conn){
//receive the message
var msg string
err := gob.NewDecoder(c).Decode(&msg)
if err != nil{
fmt.Println(err)
}else{
fmt.Println("Received", msg)
}
c.Close()
}
func client(){
//connect to the server
c, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil{
fmt.println(err)
return
}
//server the message
msg := "Hello, World"
fmt.Println("Sending", msg)
err = gob.newEncoder(c).Encode(msg)
if err != nil{
fmt.println(err)
}
c.Close()
}
func main(){
go server()
go client()
var input string
fmt.Scanln(&input)
}
Here in this program, we are using some of the packages which are used to perform certain functions like
the "
encoding/gob
" package which makes it easy to encode GO values so that other Go programs can read them. Additional encoding is possible in packages underneath encoding as well as in third-party packages."
net
" is already been described with its listener function above."
fmt
" function is sued for general purpose use which reduces the chance of having overlapping names, organizes our code so that it's easier to find code to edit, and speeds up the compiler by only requiring recompilation of smaller chunks of a program.In the provided code snippet,
ln
anderr
are used in theserver()
function for error handling and to establish a TCP listener on a specific port. Thenet.Listen()
function is used to create a TCP listener that listens for incoming connections on a specified network address. In this case, it listens on TCP network address "8080". The function returns anet.Listener
object (ln
) and an error (err
) if any occurred during the listener creation.The
ln.Accept()
function in the for loop is called to accept an incoming client connection. It waits until a client connects to the listener (ln
) and returns anet.Conn
object (c
) representing the connection and an error (err
) if any occurred during the acceptance process.gob.NewDecoder
andgob.NewEncoder
is used for serialization and deserialization of messages between the server and the client. Thegob
package provides functionality for encoding and decoding Go values in a compact binary format.gob.NewDecoder
is used in the coed to decode the message accepted from the client connection and thengob.NewEncoder
is used to deserialize the message into the binary form msg which is in the string format and variable in nature as defined in thehandleServerConnection
function.
HTTP
HTTP(Hyper Text Transfer Protocol) servers are even easier to set up and use:
package main
import ("net/http" ; "io")
func hello(res http.ResponseWriter, req *http.Request){
res.Header().Set(
"Content-Type",
"text/html",
)
io.WriteString(
res,
"<DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello, World
</body>
</html>"
)
}
func main(){
http.HandleFunc("/hello", hello)
http.ListenAndServer(":9000", nil)
}
HandleFunc
handles a URL route (/hello) by calling the given function. We can also handle static files by using FileServer
:
http.Handle(
"/assets/",
http.Stripeprefix(
"/assets/",
http.FileServer(http.Dir("assets")),
),
)
Again here, there are a lot of sets and apckages being used in the code representing different functionality of the program as:
req *http.Request
is used as a parameter in thehello
function to represent the incoming HTTP request. Thehttp.Request
type provides access to various information about the client's request, such as headers, URL parameters, and request body.res.Header()
is used to access the HTTP response headers, specifically the headers of thehttp.ResponseWriter
object. TheHeader()
method returns anhttp.Header
object, which represents the collection of headers associated with the response.res.Header().Set("Content-Type", "text/html")
sets theContent-Type
header of the response to"text/html"
. TheContent-Type
header specifies the type of content being returned by the server. In this case, it indicates that the response will be in HTML format.The
io.WriteString(res, ...)
function is used to write a string response to the client. It takes thehttp.ResponseWriter
object (res
) and the string content to be written as its arguments.
Then the FileServer which we used to handle static files also have certain keywords and functions for different uses:
http.Handle
is used to associate the/assets/
URL path with an HTTP handler. The HTTP handler being used ishttp.StripPrefix
, which removes the/assets/
prefix from the URL path before serving the static files.By combining
http.StripPrefix
andhttp.FileServer
in this manner, you create a handler that serves static files from the "assets" directory, but removes the/assets/
prefix from the URL path when looking for the corresponding file.http.Dir("assets")
function call creates anhttp.Dir
object, which represents a directory on the file system. In this case, it specifies the directory named "assets" from which the static files will be served.http.FileServer(http.Dir("assets"))
: Thehttp.FileServer
function creates an HTTP handler that serves static files from the provided directory. It takes anhttp.Dir
object as an argument to specify the directory containing the static files.
RPC
The net/rpc (remote procedure call) and net/rpc/jsonrpc packages provide an easy way to expose methods so they can be invoked over a network rather than just running in the program.
package main
import (
"fmt"
"net"
"net/rpc"
)
type Server struct{}
func (this *Server) Negate(i int64, reply *int64) error{
*reply = -i
return nil
}
func server(){
rpc.Register(new(Server))
ln, err := net.Listen("tcp", ":8080")
if err != nil{
fmt.Println(err)
return
}
for{
c, err := ln.Accept()
if err != nil{
continue
}
go rpc.ServerConn(c)
}
}
func client(){
c, err := rpc.Dial("tcp", "127.0.0.1:8080")
if err != nil{
fmt.Println(err)
return
}
var result int64
err = c.Call("Server.Negate", int64(999), &result)
if err != nil{
fmt.Println(err)
} else {
fmt.Println("Server.Neagte(999)=", result)
}
}
func main(){
go server()
go client()
var input string
fmt.Scanln(&input)
}
In this section there are a lot of things and functions being used in some different way than the normal format which proves to be executing the main function for which many developers use Golang which were mentioned at the beginning of the blog which are concurrency and multithreading:
At the start of the
main
functiongo server()
&go client()
are used as, go instructs the function to use those functions concurrently as goroutine.Goroutines are lightweight concurrent threads of execution that can run concurrently with other goroutines, allowing for concurrent and parallel execution. By launching both the server and client functions as goroutines, the
main
function can continue its execution without being blocked by the server or client operations. This way, the server and client can operate concurrently and independently.The subsequent
fmt.Scanln(&input)
statement is used to keep the main function from exiting immediately. It waits for user input from the console, allowing the server and client goroutines to continue running until user input is provided.
Now in the general code after making a call of the different functions in the main function.
The purpose of the
Negate
method is to allow remote procedure calls (RPC) to the server. When the client makes an RPC call to theNegate
method on the server, passing an integer value as the argument, the server will negate the value and store the result in thereply
parameter. The modifiedreply
value is then returned to the client.rpc.Register(new(Server))
registers an instance of theServer
struct as an RPC service. It allows the methods defined on theServer
struct (such as theNegate
method) to be accessible for remote procedure calls.By calling
rpc.Register
withnew(Server)
, an instance of theServer
struct is created and registered as an RPC service. This means that when a client makes an RPC call to the server, it can invoke methods on this registered instance ofServer
.In the client code,
c.Call
("Server.Negate", int64(999), &result)
makes an RPC call to the server'sNegate
method, passing999
as the value to be negated. The negated value is then stored in theresult
variable on the client side.go rpc.ServeConn(c)
:Starts an RPC server on the provided connection (
c
). It handles incoming RPC requests from the client and dispatches them to the appropriate registered service (in this case, theServer
instance). By usinggo
beforerpc.ServeConn(c)
, it runs the server method concurrently in a goroutine. This allows the server to handle multiple client connections simultaneously and ensures that the server doesn't block the main execution flow.The
rpc.ServeConn
function listens for RPC requests on the given connection (c
) and serves them accordingly. It waits for an incoming request, determines which RPC method is being called, and invokes the appropriate method on the registered service.
Parsing Command-Line Arguments
When we invoke a command on the terminal, it's possible to pass that command argument.
go run myfile.go
We have seen this code already and myfile.go are arguments. We can also pass flags to a command as
go run -v myfile.go
The flag package allows us to parse arguments and flags sent to our program. Here's an example for generating numbers from 1 to 100 as
package main
import ("fmt"; "flag"; "math/rand")
func main(){
//Define flags
maxp := flag.Int("max", 100, "the max value")
//Parse
flag.Parse()
//Generate a number between 0 and max
fmt.Println(rand.Intn(*maxp))
}
Any additional non-flag arguments can be retrieved with a flag.Args(), which returns a []string.
If you are a freshman who just started or going to start using Go more frequently then please read my other blogs as well which will surely help you a lot in the introduction of this language at least 😄
Soon, I will be publishing a blog on the use of testing in Go, Unit test, and Integration Test and how can we create and use them to make our code more productive
Hope you get to learn some from this blog for which you came here 😄
If you like my Article then please react to it and connect with me on Twitter if you are also a tech enthusiast. I would love to collaborate with people and share the experience of tech😄😄.
My Twitter Profile: