Go öğreniyorum - Gün 8 : Paketler - Bölüm 2

`Introducing Go` kitabından bugün öğrendiklerim

Paketlere devam ediyoruz.

Chapter 8

Packages: Containers

List

Doubly link (çift bağlı liste) uyarlamasıdır. Birbirine bağlı node’lar, her node’a ait bir value (değer) saklanması. Her node’un değeri ve önceki/sonraki node için bir pointer’ı var.

Doubly List List
package main

import ("fmt" ; "container/list")

func main() {
    var x list.List
    x.PushBack(1)
    x.PushBack(2)
    x.PushBack(3)

    for e := x.Front(); e != nil; e=e.Next() {
        fmt.Println(e.Value.(int))
    }
}

// 1
// 2
// 3

Liste için sıfır değerli boş listeden ibaret. Yeni liste oluşturma aşamasında x := list.New() şeklinde de atama yapılabilir. x.PushBack ile arka arkaya node’ları birbirine bağladık ve nil bulana kadar döngü içinde dolaştık.

Sort

Rastgele dizilmiş data’yı sıralamak için kullanılabilecek bir dizi fonksiyon içeren paket.

package main

import ("fmt" ; "sort")

type Person struct {
    Name string
    Age int
}

type ByName []Person

func (ps ByName) Len() int {
    return len(ps)
}
func (ps ByName) Less(i, j int) bool {
    return ps[i].Name < ps[j].Name
}
func (ps ByName) Swap(i, j int) {
    ps[i], ps[j] = ps[j], ps[i]
}

func main() {
    kids := []Person{
        {"Jill",9},
        {"Jack",10},
    }
    sort.Sort(ByName(kids))
    fmt.Println(kids) // [{Jack 10} {Jill 9}]
}

Sort fonksiyonu bir interface bekler. Bu interface’in 3 method’u uygulamış olması gerekir: Len, Less ve Swap. Örnekte kendi özel yöntemimizi oluşturduk: ByName ile. ByName bahsi geçen 3 method’u da implemente etti.

  • Len mutlaka sıramak istediğimiz şeyin uzunluğu/boyu olmalı.
  • Less karşılaştırma yaparken gelen iki elemanı ölçmek için.
  • Swap elemanları birbiriyle değişmek için.

Aynı mantıkta yaşa göre yani ByAge gerekseydi:

package main

import ("fmt" ; "sort")

type Person struct {
    Name string
    Age int
}

type ByName []Person
type ByAge []Person

func (ps ByName) Len() int {
    return len(ps)
}
func (ps ByName) Less(i, j int) bool {
    return ps[i].Name < ps[j].Name
}
func (ps ByName) Swap(i, j int) {
    ps[i], ps[j] = ps[j], ps[i]
}

func (this ByAge) Len() int {
    return len(this)
}
func (this ByAge) Less(i, j int) bool {
    return this[i].Age < this[j].Age
}
func (this ByAge) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

func main() {
    kids := []Person{
        {"Jill",9},
        {"Jack",10},
    }
    sort.Sort(ByName(kids))
    fmt.Println(kids) // [{Jack 10} {Jill 9}]

    sort.Sort(ByAge(kids))
    fmt.Println(kids) // [{Jill 9} {Jack 10}]
}

Hashes and Cryptography

Hash fonksiyonu, setler halinde input (girdi) alıp küçük sabit bir boyuta indirger. Golang’de hask fonkksiyonarı 2 kategoriye ayrılıyor: cryptographic ve non-cryptographic.

non-cryptographic

Bazıları: adler32, crc32, crc64 ve fnv. Şimdi crc32 örneğine bakalım:

package main

import (
    "fmt"
    "hash/crc32"
)

func main() {
    h := crc32.NewIEEE()
    h.Write([]byte("merhaba"))
    v := h.Sum32() // crc32 checksum
    fmt.Println(v) // 1994389773
}

Şimdi fiziksel 2 dosyanın hash’lerini karşılaştıralım:

package main

import (
    "fmt"
    "hash/crc32"
    "os"
    "io"
)

func getHash(filename string) (uint32, error) {
    // dosyayı aç
    f, err := os.Open(filename)
    if err != nil {
        return 0, err
    }
    // açılan dosyları mutlaka kapat!
    defer f.Close()

    // hash yapıcı oluştur
    h := crc32.NewIEEE()
    // dosyayı hash yapıcıya kopyala
    // - copy (dst, src) ve geriye (bytesWritten, error) döner
    if _, err := io.Copy(h, f); err != nil {
        return 0, err
    }
    // kaç byte döndüğü bizim için önemli değil, sadece hatayı
    // yakalamamız önemli
    // hatayı yakala
    return h.Sum32(), nil
}

func main() {
    h1, err := getHash("/tmp/test1.txt")
    if err != nil {
        return
    }
    h2, err := getHash("/tmp/test2.txt")
    if err != nil {
        return
    }
    fmt.Println(h1, h2, h1 == h2) // 1276159447 1707422633 false
}

cryptographic

Aslında aynı non-cryptographic gibidir ama en büyük farkı cryptographic olanıları çözmek (reverse etmek) neredeyse ikmansızdır. En meşhurlarda SHA-1’e bakalım:

package main

import (
    "fmt"
    "crypto/sha1"
)

func main() {
    // hasy yapıcı oluştur
    h := sha1.New()
    h.Write([]byte("test"))

    bs := h.Sum([]byte{})
    fmt.Println(bs) // byte-array
    // [169 74 143 229 204 177 155 166 28 76 
    //  8 115 211 145 233 135 152 47 187 211]
    // 20 byte = 20 * 8bit = 160-bit hash
}

test kelimesinin 160-bit’lik hash’ini ürettik!

Servers

Dağıtık ağ uygulamaları yazmak Golang’in dnasında bulunan birşey. En sık kullanılar 3 iletişim aracı: TCP, HTTP ve RPC konularına bakacağız.

TCP

Internet’in ana iletişim protokolü. Her yerde karşımıza çıkar. net paketinden gelen Listen fonksiyonu ile TCP sunucu yapabiliriz. Listen bir network tipi alıp net.Listener döner:

type Listener interface {
    // Accept; bekler ve sonraki bağlantıyı listener’a döner
    Accept() (c Conn, err error)

    // Close; listener’ı kapatır.
    // bloklanan Accept operasyonları bu sayede bloğu kalkar ve 
    // hataları döner.
    Close() error

    // Addr; listener’ın network adresini döner.
    Addr() Addr
}

Basit bir TCP client/server örneği. server.go adı ile kaydedip;

go run server.go

ile çalıştırabilirsiniz.

package main

import (
    "encoding/gob"
    "fmt"
    "net"
)

func server() {
    // listen on a port
    ln, err := net.Listen("tcp", ":9999")
    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 handleServerConnection(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:9999")
    if err != nil {
        fmt.Println(err)
        return
    }

    // send 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)
}

// Sending Hello, World
// Received Hello, World

HTTP

Hemen bir web sunucu yapalım. Aynı şekilde web_server.go olarak kaydedip:

go run web_server.go

Şimdi http://127.0.0.1:9000/hello

Web sunucusu 9000. porttan http isteği
package main

import (
    "io"
    "net/http"
)

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.ListenAndServe(":9000", nil)
}

Örneğin sunmak istediğimiz statik şeyler olsun, mesela css gibi. Şimdi, bir dizin oluşturup, web_server.go dosyasını bu dizin altına atalım. Aynı yere assets adında bir dizin oluşturalım ve için styles.css dosyası oluşturalım. Bu dosyada ;

body {
    background-color: black;
    color: white;
}

bilgileri olsun. Dizin yapısı aşağıdaki gibi olsun:

web_sunucumuz/
├── assets/
│   └── styles.css
└── web_server.go

şimdi statikleri sunmak için şu kod parçasını ekleyelim:

http.Handle(
    "/assets/",
    http.StripPrefix(
        "/assets/",
        http.FileServer(http.Dir("assets")),
    ),
)

Bu parçacık assets/ altındakileri sunmaya yarıyor. web_server.go dosyasının son hali:

package main

import (
    "io"
    "net/http"
)

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>
              <link rel="stylesheet" href="/assets/styles.css">
          </head>
          <body>
              Hello, World!
          </body>
        </html>`,
    )
}
func main() {
    http.HandleFunc("/hello", hello)
    http.Handle(
        "/assets/",
        http.StripPrefix(
            "/assets/",
            http.FileServer(http.Dir("assets")),
        ),
    )
    http.ListenAndServe(":9000", nil)
}
CSS sunan web sunucusu

RPC

Remote Procedure Call yani sunucu tarafında ya da uzaktaki bir makinede çalışan bir uygulamanın bir özelliğini çağırmak protokolü. Örneği rpc.go ismi ile kaydedelim:

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", ":9999")
    if err != nil {
        fmt.Println(err)
        return
    }
    for {
        c, err := ln.Accept()
        if err != nil {
            continue
        }
        go rpc.ServeConn(c)
    }
}
func client() {
    c, err := rpc.Dial("tcp", "127.0.0.1:9999")
    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.Negate(999) =", result)
    }
}
func main() {
    go server()
    go client()

    var input string
    fmt.Scanln(&input) // Server.Negate(999) = -999
}

Server, 9999. porttan dinlemededir. Server’ın Negate adlı method’una int64 cinsinden bir sayı geçiyoruz, Negate bu gelen sayıyı negatife çeviriyor. Çıktı: Server.Negate(999) = -999