go language implementation chat server sample code

  • 2020-06-15 09:17:18
  • OfStack

After two days of reading go, it was time to get some practice.

go's routine (routines) and chan (channels) are artifacts, and implementing multithreading (multiple routines in go, to be precise) is not too easy.

So began to code a stupid version of the black box chat.

server side:

Listen for the TCP connection; Support custom client commands; Support message distribution; Theoretically support broadcasting; .


package main

import (
  "fmt"
  "net"
  "io"
  "strconv"
  "time"
  "strings"
)

const (
  NORMAL_MESSAGE = iota
  LIST_MESSAGE
)

var clientSenders = make(map[string] chan string)

func send (addr string, conn *net.Conn){
  senderChan := clientSenders[addr]
  for s := range senderChan{
    (*conn).Write([]byte(s))
  }
}

func sendUsersInfo(addr string){
  senderChan := clientSenders[addr]
  if nil != senderChan{
    ls := strconv.Itoa(LIST_MESSAGE)
    cs := strconv.Itoa(NORMAL_MESSAGE) + " List of logged in clients: \n"
    i := 1
    for k := range clientSenders{
      a := ""
      if k == addr {
        a = " (I) "
      }
      cs = cs + strconv.Itoa(i) + " ) " + k + a + "\n"
      ls += k + "\n"
      i ++
    }
    cs += " Send a message that can be used  1<- This is for 1 The number of client messages \n (Please use English for the best experience) \n"

    senderChan <- cs
    time.Sleep(time.Millisecond * 300)
    senderChan <- ls

    //  Send a formatted list 

    fmt.Println(" "Login User Information" sent ", addr)
  } else{
    fmt.Println(" The client acceptance channel does not exist ", addr)
  }
}

func serve (conn *net.Conn){
  connect := *conn

  addr := connect.RemoteAddr().String()

  fmt.Println(addr, " Access to the service ")

  senderChan := make(chan string, 3)
  clientSenders[addr] = senderChan

  //  Start sending 
  go send(addr, conn)

  //  Send current user information 
  go sendUsersInfo(addr)

  buff := make([]byte, 10240)
  for {
    n, err := connect.Read(buff)
    if err != nil {
      if err == io.EOF {
        fmt.Println(" The client disconnects, ", addr)
        delete(clientSenders, addr)
        return
      } else{
        fmt.Println(err)
      }
    }

    msg := string(buff[:n])

    //  Refresh the client list 
    if msg == "ls\n" {
      go sendUsersInfo(addr)
      continue
    }

    //  Extract the data 
    msgs := strings.Split(msg, "<-")
    if len(msg) < 2{
      senderChan <- string(" Incorrect data format, please contact developer ")
      continue
    }

    aimAddr := msgs[0]
    aimSender := clientSenders[aimAddr]
    if aimSender == nil {
      senderChan <- string(" Client is offline and in use  ls  Command gets the latest client list ")
      continue
    }

    aimSender <- strconv.Itoa(NORMAL_MESSAGE) + "[from:" + addr + "]:" + strings.Join(msgs[1:], "<-")
  }
}

func main(){
  addr := ":8080"
  listener, err := net.Listen("tcp", addr)
  if err != nil{
    fmt.Println(err)
    return
  }

  //  Start the message scheduler 

  defer listener.Close()

  //  Start connection listening 
  for {
    conn, err := listener.Accept()
    if err != nil {
      fmt.Println(err)
      continue
    }

    go serve(&conn)
  }
}

Client:

Support disconnection and reconnection; Supports sending messages to specific other clients


package main

import (
  "net"
  "fmt"
  "io"
  "os"
  "bufio"
  "sync"
  "time"
  "strings"
  "strconv"
)


var conn *net.Conn
var addrs []string

const (
  NORMAL_MESSAGE = iota
  LIST_MESSAGE
)

func read(conn2 *net.Conn){
  defer func() {
    fmt.Println(" Try to reconnect to ")
    go connectServer()
  }()

  connect := *conn2
  buff := make([]byte, 20140)
  for {
    n, err := connect.Read(buff)
    if err != nil {
      if err == io.EOF{
        fmt.Println(" The end of the ")
        (*conn2).Close()
        conn = nil
        return
      } else{
        fmt.Println(err)
      }
    }

    msg := string(buff[:n])
    t, err := strconv.Atoi(string(msg[0]))
    msg = msg[1:]

    switch t {
    case NORMAL_MESSAGE:
      fmt.Print(msg)
      break
    case LIST_MESSAGE:
      //  Parse client list data 
      addrs = strings.Split(msg, "\n")
      fmt.Println(" Received client list. \n")
      break
    default:
      fmt.Print(msg)
      break
    }
  }
}

func connectServer(){
  addr := "192.168.99.236:8080"
  fmt.Println(" Wait for the server to start ")
  conn2, err := net.Dial("tcp", addr)
  if err != nil {
    fmt.Print(err)
    fmt.Println(" Connection failed, 10s After trying to ")
    time.Sleep(10 * time.Second)
    go connectServer()
    return
  }

  fmt.Println(" The connected ")

  conn = &conn2
  go read(&conn2)
}

func send (){
  inputReader := bufio.NewReader(os.Stdout)
  for {
    input, err := inputReader.ReadString('\n')
    if err != nil {
      if err == io.EOF{
        return
      } else{
        fmt.Println(err)
      }
    }

    if input == "ls\n" {
      (*conn).Write([]byte(input))
      continue
    }

    msgs := strings.Split(input, "<-")
    if len(msgs) < 2 {
      fmt.Println(" The sending posture is not correct. It should look like this  1<- to 1 No. Send message \n")
      continue
    }

    index, err := strconv.Atoi(msgs[0])
    if err != nil {
      fmt.Println(" The sending posture is not correct. It should look like this  1<- to 1 No. Send message \n")
      continue
    }

    if len(addrs) <= index {
      fmt.Println(" There is no first " + strconv.Itoa(index) + " A client \n")
      continue
    }

    addr := addrs[index-1]

    input = addr + "<-" + strings.Join(msgs[1:], "<-")

    if nil != conn {
      (*conn).Write([]byte(input))
    }
  }
}

func main (){
  var wg sync.WaitGroup
  wg.Add(2)
  go connectServer()
  go send()
  wg.Wait()

  defer func() {
    if nil != conn {
      (*conn).Close()
    }
  }()
}


Related articles: