Go语言项目 -- 海量用户即时通讯系统

1. 参考博客

https://blog.csdn.net/A_art_xiang/article/details/137236072

2. 分析

需求分析

  1. 用户注册登录
  2. 显示在线用户列表
  3. 群聊
  4. 点对点聊天
  5. 离线留言

思路分析

完成链接创建,数据发送功能

发送接受消息流程示意图

server端

package main

import (
	"fmt"
	"net"
)

func process(conn net.Conn){

	// 延时关闭conn
	defer conn.Close()
	buffer := make([]byte,8096)
	// 循环读取客户端发送的信息
	for {

		n,err := conn.Read(buffer[:4])
		if n != 4 || err != nil{
			fmt.Println("读取错误",err)
			return
		}
		fmt.Println("读到",buffer[:4])
	}
}


func main(){
	fmt.Println("服务器在8889端口监听")
	listener,err := net.Listen("tcp","127.0.0.1:8889")
	defer listener.Close()
	if err != nil{
		fmt.Println("监听错误",err)
		return
	}
	// 监听成功等待客户端链接服务器
	for  {
		fmt.Println("等待链接")
		conn,err := listener.Accept()
		if err != nil{
			fmt.Println("链接出错",err)
		}
		// 链接成功,启动协程和客户端保持通信

		go process(conn)
	}
}

client端

main.go

package main

import (
	"fmt"
)


var userId int
var password string

func main()  {
	// 接受用户的选择
	var key int
	for {
		fmt.Println("登录多人聊天")
		fmt.Println("1 登录")
		fmt.Println("2 注册")
		fmt.Println("3 退出")
		fmt.Println("请选择(1-3)")
		fmt.Scanf("%d\n",&key)
		switch key {
		case 1:
			fmt.Println("登录聊天室")
			// 登录
			fmt.Println("请输入用户ID:")
			fmt.Scanf("%d\n",&userId)
			fmt.Println("请输入密码:")
			fmt.Scanf("%d\n",&password)

			err := login(userId,password)
			fmt.Println(err)
			if err != nil{
				fmt.Println("登录成功")
			}else{
				fmt.Println("登录失败")
			}
		case 2:
			fmt.Println("注册用户")
		case 3:
			fmt.Println("退出系统")
			// 退出系统
			//os.Exit(0)
			return
		default:
			fmt.Println("输入有误")
		}
	}
}

client.go

package main

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"goproject/src/chatroom/common/message"
	"net"
)

func login(userId int, password string) (err error) {
	// 链接到服务端
	conn, err := net.Dial("tcp", "127.0.0.1:8889")
	if err != nil {
		fmt.Println("链接错误", err)
		return
	}
	// 延时关闭
	defer conn.Close()


	//  通过conn,发送消息给服务端
	var mess message.Message
	mess.Type = message.LoginMesType

	// 创建一个LoginMes 结构体,并赋值
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.Password = password
	loginMes.UserName = "xiaoming"

	// 将结构体序列化成字符串
	data,err := json.Marshal(loginMes)
	if err != nil{
		fmt.Println("序列化错误",err)
		return
	}
	// 将字节切片转成字符串
	mess.Data = string(data)

	// 将mess 结构体序列化成字符串
	data,err = json.Marshal(mess)
	if err != nil{
		fmt.Println("序列化错误",err)
		return
	}

	// 先发送data 的字节长度,发送给 服务器,防止丢包
	// 先获取data 长度,转换 成byte切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buffer [4]byte

	binary.BigEndian.PutUint32(buffer[0:4],pkgLen)

	n,err := conn.Write(buffer[:4])
	if n != 4 || err != nil{
		fmt.Println("发送失败",err)
		return
	}
	fmt.Println(n,string(data))


	return
}

common 消息格式定义

package message


const (
	LoginMesType  = "LoginMes"
	LoginResultMesType  =  "LoginResultMes"
)

type Message struct {
	Type string  `json:"type"` // 消息类型
	Data string `json:"data"`  // 消息具体内容
}


// 定义两种消息

type LoginMes struct {
	UserId int  `json:"user_id"`
	Password string `json:"password"`
	UserName string `json:"user_name"`
}


type LoginResultMes struct {
	code int   `json:"code"`  //状态码
	Error string `json:"error"`  // 错误信息
}


思路分析二

完成客户端可以发送消息,服务端可以正常接收消息,并根据客户端发送的消息,判断用户的合法性,并返回对应的消息

  1. 客户端发送消息
  2. 服务器接收消息,反序列化成对应的消息结构体
  3. 服务端根据反序列化的消息,判断登录用户是否合法,返回消息
  4. 客户端解析返回的消息,显示对应界面

服务端解析客户端发送的消息

package main

import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"goproject/src/chatroom/common/message"
	"io"
	"net"
)

func readPkg(conn net.Conn)(mes message.Message,err error){
	buffer := make([]byte,8096)
	n,err := conn.Read(buffer[:4])
	if err != nil{
		if err == io.EOF{
			return
		}
		fmt.Println("读取错误",err)
		return
	}

	// 根据buffer[:4] 转成uint32类型,获取到需要读取多少个字节数
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(buffer[:4])

	// 根据pkgLen 读取内容,存放到buffer数组中
	n,err = conn.Read(buffer[:pkgLen])
	if uint32(n) != pkgLen || err != nil{

		// 自定义错误
		err = errors.New("读取错误~")
		return
	}


	// 将buffer 反序列化成结构体,需要传结构体的地址!!!!
	err = json.Unmarshal(buffer[:pkgLen],&mes)
	if err != nil{

		err = errors.New("反序列化错误")
		return
	}
	return

}


func process(conn net.Conn){

	// 延时关闭conn
	defer conn.Close()

	// 循环读取客户端发送的信息
	for {

		message,err := readPkg(conn)
		if err != nil{
			if err == io.EOF{
				return
			}
			fmt.Println("错误",err)
			return

		}
		fmt.Println("客户端发送的消息为:",message)

	}
}


func main(){
	fmt.Println("服务器在8889端口监听")
	listener,err := net.Listen("tcp","127.0.0.1:8889")
	defer listener.Close()
	if err != nil{
		fmt.Println("监听错误",err)
		return
	}
	// 监听成功等待客户端链接服务器
	for  {
		fmt.Println("等待链接")
		conn,err := listener.Accept()
		if err != nil{
			fmt.Println("链接出错",err)
		}
		// 链接成功,启动协程和客户端保持通信

		go process(conn)
	}
}

客户端发送消息

// 发送消息
	_, err = conn.Write(data)
	if err != nil {
		fmt.Println("发送失败", err)
		return
	}

结构化目录重构

服务端

思维导图

目录结构

客户端

将数据缓存在客户端的文件中,以文件当做一个数据库,这样可以实现无网络也能看到消息

思维导图

posted @ 2021-10-07 15:35  河图s  阅读(225)  评论(0)    收藏  举报