飞道的博客

goLang简单聊天室

396人阅读  评论(0)

有一段时间没写博客了,现在因为工作需要学习了go,做一个小Demo,简单的聊天室。

  1. 首先服务端开启监听8889端口,监听每一个客户端。
  2. 客户端需要先登录,登录成功后,服务端会发送给每一个客户端一个消息,XXX用户登陆成功,目前程序还没写完全,后续会不断更新~~
  • message.go:

首先需要一个消息传送的protocol,因为是基于TCP协议,传输过程中会有粘包和拆包问题,因此定义一个协议来保证数据完整性。


  
  1. package message
  2. const (
  3. LoginMesType = "LoginMes"
  4. RegisterMesType = "RegisterMesType"
  5. MesType = "MesType"
  6. )
  7. type Message struct {
  8. Type string `json:"type"` // 消息类型
  9. Data string `json:"data"` // 消息内容
  10. }
  11. type LoginMes struct {
  12. UserId int `json:"userId"` // 用户id
  13. UserPwd string `json:"userPwd"` // 用户密码
  14. UserName string `json:"userName"` // 用户名
  15. }
  • coding.go:

这个包是用来编码和解码的作用,当发送数据时需要分为2段编码,然后转为字节进行发送。接收数据时,通过反序列化直接就可以获得数据。


  
  1. package coding
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/binary"
  6. "encoding/json"
  7. "fmt"
  8. )
  9. func Encode(i interface{}) (b []byte, err error) {
  10. //将message进行序列化
  11. data, err := json.Marshal(i)
  12. if err != nil {
  13. fmt.Println( "json Marshal error")
  14. return
  15. }
  16. //这个时候data就是要发送的消息
  17. var pkgLen uint32
  18. var pkg = new(bytes.Buffer)
  19. pkgLen = uint32( len(data))
  20. // 写入头
  21. err = binary.Write(pkg, binary.LittleEndian, pkgLen)
  22. // 写入体
  23. err = binary.Write(pkg, binary.LittleEndian, data)
  24. b = pkg.Bytes()
  25. return
  26. }
  27. func Decode(reader *bufio.Reader) (b []byte, err error) {
  28. // 读取消息
  29. preBytes, err := reader.Peek( 4)
  30. var length uint32
  31. err = binary.Read(bytes.NewBuffer(preBytes), binary.LittleEndian, &length)
  32. if err != nil {
  33. fmt.Println( "读取头4字节失败", err)
  34. return
  35. }
  36. if uint32(reader.Buffered()) < length+ 4 {
  37. return [] byte{}, fmt.Errorf( "读取到%d,应为%d", reader.Buffered(), length+ 4)
  38. }
  39. fmt.Println( "body长度为", length)
  40. p := make([] byte, length+ 4)
  41. _, err = reader.Read(p)
  42. if err != nil {
  43. fmt.Println( "读取头数据体失败")
  44. return
  45. }
  46. b = p[ 4:]
  47. return
  48. }
  • server.go

服务器主要代码,因为是个简单demo,没有考虑很多。


  
  1. package main
  2. import (
  3. "bufio"
  4. "coding"
  5. "encoding/json"
  6. "fmt"
  7. "message"
  8. "net"
  9. "redisCli"
  10. "strconv"
  11. )
  12. var r *redisCli.RedisCli
  13. var online map[net.Conn]message.LoginMes
  14. func init() {
  15. r, _ = redisCli.NewRedisCli( "tcp", "127.0.0.1:6379")
  16. online = make( map[net.Conn]message.LoginMes, 10)
  17. }
  18. func handleLogin(b []byte) (loginMes message.LoginMes) {
  19. _ = json.Unmarshal(b, &loginMes)
  20. //使用redis操作是否存在该账号
  21. str, err := r.GetString(strconv.Itoa(loginMes.UserId) + ":" + loginMes.UserPwd)
  22. if err != nil {
  23. return
  24. }
  25. //if str == "" {
  26. // fmt.Println("登录失败")
  27. // return
  28. //}
  29. loginMes.UserName = str
  30. return
  31. }
  32. func handleRegister(b []byte) {
  33. }
  34. func process(conn net.Conn) {
  35. // 读取客户端发送的信息
  36. reader := bufio.NewReader(conn)
  37. mes := message.Message{}
  38. b, err := coding.Decode(reader)
  39. if err != nil {
  40. fmt.Println( "decode失败")
  41. return
  42. }
  43. err = json.Unmarshal(b, &mes)
  44. if err != nil {
  45. fmt.Println( "反序列化失败")
  46. return
  47. }
  48. dataBytes := [] byte(mes.Data)
  49. switch mes.Type {
  50. case message.LoginMesType: // 登录消息
  51. loginMes := handleLogin(dataBytes)
  52. online[conn] = loginMes
  53. mes := message.Message{
  54. Type: message.MesType,
  55. Data: loginMes.UserName + "加入群聊啦",
  56. }
  57. b, err := coding.Encode(mes)
  58. if err != nil {
  59. fmt.Println( "序列化失败")
  60. return
  61. }
  62. for c, _ := range online {
  63. _, _ = c.Write(b)
  64. }
  65. case message.RegisterMesType: // 注册消息
  66. handleRegister(dataBytes)
  67. }
  68. }
  69. func main() {
  70. fmt.Println( "服务器从8889端口监听")
  71. listen, err := net.Listen( "tcp", "0.0.0.0:8889")
  72. if err != nil {
  73. fmt.Println( "net listen error")
  74. return
  75. }
  76. //监听成功等待
  77. for {
  78. conn, err := listen.Accept()
  79. if err != nil {
  80. fmt.Println( "net client error")
  81. return
  82. }
  83. //连接成功就保持通讯
  84. go process(conn)
  85. }
  86. }
  • client.go:

客户端代码


  
  1. package main
  2. import (
  3. "bufio"
  4. "coding"
  5. "context"
  6. "fmt"
  7. "message"
  8. "sync"
  9. )
  10. var (
  11. userId int
  12. userPwd string
  13. )
  14. var wg sync.WaitGroup
  15. func main() {
  16. // 接收用户的选择
  17. var key int
  18. // 判断是否还继续显示菜单
  19. var loop = true
  20. for loop {
  21. fmt.Println( "----------欢迎登录多人聊天系统----------")
  22. fmt.Println( "\t\t\t 1 登录聊天系统")
  23. fmt.Println( "\t\t\t 2 注册用户")
  24. fmt.Println( "\t\t\t 3 退出系统")
  25. fmt.Println( "\t\t\t 请选择(1-3):")
  26. _, _ = fmt.Scanf( "%d\n", &key)
  27. switch key {
  28. case 1:
  29. fmt.Println( "登录聊天系统")
  30. loop = false
  31. case 2:
  32. fmt.Println( "注册用户")
  33. case 3:
  34. fmt.Println( "退出系统")
  35. loop = false
  36. default:
  37. fmt.Println( "输入有误 请重新输入")
  38. }
  39. }
  40. if key == 1 {
  41. // 说明用户要登录
  42. fmt.Println( "请输入用户id")
  43. _, _ = fmt.Scanf( "%d\n", &userId)
  44. fmt.Println( "请输入用户密码")
  45. _, _ = fmt.Scanf( "%s\n", &userPwd)
  46. // 先把登录的函数写到另一个文件中
  47. conn, err := login(userId, userPwd)
  48. if err != nil {
  49. fmt.Println( "登录失败了")
  50. } else {
  51. fmt.Println( "登录成功了")
  52. // 这里需要监听服务器发来的信息
  53. ctx, cancel := context.WithCancel(context.Background())
  54. wg.Add( 2)
  55. // 开启接收端进程
  56. go func(ctx context.Context, cancel context.CancelFunc) {
  57. fmt.Println( "开启接收进程")
  58. defer wg.Done()
  59. defer cancel()
  60. for {
  61. select {
  62. case <-ctx.Done():
  63. fmt.Println( "退出接收进程")
  64. return
  65. default:
  66. b, err := coding.Decode(bufio.NewReader(conn))
  67. if err != nil {
  68. fmt.Println( "退出接收进程, err:", err)
  69. return
  70. }
  71. str := string(b)
  72. fmt.Println( "收到消息:", str)
  73. if "exit" == str {
  74. return
  75. }
  76. }
  77. }
  78. }(ctx, cancel)
  79. // 开启发送端进程
  80. go func(ctx context.Context) {
  81. defer wg.Done()
  82. fmt.Println( "开启写入进程")
  83. for {
  84. select {
  85. case <-ctx.Done():
  86. fmt.Println( "退出写入进程")
  87. return
  88. default:
  89. var str string
  90. _, _ = fmt.Scanf( "%s\n", &str)
  91. mes := message.Message{
  92. Type: message.MesType,
  93. Data: str,
  94. }
  95. b, err := coding.Encode(mes)
  96. if err != nil {
  97. fmt.Println( "序列化失败")
  98. continue
  99. }
  100. _, _ = conn.Write(b)
  101. }
  102. }
  103. }(ctx)
  104. wg.Wait()
  105. }
  106. } else if key == 2 {
  107. fmt.Println( "进行用户注册")
  108. }
  109. }
  110. // 写一个函数,完成登录
  111. func login(userId int, userPwd string) (conn net.Conn, err error) {
  112. // 开始定协议
  113. conn, err = net.Dial( "tcp", "localhost:8889")
  114. if err != nil {
  115. fmt.Println( "net Dial error")
  116. return
  117. }
  118. // 准备conn发送消息
  119. var mes message.Message
  120. mes.Type = message.LoginMesType
  121. // 创建一个LoginMes 结构体
  122. var loginMes message.LoginMes
  123. loginMes.UserId = userId
  124. loginMes.UserPwd = userPwd
  125. b, err := json.Marshal(loginMes)
  126. if err != nil {
  127. fmt.Println( "json Marshal error")
  128. return
  129. }
  130. mes.Data = string(b)
  131. data, err := coding.Encode(mes)
  132. if err != nil {
  133. fmt.Println( "data Marshal error")
  134. return
  135. }
  136. _, _ = conn.Write(data)
  137. return
  138. }

效果:

  • 启动服务器

  • 加入第一个客户端,第一个客户端显示如下

  • 加入第二个客户端,第一个客户端显示如下


转载:https://blog.csdn.net/a568283992/article/details/117199968
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场