In this past few days, I've tried to explore a programming language which is known as Go (GoLang). This language is developed by Google Inc. and released to public in 2009. From the historical background, the original authors of Go said that they hate C/C++ and want to create a new language which is more simple and concise. In my opinion, the syntax of Go is quite similar to C/C++ and Python. From IEEE Spectrum 2014, Go placed in the twentieth rank.
Let's dive into the codes :)
Hello World in Go language looks simply as the following:
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
And you can run it by typing (assuming hello.go is the filename):
go run hello.go
In this exploration, I'm trying to create a simple tracker which is able to check all available servers in the network.
The following code shows a simple server which will wait for a connection and write a simple "Hello World" to the connected client. Go provides a goroutine which is simple to use and similar to Thread in Java. See the following "server.go" codes:
package main
import (
"fmt"
"net"
)
func acceptConn(conn net.Conn) {
conn.Write([]byte("Hello World")) // convert a string "Hello World" to array of byte ([]byte)
}
func main() {
listenerTCP, err := net.Listen("tcp", "127.0.0.1:8000") // listen to 127.0.0.1 in port 8000
if err != nil {
// you can write your error handler here
}
for {
conn, err := listenerTCP.Accept() // this blocks until connection or error
if err != nil {
// you can write your error handler here
continue
}
fmt.Println("Start a new asynchronous task")
go acceptConn(conn) // a simple goroutine for each connection
}
}
In Go, you need to write "variableName datatype" instead of "datatype variableName". For example, you need to write "counter int" instead of "int counter" (in C/C++). If you want to declare a new variable, you need to use ":=" operators, while the next assignment can be handled by using "=" operator. The declaration itself is similar to Python, where you don't need to specify its datatype manually.
Let's move on to the client side. On the client side, you can simply connect to this server by the following codes:
package main
import (
"bytes"
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8000") // connect to server at 127.0.0.1 port 8000
if err != nil {
// you can write your error handler here
}
readBuf := make([]byte, 4096) // create an array of bytes (4096 bytes-sized)
conn.Read(readBuf) // read from server
length := bytes.Index(readBuf, []byte{0}) // get the length of "Hello World"
fmt.Println(string(readBuf[:length])) // print "Hello World" to screen
}
At this point, you should be able to understand the basic implementation of TCP connection in Go.
The second topic that we will discuss here is about Custom JSON. In Go, you can easily Marshal and Unmarshal a JSON if you have a predefined struct. For example, you have a simple JSON which looks like this:
{
"method": "a",
"value":
[
{“ip” : “192.168.1.2”, “port”: 8000},
{“ip” : “192.168.1.3”, “port”: 8000}
]
}
If you want to Unmarshal it easily, you need to create a struct as the following:
type ValueA struct {
Ip string json:"ip"
Port int json:"port"
}
type ValueB struct {
Method string json:"method"
Value []ValueA json:"value"
}
json.Unmarshal(...) will do the rest for you. The problem is that if the JSON is not well-defined. Fortunately, Go provides us with an "interface{}" to solve this problem. See the following code for better understanding:
// Convert []byte to map interface
var value map[string]interface{}
err := json.Unmarshal(readBuf[:length], &value) // readBuf[:length] is our JSON-formatted string from clients
if err != nil {
// you can write your error handler here
}
if value["method"] == nil { // check key "method" exists
// you can write your error handler here
}
method_string, ok:= value["method"].(string)
if !ok { // check if value["method"] is a string-formatted datatype
// you can write your error handler here
}
That's it! The rest of JSON conversion should be intuitive :)
Finally, we have arrived at the third topic, that's it, WaitGroup. This feature is used to wait for all processes (goroutines) to be executed before continuing to the next command. In this example, I want to wait for all clients' answers before sending out a broadcast message. The implementation can be seen as follows:
func HandleConnection() {
var wg sync.WaitGroup // you need to import "sync" to use this module
// retrieve rows from database
for rows.Next() {
err := rows.Scan(&address)
if err != nil {
// you can write your error handler here
}
wg.Add(1) // increment the wait group counter
go TimeoutCheck(address, &wg)
}
wg.Wait() // wait until all TimeoutCheck() finished
go broadcastAll() // asynchronous broadcast
}
func TimeoutCheck(address string, wg *sync.WaitGroup) {
defer wg.Done()
// Your execution of TimeoutCheck here
}
So, what is "defer" in the execution above? Defer in Go pushes a function call to the list (stack). The most easiest way to see it is that defer will be called during the specified time. For example, "defer wg.Done()" will be referenced / called after TimeoutCheck finishes all of its execution.
That's all for today! See you on the next blog post! Feel free to comment here and give your insight about this topic :D