有一段时间没写博客了,现在因为工作需要学习了go,做一个小Demo,简单的聊天室。
- 首先服务端开启监听8889端口,监听每一个客户端。
- 客户端需要先登录,登录成功后,服务端会发送给每一个客户端一个消息,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
-
}
效果:
- 启动服务器
- 加入第一个客户端,第一个客户端显示如下
- 加入第二个客户端,第一个客户端显示如下
转载:https://blog.csdn.net/a568283992/article/details/117199968
查看评论