go 聊天系统项目-1

1、登录界面

说明:这一节的内容采用 go mod 管理【GO111MODULE=‘’】的模块,从第二节开始使用【GO111MODULE=‘off’】GOPATH 管理模块。具体参见 go 包相关知识

1.1登录界面代码目录结构

代码所在目录/Users/zld/Go-project/day8/chatroom/
在这里插入图片描述

1.2登录界面代码

main.go

package main

import (
	"fmt"
)

var userId int
var userPwd string

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("注册用户")
			loop = false
		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)
		err := login(userId, userPwd)
		if err != nil {
			fmt.Println("登录失败")
		} else {
			fmt.Println("登录成功")
		}
	} else if key == 2 {
		fmt.Println("进行用户注册的逻辑")

	}
}

login.go

package main

import (
	"fmt"
)

func login(userId int, userPwd string) (err error) {
	fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	return nil
}

1.3初始化模块

go mod init client 

注意:init 后跟的名字和二进制文件名字(go build -o 后的名字)一样

go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
go: creating new go.mod: module client
go: to add module requirements and sums:
        go mod tidy

1.4编译

cd /Users/zld/Go-project/day8/chatroom/client/
go build -o client ./
输出
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject

1.5演示代码

go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
1
登录聊天室
请输入用户的id
123
请输入用户密码
qwe
userId = 123 userPwd = qwe
登录成功
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
3
退出系统
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
2
注册用户
进行用户注册的逻辑
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
>
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
?
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
5
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

2、客户端服务端简单交互

2.1代码目录结构

GOPATH=‘/Users/zld/Go-project’
项目目录结构,项目在 /Users/zld/Go-project/src 【GOPATH指定的目录】下

tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go  
    ├── common
    │   └── message
    │       └── message.go
    └── server
        └── main.go

6 directories, 4 files

2.2代码

2.2.1 day8/chatroom/common/message/message.go
package message

const (
	LoginMesType    = "LoginMes"
	LoginResMesType = "LoginResMes"
)

type Message struct {
	Type string `josn: "type"`
	Data string `json: "Data"`
}
type LoginMes struct {
	UserId   int    `json: "userId"`
	UserPwd  string `json: "userPwd"`
	UserName string `json: "userName"`
}
type LoginResMes struct {
	Code  int    `json: "code"`
	Error string `json: "error"`
}
2.2.2 day8/chatroom/server/main.go
package main

import (
	"fmt"
	"net"
)

// 处理和客户端的通信
func process(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//循环的客户端发送的信息
	for {
		buf := make([]byte, 8096)
		n, err := conn.Read(buf[:4])
		if n != 4 || err != nil {
			fmt.Println("conn.Read err=", err)
			return
		}
		fmt.Printf("读到的buf=%d\n", buf[:4])
	}

}

func main() {
	//提示信息
	fmt.Println("服务器在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	if err != nil {
		fmt.Println("net.Listen err=", err)
		return
	}
	for {
		fmt.Println("等待客户端连接服务器......")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("listen.Accept() err=", err)
		}
		//一旦连接成功,则启动一个协程和客户端保持通讯
		go process(conn)
	}
}
2.2.3 day8/chatroom/client/client.go
package main

import (
	"fmt"
)

var userId int
var userPwd string

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("注册用户")
			loop = false
		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)
		err := login(userId, userPwd)
		if err != nil {
			fmt.Println("登录失败")
		} else {
			fmt.Println("登录成功")
		}
	} else if key == 2 {
		fmt.Println("进行用户注册的逻辑")

	}
}
2.2.4 day8/chatroom/client/main.go
package main

import (
	"day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

func login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
	//发送长度
	n, err := conn.Write(buf[0:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	return
}

2.3 编译项目代码

注意:如果在 GO111MODULE=‘off’ 的情况下,编译代码一定要进到 $GOPATH 目录。

cd $GOPATH
go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

2.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
qwe
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"qwe\",\"UserName\":\"\"}"}
登录成功

server

等待客户端连接服务器......
读到的buf=[0 0 0 83]
conn.Read err= EOF

3、判断用户输入账户密码并改进代码结构

3.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go
    │   └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        └── main.go

6 directories, 5 files

3.2 代码

3.2.1 day8/chatroom/client/login.go
package main

import (
	"day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	//"time"
)

func login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
	//发送长度
	n, err := conn.Write(buf[0:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	_, err = conn.Write(data)
	if err != nil {
		fmt.Printf("conn.Write(data) fail", err)
		return
	}
	//time.sleep(20*time.Second)
	//fmt.Println("休眠了20S")
	//这里还需要处理服务器返回的消息
	mes, err = readPkg(conn) //mes 就是

	if err != nil {
		fmt.Println("readPkg(conn) err=", err)
		return
	}

	//将 mes 的 data 部分反序列化成 LoginResMes
	var loginResMes message.LoginResMes
	err = json.Unmarshal([]byte(mes.Data), &loginResMes)
	if loginResMes.Code == 200 {
		fmt.Println("登录成功")
	} else if loginResMes.Code == 500 {
		fmt.Println(loginResMes.Error)
	}

	return
}
3.2.2 day8/chatroom/client/main.go
package main

import (
	"fmt"
)

var userId int
var userPwd string

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("注册用户")
			loop = false
		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)
		login(userId, userPwd)
		//err := login(userId, userPwd)
		// if err != nil {
		// 	fmt.Println("登录失败")
		// } else {
		// 	fmt.Println("登录成功")
		// }
	} else if key == 2 {
		fmt.Println("进行用户注册的逻辑")

	}
}
3.2.3 day8/chatroom/client/utils.go
package main

import (
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"net"
)

func readPkg(conn net.Conn) (mes message.Message, err error) {
	buf := make([]byte, 8096)
	fmt.Println("读取客户端发送的数据...")
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = conn.Read(buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(buf[0:4])
	n, err := conn.Read(buf[:pkgLen])
	if n != int(pkgLen) || err != nil {
		//err = errors.New("read pkg body error")
		return
	}
	//把 pkgLen 反序列化成  -> message.Message
	err = json.Unmarshal(buf[:pkgLen], &mes)
	if err != nil {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func writePkg(conn net.Conn, data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
	//发送长度
	n, err := conn.Write(buf[0:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	//发送 data本身
	n, err = conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}
3.2.4 day8/chatroom/common/message/message.go
package message

const (
	LoginMesType    = "LoginMes"
	LoginResMesType = "LoginResMes"
	RegisterMesType = "RegisterMes"
)

type Message struct {
	Type string `josn: "type"`
	Data string `json: "Data"`
}
type LoginMes struct {
	UserId   int    `json: "userId"`
	UserPwd  string `json: "userPwd"`
	UserName string `json: "userName"`
}
type LoginResMes struct {
	Code  int    `json: "code"`
	Error string `json: "error"`
}

type RegisterMes struct{
	//
}
3.2.5 day8/chatroom/server/main.go
package main

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

func readPkg(conn net.Conn) (mes message.Message, err error) {
	buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = conn.Read(buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(buf[0:4])
	n, err := conn.Read(buf[:pkgLen])
	if n != int(pkgLen) || err != nil {
		//err = errors.New("read pkg body error")
		return
	}
	//把 pkgLen 反序列化成  -> message.Message
	err = json.Unmarshal(buf[:pkgLen], &mes)
	if err != nil {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func writePkg(conn net.Conn, data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
	//发送长度
	n, err := conn.Write(buf[:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	//发送 data本身
	n, err = conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}

func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {
	//核心代码
	//先从mes 中取出 mes.Data,并直接反序列化成 LoginMes
	var loginMes message.LoginMes
	err = json.Unmarshal([]byte(mes.Data), &loginMes)
	if err != nil {
		fmt.Println("json.Unmarshal fail err=", err)
		return
	}

	// 先声明一个 resMes
	var resMes message.Message
	resMes.Type = message.LoginResMesType

	//再声明一个 LoginResMes
	var loginResMes message.LoginResMes

	//如果用户id=100,密码=123456,认为合法,否则不合法
	if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
		//合法
		loginResMes.Code = 200
	} else {
		//不合法
		loginResMes.Code = 500
		loginResMes.Error = "该用户不存在,请注册再使用..."
	}
	//将 loginResMes 序列化
	data, err := json.Marshal(loginResMes)
	if err != nil {
		fmt.Println("json.Marshal fail", err)
		return
	}
	//将data赋值给resMes
	resMes.Data = string(data)

	//对 resMes 进行序列化,准备发送
	data, err = json.Marshal(resMes)
	if err != nil {
		fmt.Println("json.Marshal fail", err)
		return
	}
	//发送 data,将其封装到函数中
	err = writePkg(conn, data)
	return
}

func serverProcessMes(conn net.Conn, mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录
		err = serverProcessLogin(conn, mes)
	case message.RegisterMesType:
	//处理注册
	default:
		fmt.Printf("消息类型不存在,无法处理")
	}
	return
}

// 处理和客户端的通信
func process(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//循环客户端发送信息
	for {
		mes, err := readPkg(conn)

		if err != nil {
			if err == io.EOF {
				fmt.Println("客户端退出,服务器端也退出..")
				return
			} else {
				fmt.Println("readPkg err=", err)
				return
			}
		}

		//fmt.Println("mes=", mes)
		err = serverProcessMes(conn, &mes)
		if err != nil {
			return
		}
	}

}

func main() {
	//提示信息
	fmt.Println("服务器在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	if err != nil {
		fmt.Println("net.Listen err=", err)
		return
	}
	for {
		fmt.Println("等待客户端连接服务器......")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("listen.Accept() err=", err)
		}
		//一旦连接成功,则启动一个协程和客户端保持通讯
		go process(conn)
	}
}

3.3 编译项目代码

go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

3.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
123
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":123,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
该用户不存在,请注册再使用...

server

等待客户端连接服务器......
客户端退出,服务器端也退出..

4、改进服务端代码结构

客户端 client 目录下的代码不变

4.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go
    │   └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        ├── main
        │   ├── main.go
        │   └── processor.go
        ├── model
        ├── process
        │   ├── smsProcess.go
        │   └── userProcess.go
        └── utils
            └── utils.go

10 directories, 9 files

4.2 代码

这里只展示改动的 server 目录下的代码。

4.2.1 day8/chatroom/server/main/main.go
package main

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

// func readPkg(conn net.Conn) (mes message.Message, err error) {
// 	buf := make([]byte, 8096)
// 	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
// 	//如果客户端关闭了conn 就不会阻塞
// 	_, err = conn.Read(buf[:4])
// 	if err != nil {
// 		//err = errors.New("read pkg header error")
// 		return
// 	}
// 	//根据读到的  buf[:4] 转成一个 unit32 类型
// 	var pkgLen uint32
// 	pkgLen = binary.BigEndian.Uint32(buf[0:4])
// 	n, err := conn.Read(buf[:pkgLen])
// 	if n != int(pkgLen) || err != nil {
// 		//err = errors.New("read pkg body error")
// 		return
// 	}
// 	//把 pkgLen 反序列化成  -> message.Message
// 	err = json.Unmarshal(buf[:pkgLen], &mes)
// 	if err != nil {
// 		err = errors.New("json.Unmarshal error")
// 		return
// 	}
// 	return

// }

// func writePkg(conn net.Conn, data []byte) (err error) {
// 	//先发送一个长度给对方
// 	//data是 我们要发送的消息,先发送 data 长度
// 	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
// 	var pkgLen uint32
// 	pkgLen = uint32(len(data))
// 	var buf [4]byte
// 	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 	//发送长度
// 	n, err := conn.Write(buf[:4])
// 	if n != 4 || err != nil {
// 		fmt.Println("conn.Write(bytes) fail", err)
// 		return
// 	}
// 	//发送 data本身
// 	n, err = conn.Write(data)
// 	if n != int(pkgLen) || err != nil {
// 		fmt.Println("conn.Write(bytes) fail", err)
// 		return
// 	}
// 	return
// }


// func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {
// 	//核心代码
// 	//先从mes 中取出 mes.Data,并直接反序列化成 LoginMes
// 	var loginMes message.LoginMes
// 	err = json.Unmarshal([]byte(mes.Data), &loginMes)
// 	if err != nil {
// 		fmt.Println("json.Unmarshal fail err=", err)
// 		return
// 	}

// 	// 先声明一个 resMes
// 	var resMes message.Message
// 	resMes.Type = message.LoginResMesType

// 	//再声明一个 LoginResMes
// 	var loginResMes message.LoginResMes

// 	//如果用户id=100,密码=123456,认为合法,否则不合法
// 	if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// 		//合法
// 		loginResMes.Code = 200
// 	} else {
// 		//不合法
// 		loginResMes.Code = 500
// 		loginResMes.Error = "该用户不存在,请注册再使用..."
// 	}
// 	//将 loginResMes 序列化
// 	data, err := json.Marshal(loginResMes)
// 	if err != nil {
// 		fmt.Println("json.Marshal fail", err)
// 		return
// 	}
// 	//将data赋值给resMes
// 	resMes.Data = string(data)

// 	//对 resMes 进行序列化,准备发送
// 	data, err = json.Marshal(resMes)
// 	if err != nil {
// 		fmt.Println("json.Marshal fail", err)
// 		return
// 	}
// 	//发送 data,将其封装到函数中
// 	err = writePkg(conn, data)
// 	return
// }

// func serverProcessMes(conn net.Conn, mes *message.Message) (err error) {
// 	switch mes.Type {
// 	case message.LoginMesType:
// 		//处理登录
// 		err = serverProcessLogin(conn, mes)
// 	case message.RegisterMesType:
// 	//处理注册
// 	default:
// 		fmt.Printf("消息类型不存在,无法处理")
// 	}
// 	return
// }

// 处理和客户端的通信
func process(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//这里调用总控,创建一个
	processor := &Processor{
		Conn : conn,
	}
	err := processor.process2()
	if err != nil{
		fmt.Println("客户端和服务器端通讯的协程错误=err",err)
		return
	}
	// //循环客户端发送信息
	// for {
	// 	mes, err := readPkg(conn)

	// 	if err != nil {
	// 		if err == io.EOF {
	// 			fmt.Println("客户端退出,服务器端也退出..")
	// 			return
	// 		} else {
	// 			fmt.Println("readPkg err=", err)
	// 			return
	// 		}
	// 	}

	// 	//fmt.Println("mes=", mes)
	// 	err = serverProcessMes(conn, &mes)
	// 	if err != nil {
	// 		return
	// 	}
	// }

}

func main() {
	//提示信息
	fmt.Println("服务器[新的结构]在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	if err != nil {
		fmt.Println("net.Listen err=", err)
		return
	}
	for {
		fmt.Println("等待客户端连接服务器......")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("listen.Accept() err=", err)
		}
		//一旦连接成功,则启动一个协程和客户端保持通讯
		go process(conn)
	}
}
4.2.2 day8/chatroom/server/main/processor.go
package main
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"day8/chatroom/server/process"
	"day8/chatroom/server/utils"
	"io"
)

//先创建一个processor的结构体
type Processor struct{
	Conn net.Conn
}

func (this *Processor) serverProcessMes(mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录
		up := &process2.UserProcess{
			Conn : this.Conn,
		}
		err = up.ServerProcessLogin(mes)
	case message.RegisterMesType:
	//处理注册
	default:
		fmt.Printf("消息类型不存在,无法处理")
	}
	return
}

func (this *Processor) process2() (err error) {
	//循环客户端发送信息
	for {
		//创建一个 Transfer 实例完成读包的任务
		tf := &utils.Transfer{
			Conn : this.Conn,
		}
		mes,err := tf.ReadPkg()

		if err != nil {
			if err == io.EOF {
				fmt.Println("客户端退出,服务器端也退出..")
				return err
			} else {
				fmt.Println("readPkg err=", err)
				return err
			}
		}
		//fmt.Println("mes=", mes)
		err = this.serverProcessMes(&mes)
		if err != nil {
			return err
		}
	}
}
4.2.3 day8/chatroom/server/process/smsProcess.go
package process2
4.2.4 day8/chatroom/server/process/userProcess.go
package process2

import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"day8/chatroom/server/utils"
	"encoding/json"

)

type UserProcess struct{
	//
	Conn net.Conn
}
func (this *UserProcess) ServerProcessLogin(mes *message.Message) (err error) {
	//核心代码
	//先从mes 中取出 mes.Data,并直接反序列化成 LoginMes
	var loginMes message.LoginMes
	err = json.Unmarshal([]byte(mes.Data), &loginMes)
	if err != nil {
		fmt.Println("json.Unmarshal fail err=", err)
		return
	}

	// 先声明一个 resMes
	var resMes message.Message
	resMes.Type = message.LoginResMesType

	//再声明一个 LoginResMes
	var loginResMes message.LoginResMes

	//如果用户id=100,密码=123456,认为合法,否则不合法
	if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
		//合法
		loginResMes.Code = 200
	} else {
		//不合法
		loginResMes.Code = 500
		loginResMes.Error = "该用户不存在,请注册再使用..."
	}
	//将 loginResMes 序列化
	data, err := json.Marshal(loginResMes)
	if err != nil {
		fmt.Println("json.Marshal fail", err)
		return
	}
	//将data赋值给resMes
	resMes.Data = string(data)

	//对 resMes 进行序列化,准备发送
	data, err = json.Marshal(resMes)
	if err != nil {
		fmt.Println("json.Marshal fail", err)
		return
	}
	//发送 data,将其封装到函数中
	//因为使用分层模式(mvc),我们先创建一个 Transfer 实例,然后读取
	tf := &utils.Transfer{
		Conn : this.Conn,
	}
	err = tf.WritePkg(data)
	return
}
4.2.5 day8/chatroom/server/utils/utils.go
package utils
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{
	//分析它应该有哪些字段
	Conn net.Conn
	Buf [8096]byte // 这是传输时,使用缓冲
}


func (this *Transfer) ReadPkg() (mes message.Message, err error) {
	//buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = this.Conn.Read(this.Buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
	n, err := this.Conn.Read(this.Buf[:pkgLen])
	if n != int(pkgLen) || err != nil {
		//err = errors.New("read pkg body error")
		return
	}
	//把 pkgLen 反序列化成  -> message.Message
	err = json.Unmarshal(this.Buf[:pkgLen], &mes)
	if err != nil {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	//var buf [4]byte
	binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
	//发送长度
	n, err := this.Conn.Write(this.Buf[:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	//发送 data本身
	n, err = this.Conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}

4.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/

4.4 演示代码

./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
登录成功

server

等待客户端连接服务器......
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

5、改进客户端代码结构

5.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── main
    │   │   └── main.go
    │   ├── model
    │   ├── process
    │   │   ├── server.go
    │   │   ├── smsProcess.go
    │   │   └── userProcess.go
    │   └── utils
    │       └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        ├── main
        │   ├── main.go
        │   └── processor.go
        ├── model
        ├── process
        │   ├── smsProcess.go
        │   └── userProcess.go
        └── utils
            └── utils.go

14 directories, 11 files

5.2 代码

5.2.1 day8/chatroom/client/main/main.go
package main

import (
	"day8/chatroom/client/process"
	"fmt"
)

var userId int
var userPwd string

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("登录聊天室")
			fmt.Println("请输入用户的id")
			fmt.Scanf("%d\n", &userId)
			fmt.Println("请输入用户密码")
			fmt.Scanf("%s\n", &userPwd)
			//完成登录
			//1.创建一个UserProcess的实例
			up := &process.UserProcess{}
			up.Login(userId, userPwd)

			//loop = false
		case 2:
			fmt.Println("注册用户")
			loop = false
		case 3:
			fmt.Println("退出系统")
			loop = false
		default:
			fmt.Println("你的输入有误,请重新输入")
		}
	}

	//if key == 1 {

	//这里需要重新调用
	//因为使用了新的程序结构,因此我们创建
	//login(userId, userPwd)
	//err := login(userId, userPwd)
	// if err != nil {
	// 	fmt.Println("登录失败")
	// } else {
	// 	fmt.Println("登录成功")
	// }
	//} else if key == 2 {
	//	fmt.Println("进行用户注册的逻辑")

	//}
}
5.2.2 day8/chatroom/client/process/server.go
package process

import (
	"day8/chatroom/client/utils"
	"fmt"
	"net"
	"os"
)

func ShowMenu() {
	fmt.Println("----------恭喜xxx登录成功--------")
	fmt.Println("--------1、显示在线用户列表--------")
	fmt.Println("--------2、发送消息--------")
	fmt.Println("--------3、信息列表--------")
	fmt.Println("--------4、退出系统--------")
	var key int
	fmt.Scanf("%d\n", &key)
	switch key {
	case 1:
		fmt.Println("显示在线用户列表")
	case 2:
		fmt.Println("发送消息")
	case 3:
		fmt.Println("信息列表")
	case 4:
		fmt.Println("你选择退出了系统...")
		os.Exit(0)
	default:
		fmt.Println("你输入的选项不正确..")
	}
}

// 和服务器保持通讯
func serverProcessMes(conn net.Conn) {
	//创建一个transfer实例,不停的读取服务器发送的消息
	tf := &utils.Transfer{
		Conn: conn,
	}
	for {
		fmt.Println("客户端%s正在等待读取服务器发送的消息")
		mes, err := tf.ReadPkg()
		if err != nil {
			fmt.Println("tf.ReadPkg err=", err)
			return
		}
		//如果读取到消息,又是下一步处理逻辑
		fmt.Printf("mes=%v", mes)
	}
}
5.2.3 day8/chatroom/client/process/smsProcess.go
package process
5.2.4 day8/chatroom/client/process/userProcess.go
package process

import (
	"day8/chatroom/client/utils"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

type UserProcess struct {
}

func (this *UserProcess) Login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	var buf [4]byte
	binary.BigEndian.PutUint32(buf[0:4], pkgLen)
	//发送长度
	n, err := conn.Write(buf[0:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	_, err = conn.Write(data)
	if err != nil {
		fmt.Printf("conn.Write(data) fail", err)
		return
	}
	//time.sleep(20*time.Second)
	//fmt.Println("休眠了20S")
	//这里还需要处理服务器返回的消息
	tf := &utils.Transfer{
		Conn: conn,
	}
	mes, err = tf.ReadPkg() //mes 就是

	if err != nil {
		fmt.Println("readPkg(conn) err=", err)
		return
	}

	//将 mes 的 data 部分反序列化成 LoginResMes
	var loginResMes message.LoginResMes
	err = json.Unmarshal([]byte(mes.Data), &loginResMes)
	if loginResMes.Code == 200 {
		//fmt.Println("登录成功")
		//这里我们还需要在客户端启动一个协程
		//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端
		//则接收并显示在客户端的终端
		go serverProcessMes(conn)
		//1.显示登录成功后的菜单
		for {
			ShowMenu()
		}
	} else if loginResMes.Code == 500 {
		fmt.Println(loginResMes.Error)
	}

	return
}
5.2.5 day8/chatroom/client/utils/utils.go
package utils
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{
	//分析它应该有哪些字段
	Conn net.Conn
	Buf [8096]byte // 这是传输时,使用缓冲
}


func (this *Transfer) ReadPkg() (mes message.Message, err error) {
	//buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = this.Conn.Read(this.Buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
	n, err := this.Conn.Read(this.Buf[:pkgLen])
	if n != int(pkgLen) || err != nil {
		//err = errors.New("read pkg body error")
		return
	}
	//把 pkgLen 反序列化成  -> message.Message
	err = json.Unmarshal(this.Buf[:pkgLen], &mes)
	if err != nil {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	var pkgLen uint32
	pkgLen = uint32(len(data))
	//var buf [4]byte
	binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
	//发送长度
	n, err := this.Conn.Write(this.Buf[:4])
	if n != 4 || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	//发送 data本身
	n, err = this.Conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}

5.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/main

5.4 演示代码

 ./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
客户端%s正在等待读取服务器发送的消息

server

等待客户端连接服务器......

client

1
显示在线用户列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
2
发送消息
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
3
信息列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
4
你选择退出了系统...

server

客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905456.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

支持向量机背后的数学奥秘

一、基本概念与原理 1.1 支持向量机的定义 支持向量机是一种二分类模型,其核心思想是在样本空间中寻找一个超平面,将不同类别的样本分开。这个超平面被称为决策边界或分隔超平面。支持向量是距离决策边界最近的点,这些点决定了决策边界的位…

C语言指针和数组相关习题

目录 sizeof和一维int数组sizeof和一维char数组strlen()和一维char数组sizeof和字符串strlen()和字符串指针变量指向字符串字面常量易错点sizeof(a):sizeof是操作符 当心整型提升sizeof和二维数组复习一下相关知识点练习题 一个离谱的错误指针1指针2指针3指针4指针5指针6指针7指…

Centos安装ZooKeeper教程(单机版)

本章教程介绍,如何在Centos7中,安装ZooKeeper 3.9.3版本。 一、什么是ZooKeeper ? Apache ZooKeeper 是一个分布式协调服务,用于大型分布式系统中的管理和协调。它为分布式应用提供了一个高性能的通信框架,简化了开发人员在构建复杂分布式系统的任务。ZooKeeper 能够解决一…

出国工作——常用英语——网站注册

Please set your password for your new Qt Account. Password must be at least 8 characters in length. 请为您的新 Qt 账户设置密码。密码长度必须至少为 8 个字符。 Password Password strength: BadThis is similar to a commonly used password. TIP: Add another wor…

江协科技STM32学习- P25 UART串口协议

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…

Servlet 3.0 新特性全解

文章目录 Servlet3.0新特性全解Servlet 3.0 新增特性Servlet3.0的注解Servlet3.0的Web模块支持servlet3.0提供的异步处理提供异步原因实现异步原理配置servlet类成为异步的servlet类具体实现异步监听器改进的ServletAPI(上传文件) Servlet3.0新特性全解 tomcat 7以上的版本都支…

mysql 通过GROUP BY 聚合并且拼接去重另个字段

我的需求: 我想知道同一个手机号出现几次,并且手机号出现在哪些地方。下面是要的效果。 源数据: CREATE TABLE bank (id bigint(20) unsigned NOT NULL AUTO_INCREMENT,user_id int(11) NOT NULL DEFAULT 0,tel varchar(255) COLLATE utf8mb4_unicode_…

新加坡托管服务器VS香港托管服务器:AI时代的选择策略

在人工智能迅速发展的今天,服务器作为数据存储与计算的核心基础设施,其性能、稳定性和地理位置对于用户体验和业务效率至关重要。对于中国用户而言,在选择服务器时,新加坡服务器和香港服务器无疑是两个极具吸引力的选项。两者同属…

Linux的硬盘管理

硬盘有价,数据无价 1. 硬盘的概念 硬盘是一种计算机的存储设备,通常是由一个或者多个磁性盘片组成。硬盘即可以安装在计算机的内部,也可以外接计算机。 保存数据 数据:操作系统,应用程序,文档多媒体文件…

震惊,盖子居然重现CSDN?

盖子奇迹般重回C站 众所周知,盖子上次发布文章是在2024年5月18号(感兴趣的可以回去看一下,链接放在下面了) 盖子的c小课堂——第二十七讲:背包变形题_恰好装满的01背包-CSDN博客https://blog.csdn.net/m0_73334782/a…

PostgreSQL的学习心得和知识总结(一百五十七)|新的 COPY 选项 LOG_VERBOSITY

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…

【力扣打卡系列】二叉树的最近公共祖先

坚持按题型打卡&刷&梳理力扣算法题系列,语言为go,Day18 二叉树的最近公共祖先 题目描述 解题思路 最近公共祖先分类讨论 当前节点是空节点(返回当前节点)当前节点是p(返回当前节点)当前节点是q&am…

Redis常见面试题总结(上)

Redis 基础 什么是 Redis? Redis (REmote DIctionary Server)是一个基于 C 语言开发的开源 NoSQL 数据库(BSD 许可)。与传统数据库不同的是,Redis 的数据是保存在内存中的(内存数据库,支持持久…

Training-free layout control with cross-attention guidance

https://zhuanlan.zhihu.com/p/666445024https://zhuanlan.zhihu.com/p/666445024 支持两种模式,1.sd文生图;2.绑定了dreambooth和text inversion的图像编辑。 # ------------------ example input ------------------examples &

‌Spring MVC的主要组件有哪些?

前言 SpringMVC的核心组件包括DispatcherServlet、Controller、HandlerMapping、HandlerAdapter、ViewResolver、ModelAndView等,它们协同工作以支持基于MVC架构的Web应用程序开发。这些组件使得开发人员能够以一种声明式和模块化的方式构建Web应用程序&#xff0c…

Python突破浏览器TLS/JA3 指纹

初识指纹遇到一个网站,忽然发现无论如何如何更换UA和代理请求都是403,curl_cffi 可模拟真实浏览器的 TLS | JA3 指纹。 查看 tls 指纹的网站: https://tls.browserleaks.com/json不同网站的生成的指纹可能有差异,但是多次访问同一个网站生成…

Redis新数据类型

新数据类型 Bitmaps 命令 setbit 实例 getbit 实例 bitcount 实例 bitop 实例 Bitmaps与set 对比 HyperLogLog 命令 pfadd 实例 pfcount 实例 pfmerge 实例 Geospatial 命令 geoadd 实例 geopos 实例 geodist 实例 georadius 实例 Bitmaps Ⅰ.B…

【Qt】QTableView添加下拉框过滤条件

实现通过带复选框的下拉框来为表格添加过滤条件 带复选框的下拉框 .h文件 #pragma once #include <QCheckBox> #include <QComboBox> #include <QEvent> #include <QLineEdit> #include <QListWidget>class TableComboBox : public QComboBox …

Java Executor ScheduledExecutorService 源码

前言 相关系列 《Java & Executor & 目录》《Java & Executor & ScheduledExecutorService & 源码》《Java & Executor & ScheduledExecutorService & 总结》《Java & Executor & ScheduledExecutorService & 问题》 涉及内容 …

C++:继承~派生类以及衍生的多继承与菱形继承问题

C中的继承其实是一个C中的坑,主要体现在其多继承(菱形继承)方面,我们先来了解下继承的概念 一,继承的概念与定义 1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许我们在保持原有类特性的基础上进行扩展&#xff0c;增…