goLang简单聊天室

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

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

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

package message

const (
	LoginMesType    = "LoginMes"
	RegisterMesType = "RegisterMesType"
	MesType         = "MesType"
)

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

type LoginMes struct {
	UserId   int    `json:"userId"`   // 用户id
	UserPwd  string `json:"userPwd"`  // 用户密码
	UserName string `json:"userName"` // 用户名
}

  • coding.go:

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

package coding

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
)

func Encode(i interface{}) (b []byte, err error) {
	//将message进行序列化
	data, err := json.Marshal(i)
	if err != nil {
		fmt.Println("json Marshal error")
		return
	}
	//这个时候data就是要发送的消息
	var pkgLen uint32
	var pkg = new(bytes.Buffer)
	pkgLen = uint32(len(data))
	// 写入头
	err = binary.Write(pkg, binary.LittleEndian, pkgLen)
	// 写入体
	err = binary.Write(pkg, binary.LittleEndian, data)
	b = pkg.Bytes()
	return
}

func Decode(reader *bufio.Reader) (b []byte, err error) {
	// 读取消息
	preBytes, err := reader.Peek(4)
	var length uint32
	err = binary.Read(bytes.NewBuffer(preBytes), binary.LittleEndian, &length)
	if err != nil {
		fmt.Println("读取头4字节失败", err)
		return
	}
	if uint32(reader.Buffered()) < length+4 {
		return []byte{}, fmt.Errorf("读取到%d,应为%d", reader.Buffered(), length+4)
	}
	fmt.Println("body长度为", length)
	p := make([]byte, length+4)
	_, err = reader.Read(p)
	if err != nil {
		fmt.Println("读取头数据体失败")
		return
	}
	b = p[4:]
	return
}
  • server.go

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

package main

import (
	"bufio"
	"coding"
	"encoding/json"
	"fmt"
	"message"
	"net"
	"redisCli"
	"strconv"
)

var r *redisCli.RedisCli

var online map[net.Conn]message.LoginMes

func init() {
	r, _ = redisCli.NewRedisCli("tcp", "127.0.0.1:6379")
	online = make(map[net.Conn]message.LoginMes, 10)
}

func handleLogin(b []byte) (loginMes message.LoginMes) {
	_ = json.Unmarshal(b, &loginMes)
	//使用redis操作是否存在该账号
	str, err := r.GetString(strconv.Itoa(loginMes.UserId) + ":" + loginMes.UserPwd)
	if err != nil {
		return
	}
	//if str == "" {
	//	fmt.Println("登录失败")
	//	return
	//}
	loginMes.UserName = str
	return
}

func handleRegister(b []byte) {

}

func process(conn net.Conn) {
	// 读取客户端发送的信息
	reader := bufio.NewReader(conn)
	mes := message.Message{}
	b, err := coding.Decode(reader)
	if err != nil {
		fmt.Println("decode失败")
		return
	}
	err = json.Unmarshal(b, &mes)
	if err != nil {
		fmt.Println("反序列化失败")
		return
	}
	dataBytes := []byte(mes.Data)
	switch mes.Type {
	case message.LoginMesType: // 登录消息
		loginMes := handleLogin(dataBytes)
		online[conn] = loginMes
		mes := message.Message{
			Type: message.MesType,
			Data: loginMes.UserName + "加入群聊啦",
		}
		b, err := coding.Encode(mes)
		if err != nil {
			fmt.Println("序列化失败")
			return
		}
		for c, _ := range online {
			_, _ = c.Write(b)
		}
	case message.RegisterMesType: // 注册消息
		handleRegister(dataBytes)
	}
}

func main() {
	fmt.Println("服务器从8889端口监听")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	if err != nil {
		fmt.Println("net listen error")
		return
	}
	//监听成功等待
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("net client error")
			return
		}
		//连接成功就保持通讯
		go process(conn)
	}
}
  • client.go:

客户端代码

package main

import (
	"bufio"
	"coding"
	"context"
	"fmt"
	"message"
	"sync"
)

var (
	userId  int
	userPwd string
)

var wg sync.WaitGroup

func main() {
	// 接收用户的选择
	var key int
	// 判断是否还继续显示菜单
	var loop = true
	for loop {
		fmt.Println("----------欢迎登录多人聊天系统----------")
		fmt.Println("\t\t\t 1 登录聊天系统")
		fmt.Println("\t\t\t 2 注册用户")
		fmt.Println("\t\t\t 3 退出系统")
		fmt.Println("\t\t\t 请选择(1-3):")
		_, _ = fmt.Scanf("%d\n", &key)
		switch key {
		case 1:
			fmt.Println("登录聊天系统")
			loop = false
		case 2:
			fmt.Println("注册用户")
		case 3:
			fmt.Println("退出系统")
			loop = false
		default:
			fmt.Println("输入有误 请重新输入")
		}
	}

	if key == 1 {
		// 说明用户要登录
		fmt.Println("请输入用户id")
		_, _ = fmt.Scanf("%d\n", &userId)
		fmt.Println("请输入用户密码")
		_, _ = fmt.Scanf("%s\n", &userPwd)
		// 先把登录的函数写到另一个文件中
		conn, err := login(userId, userPwd)
		if err != nil {
			fmt.Println("登录失败了")
		} else {
			fmt.Println("登录成功了")
			// 这里需要监听服务器发来的信息
			ctx, cancel := context.WithCancel(context.Background())
			wg.Add(2)
			// 开启接收端进程
			go func(ctx context.Context, cancel context.CancelFunc) {
				fmt.Println("开启接收进程")
				defer wg.Done()
				defer cancel()
				for {
					select {
					case <-ctx.Done():
						fmt.Println("退出接收进程")
						return
					default:
						b, err := coding.Decode(bufio.NewReader(conn))
						if err != nil {
							fmt.Println("退出接收进程, err:", err)
							return
						}
						str := string(b)
						fmt.Println("收到消息:", str)
						if "exit" == str {
							return
						}
					}
				}
			}(ctx, cancel)
			// 开启发送端进程
			go func(ctx context.Context) {
				defer wg.Done()
				fmt.Println("开启写入进程")
				for {
					select {
					case <-ctx.Done():
						fmt.Println("退出写入进程")
						return
					default:
						var str string
						_, _ = fmt.Scanf("%s\n", &str)
						mes := message.Message{
							Type: message.MesType,
							Data: str,
						}
						b, err := coding.Encode(mes)
						if err != nil {
							fmt.Println("序列化失败")
							continue
						}
						_, _ = conn.Write(b)
					}
				}
			}(ctx)
			wg.Wait()
		}
	} else if key == 2 {
		fmt.Println("进行用户注册")
	}

}



// 写一个函数,完成登录
func login(userId int, userPwd string) (conn net.Conn, err error) {
	// 开始定协议
	conn, err = net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net Dial error")
		return
	}
	// 准备conn发送消息
	var mes message.Message
	mes.Type = message.LoginMesType
	// 创建一个LoginMes 结构体
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	b, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json Marshal error")
		return
	}
	mes.Data = string(b)
	data, err := coding.Encode(mes)
	if err != nil {
		fmt.Println("data Marshal error")
		return
	}
	_, _ = conn.Write(data)
	return
}

效果:

  • 启动服务器

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值