当前位置 博文首页 > go语言实现聊天服务器的示例代码

    go语言实现聊天服务器的示例代码

    作者:Y_xx 时间:2021-06-26 17:43

    看了两天 go 语言,是时候练练手了。

    go 的 routine(例程) 和 chan(通道) 简直是神器,实现多线程(在 go 里准确的来说是 多例程)简直不要太轻松。

    于是动手码了一个傻瓜版的黑框聊天器。

    server 端:

    监听 TCP 连接;支持自定义客户端命令;支持消息分发;理论上支持广播;...

    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) + "已登录客户端列表:\n"
        i := 1
        for k := range clientSenders{
          a := ""
          if k == addr {
            a = "(我)"
          }
          cs = cs + strconv.Itoa(i) + ")" + k + a + "\n"
          ls += k + "\n"
          i ++
        }
        cs += "发送消息,可使用 1<-这是给1号客户端的消息\n(请使用英文以获取最佳体验)\n"
    
        senderChan <- cs
        time.Sleep(time.Millisecond * 300)
        senderChan <- ls
    
        // 发送格式化的列表
    
        fmt.Println("已发送“登录用户信息”", addr)
      } else{
        fmt.Println("客户端接受通道不存在", addr)
      }
    }
    
    func serve (conn *net.Conn){
      connect := *conn
    
      addr := connect.RemoteAddr().String()
    
      fmt.Println(addr, "接入服务")
    
      senderChan := make(chan string, 3)
      clientSenders[addr] = senderChan
    
      // 启动发送
      go send(addr, conn)
    
      // 发送当前用户信息
      go sendUsersInfo(addr)
    
      buff := make([]byte, 10240)
      for {
        n, err := connect.Read(buff)
        if err != nil {
          if err == io.EOF {
            fmt.Println("客户端断开链接,", addr)
            delete(clientSenders, addr)
            return
          } else{
            fmt.Println(err)
          }
        }
    
        msg := string(buff[:n])
    
        // 刷新客户端列表
        if msg == "ls\n" {
          go sendUsersInfo(addr)
          continue
        }
    
        // 提取数据
        msgs := strings.Split(msg, "<-")
        if len(msg) < 2{
          senderChan <- string("数据格式不正确,请联系开发者")
          continue
        }
    
        aimAddr := msgs[0]
        aimSender := clientSenders[aimAddr]
        if aimSender == nil {
          senderChan <- string("客户端已下线,使用 ls 命令获取最新的客户端列表")
          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
      }
    
      // 启动消息调度器
    
      defer listener.Close()
    
      // 启动连接监听
      for {
        conn, err := listener.Accept()
        if err != nil {
          fmt.Println(err)
          continue
        }
    
        go serve(&conn)
      }
    }
    
    

    客户端:

    支持断线重连;支持给特定其他客户端发信息

    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("尝试重连")
        go connectServer()
      }()
    
      connect := *conn2
      buff := make([]byte, 20140)
      for {
        n, err := connect.Read(buff)
        if err != nil {
          if err == io.EOF{
            fmt.Println("结束")
            (*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:
          // 解析客户端列表数据
          addrs = strings.Split(msg, "\n")
          fmt.Println("已接收客户端列表。\n")
          break
        default:
          fmt.Print(msg)
          break
        }
      }
    }
    
    func connectServer(){
      addr := "192.168.99.236:8080"
      fmt.Println("等待服务器开启中")
      conn2, err := net.Dial("tcp", addr)
      if err != nil {
        fmt.Print(err)
        fmt.Println("连接失败,10s后尝试")
        time.Sleep(10 * time.Second)
        go connectServer()
        return
      }
    
      fmt.Println("已连接")
    
      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("发送的姿势不正确,应该像这样 1<-给1号发送消息\n")
          continue
        }
    
        index, err := strconv.Atoi(msgs[0])
        if err != nil {
          fmt.Println("发送的姿势不正确,应该像这样 1<-给1号发送消息\n")
          continue
        }
    
        if len(addrs) <= index {
          fmt.Println("不存在第" + strconv.Itoa(index) + "个客户端\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()
        }
      }()
    }
    
    
    js
    下一篇:没有了