logo_tu 发表于 2019-12-21 17:47

go语言聊天室

本帖最后由 logo_tu 于 2019-12-23 11:14 编辑

今天玩go语言,发现个聊天室挺好玩了,自己在稍微改装了一下。发给大家玩玩。
现在是12-23号上午,刚才测试了,亲测win7 win10能用

服务端
package main

import (
      "net"
      "fmt"
      "strings"
      "time"
      "io/ioutil"
)

// 创建用户结构体类型!
type Client struct {
      C    chan string
      Name string
      Addr string
}

// 创建全局map,存储在线用户
var onlineMap mapClient

// 创建全局 channel 传递用户消息。
var message = make(chan string)

func WriteMsgToClient(clnt Client, conn net.Conn) {
      // 监听 用户自带Channel 上是否有消息。
      for msg := range clnt.C {
                conn.Write([]byte(msg + "\n"))
      }
}

func MakeMsg(clnt Client, msg string) (buf string) {
      buf = clnt.Name + ": " + msg
      return
}

func HandlerConnect(conn net.Conn) {
      defer conn.Close()
      // 创建channel 判断,用户是否活跃。
      hasData := make(chan bool)

      // 获取用户 网络地址 IP+port
      netAddr := conn.RemoteAddr().String()
      // 创建新连接用户的 结构体. 默认用户是 IP+port
      clnt := Client{make(chan string), netAddr, netAddr}

      // 将新连接用户,添加到在线用户map中. key: IP+port value:client
      onlineMap = clnt

      // 创建专门用来给当前 用户发送消息的 go 程
      go WriteMsgToClient(clnt, conn)

      // 发送 用户上线消息到 全局channel 中
      //message <- "[" + netAddr + "]" + clnt.Name + "login"
      message <- MakeMsg(clnt, "上线!!!")

      // 创建一个 channel , 用来判断用退出状态
      isQuit := make(chan bool)

      // 创建一个匿名 go 程, 专门处理用户发送的消息。
      go func() {
                buf := make([]byte, 4096)
                for {
                        n, err := conn.Read(buf)
                        if n == 0 {
                              isQuit <- true
                              fmt.Printf("检测到客户端:%s退出\n", clnt.Name)
                              return
                        }
                        if err != nil {
                              fmt.Println("conn.Read err:", err)
                              return
                        }
                        // 将读到的用户消息,保存到msg中,string 类型
                        msg := string(buf[:n-2])
                        // 提取在线用户列表
                        if strings.Trim(msg, " ") == "who" {
                              conn.Write([]byte("online user list:\n"))
                              // 遍历当前 map ,获取在线用户
                              for _, user := range onlineMap {
                                        userInfo := user.Addr + ":" + user.Name + "\n"
                                        conn.Write([]byte(userInfo))
                              }
                              // 判断用户发送了 改名 命令
                        } else if len(msg) >= 8 && msg[:6] == "rename" { // rename|
                              newName := strings.Split(msg, "|") // msg
                              clnt.Name = newName                   // 修改结构体成员name
                              onlineMap = clnt             // 更新 onlineMap
                              conn.Write([]byte("rename successful\n"))
                        } else {
                              // 将读到的用户消息,写入到message中。
                              message <- MakeMsg(clnt, msg)
                        }
                        hasData <- true
                }
      }()

      // 保证 不退出
      for {
                // 监听 channel 上的数据流动
                select {
                case <-isQuit:
                        delete(onlineMap, clnt.Addr)       // 将用户从 online移除
                        message <- MakeMsg(clnt, "logout") // 写入用户退出消息到全局channel
                        return
                case <-hasData:
                        // 什么都不做。 目的是重置 下面 case 的计时器。
                case <-time.After(time.Second * 60):
                        delete(onlineMap, clnt.Addr)                // 将用户从 online移除
                        message <- MakeMsg(clnt, "time out leaved") // 写入用户退出消息到全局channel
                        return
                }
      }
}
func check(e error) {
      if e != nil {
                panic(e)
      }
}
func Manager() {
      // 初始化 onlineMap
      onlineMap = make(mapClient)

      // 监听全局channel 中是否有数据, 有数据存储至 msg, 无数据阻塞。
      for {
                msg := <-message

                var d1 = []byte(msg)
                err2 := ioutil.WriteFile("./wechart.log", d1, 0666) //写入文件(字节数组)
                check(err2)

                // 循环发送消息给 所有在线用户。要想执行,必须 msg := <-message 执行完, 解除阻塞。
                for _, clnt := range onlineMap {
                        clnt.C <- msg
                }
      }
}

var URL = "0.0.0.0:8888"

func main() {

      // 创建监听套接字
      listener, err := net.Listen("tcp", URL)
      if err != nil {
                fmt.Println("Listen err", err)
                return
      }
      defer listener.Close()
      println("服务器创建成功...")
      // 创建管理者go程,管理map 和全局channel
      go Manager()

      // 循环监听客户端连接请求
      for {
                conn, err := listener.Accept()
                if err != nil {
                        fmt.Println("Accept err", err)
                        return
                }
                // 启动go程处理客户端数据请求
                go HandlerConnect(conn)
      }
}


用户端
package main

import (
      "fmt"
      "net"
      "os"
      "strings"
)

func main() {
      fmt.Println("正在连接服务器……")
      conn, err := net.Dial("tcp", "47.95.242.247:8888")
      if err != nil {
                fmt.Println("net.Dial error:", err)
                return
      }
      defer conn.Close()
      fmt.Println("连接服务器成功")

      //fmt.Println("先起一个名字吧:")
      //var userName string
      ////使用Scan输入,不允许出现空格
      //_, _ = fmt.Scan(&userName)
      //_, _ = conn.Write([]byte(userName))

      buf2 := make([]byte, 4096)
      _, err = conn.Read(buf2)
      if err != nil {
                fmt.Println("conn.Read error:", err)
                return
      }
      // 客户端收到“你好,***,欢迎来到够浪聊天室,请畅所欲言!”
      //      fmt.Println(string(buf2[:n]))
      fmt.Println("⚠提示:长时间没有发送消息会被系统强制踢出")
      // 获取用户 网络地址 IP+port
      netAddr := conn.LocalAddr().String()
      //客户端发送消息到服务器
      go func() {
                for {
                        buffer1 := make([]byte, 4096)
                        //这里使用Stdin标准输入,因为scanf无法识别空格
                        n, err := os.Stdin.Read(buffer1)
                        if err != nil {
                              fmt.Println("os.Stdin.Read error:", err)
                              continue
                        }
                        _, _ = conn.Write(buffer1[:n]) //写操作出现error的概率比较低,这里省去判断
                }
      }()
      //接收服务器发来的数据
      for {
                buffer2 := make([]byte, 4096)
                n, err := conn.Read(buffer2)
                if n == 0 {
                        fmt.Println("服务器已关闭当前连接,正在退出……")
                        return
                }
                if err != nil {
                        fmt.Println("conn.Read error:", err)
                        return
                }
                fmt.Print(strings.ReplaceAll(string(buffer2[:n]), netAddr, "我"))

      }

}


部服务端已经部署到服务器了,可以在线聊天哦。


客户端已经打包好了 地址 https://www.lanzouj.com/i87xfha






h2909198 发表于 2019-12-21 18:56

logo_tu 发表于 2019-12-21 17:51
ip暴露给大家了,不过应该没关系 ^_^

一会就有大佬顺着网线找你玩^_^

logo_tu 发表于 2019-12-21 17:51

ip暴露给大家了,不过应该没关系 ^_^

熊猫咖啡 发表于 2019-12-21 17:59

作为小白,看不懂,下载下来看看

logo_tu 发表于 2019-12-21 18:00

熊猫咖啡 发表于 2019-12-21 17:59
作为小白,看不懂,下载下来看看

来啊,坐等在线聊天

龙王的猫 发表于 2019-12-21 18:06

我进来了

logo_tu 发表于 2019-12-21 18:08

龙王的猫 发表于 2019-12-21 18:06
我进来了

我下班了!!!?

龙王的猫 发表于 2019-12-21 18:08

进不去=-=

logo_tu 发表于 2019-12-21 18:20

龙王的猫 发表于 2019-12-21 18:08
进不去=-=

win10可以,别的我就不知道啦(o^^o)

xouou 发表于 2019-12-21 18:31

win7 打开自动退出

神秘高手Mars偉 发表于 2019-12-21 18:38

页: [1] 2
查看完整版本: go语言聊天室