ruk·si

🐹 Go
TCP Server

Updated at 2015-01-17 12:02

Server program that accept TCP connections, creates representative clients of those connections, maintains a list of the clients, sends all clients a message every few seconds and prints all messages the clients send.

package main

import (
    "container/list"
    "fmt"
    "net"
    "runtime"
    "time"
)

/**
 * CLIENT
 */
type Client struct {
    Name      string
    Conn      net.Conn
    ReadChan  chan string
    WriteChan chan string
    CloseChan chan bool
}

func NewClient(name string, conn net.Conn) *Client {
    fmt.Printf("%s joined.\n", name)
    readChan := make(chan string)
    writeChan := make(chan string)
    closeChan := make(chan bool)
    newClient := &Client{name, conn, readChan, writeChan, closeChan}
    go newClient.ReadToReadChan()
    go newClient.WriteChanToWrite()
    return newClient
}

func (self *Client) Close() {
    close(self.CloseChan)
    self.Conn.Close()
    fmt.Printf("%s left.\n", self.Name)
}

func (self *Client) ReadToReadChan() {
    defer self.Close()
    var buffer [1024]byte
    for {
        bytesRead, errRead := self.Conn.Read(buffer[0:])
        if errRead != nil {
            fmt.Printf("! Client read error: %s.\n", errRead)
            return
        }
        request := string(buffer[:bytesRead])
        self.ReadChan <- request
    }
}

func (self *Client) WriteChanToWrite() {
    for {
        select {
        case <-self.CloseChan:
            // Exit func/goroutine if this client is closed.
            return
        case response := <-self.WriteChan:
            fmt.Printf("Server => %s : %s\n", self.Name, response)
            _, errWrite := self.Conn.Write([]byte(response))
            if errWrite != nil {
                fmt.Printf("! Client write error: %s.\n", errWrite)
                return
            }
        }
    }
}

/**
 * CLIENTS
 */
type Clients struct {
    list *list.List
}

func NewClients() *Clients {
    return new(Clients).Init()
}

func (self *Clients) Init() *Clients {
    self.list = list.New()
    return self
}

func (self *Clients) Count() int {
    return self.list.Len()
}

func (self *Clients) SendAll(msg string) {
    for entry := self.list.Front(); entry != nil; entry = entry.Next() {
        client := entry.Value.(*Client)
        client.WriteChan <- msg
    }
}

func (self *Clients) Add(client *Client) {
    self.list.PushBack(client)
}

func (self *Clients) RemoveOnClose(client *Client) {
    <-client.CloseChan // Wait for the client to close.
    for entry := self.list.Front(); entry != nil; entry = entry.Next() {
        otherClient := entry.Value.(*Client)
        if otherClient.Name == client.Name {
            self.list.Remove(entry)
        }
    }
}

/**
 * MAIN
 */
func main() {
    addr, _ := net.ResolveTCPAddr("tcp4", ":1234")
    listener, _ := net.ListenTCP("tcp", addr)
    var clients = NewClients()

    // Print connected clients and running goroutine count every second.
    infoTicker := time.NewTicker(time.Second)
    go func() {
        for {
            <-infoTicker.C
            c := clients.Count()
            g := runtime.NumGoroutine()
            fmt.Printf("%d clients online, running on %d goroutines.\n", c, g)
        }
    }()

    // Send Tick or Tock every 2 seconds.
    tickTockTicker := time.NewTicker(time.Second * 2)
    go func() {
        for {
            <-tickTockTicker.C
            clients.SendAll("Tick")
            <-tickTockTicker.C
            clients.SendAll("Tock")
        }
    }()

    // Each new client starts total of 4 goroutines, which of 2
    // are internal and added in NewClient().
    // 1: Listen to Conn.Read and direct it to ReadChan.
    // 2: Listen to WriteChan and direct it to Conn.Write.
    // 3: Remove client on client lists when it is closed.
    // 4: Act on messages received from ReadChan.
    clientNumber := 0
    for {
        conn, _ := listener.AcceptTCP()
        clientNumber++
        name := fmt.Sprintf("Client%d", clientNumber)
        client := NewClient(name, conn)
        clients.Add(client)
        go clients.RemoveOnClose(client)
        go handleClient(client)
    }
}

func handleClient(client *Client) {
    for {
        select {
        case <-client.CloseChan:
            // Exit func/goroutine if this client is closed.
            return
        case request := <-client.ReadChan:
            // Wait for requests and act based them.
            fmt.Printf("%s => Server : %s\n", client.Name, request)
            client.WriteChan <- "Pong"
        }
    }
}

Client spawn program that creates TCP connections in a regular interval.

package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    spanwInterval := time.Millisecond * 3000
    for {
        go ping()
        <-time.After(spanwInterval)
    }
}

func ping() {

    addr, errAddr := net.ResolveTCPAddr("tcp4", "localhost:1234")
    if errAddr != nil {
        fmt.Printf("Client addr error: %s\n", errAddr)
        return
    }

    conn, errConn := net.DialTCP("tcp", nil, addr)
    defer conn.Close()
    if errConn != nil {
        fmt.Printf("Client conn error: %s\n", errConn)
        return
    }

    // Wait for a message from the server.
    var buff [64]byte
    bytesTickerRead, errTickerRead := conn.Read(buff[0:])
    if errTickerRead != nil {
        fmt.Printf("Client read error: %s\n", errTickerRead)
        return
    }
    fmt.Printf("We got %s from the server\n", string(buff[:bytesTickerRead]))

    // Send a message to the server.
    msg := "Ping"
    _, errWrite := conn.Write([]byte(msg))
    if errWrite != nil {
        fmt.Printf("Client write error: %s\n", errWrite)
        return
    }

    // Wait for a message from the server.
    bytesPongRead, errPongRead := conn.Read(buff[0:])
    if errPongRead != nil {
        fmt.Printf("Client read error: %s\n", errPongRead)
        return
    }
    fmt.Printf("We got %s from the server\n", string(buff[:bytesPongRead]))
}

Sources