经典项目
第一步:
main.go
// 2019-11-7 下午二点 系统index首页
package main
import (
"fmt"
"os"
)
var (
userid int
userpwd string
)
func main(){
var loop = true
var work int
// 这里真是没想到,loop=true则为死循环,loop=false退出死循环
for loop{
fmt.Println("----------欢迎登录多人聊天系统----------")
fmt.Println(" 1 登录聊天系统")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println("请输入1-3选择服务")
fmt.Scanln(&work)
switch work{
case 1:
fmt.Println("---登录聊天系统---")
loop = false
case 2:
fmt.Println("---注册用户---")
loop = false
case 3:
fmt.Println("---退出系统---")
// os.Exit可以用于退出当前程序
os.Exit(0)
default :
fmt.Println("请输入正确选项1-3")
}
}
if work == 1{
fmt.Println("请输入用户id:")
fmt.Scanf("%d\n",&userid)
fmt.Println("请输入用户密码:")
fmt.Scanf("%s",&userpwd)
err := login(userid,userpwd)
if err != nil{
fmt.Println("登录失败")
}else{
fmt.Println("登录成功!")
}
}else if work != 1{
fmt.Println("其他选项...")
}
}
login.go
package main
import "fmt"
func login(userid int,userpwd string)(err error){
fmt.Printf("userid=%v userpwd=%v",userpwd,userid)
return nil
}
指定文件夹下go文件生成exe文件名:
此时编译client文件夹下的main.go文件生成的可执行文件就为client.exe
PS E:\software\go-path\goproject> go build -o client.exe .\src\gotcp\chatroom\client
疑惑:
这里我不甚理解为何要跑到src文件夹下去编译client文件夹下的main.go文件,然后main.go文件才能调用到同级文件夹下的函数
个人理解:
由于client文件夹下的go文件package声明都为main包,main包是声明主函数的如果同级有两个文件都声明为main包则会产生冲突,如果一个文件编译时候指定其他名字,则不会和另一个文件编译产生冲突
第二步:
实现完成用户登录功能
数据发送流程
client客户端:
- 1.接收到输入的id和pwd
- 2.发送id和密码
- 3.接收到服务端返回的结果
- 判断是成功还是失败,并显示对应页面
server服务端:
- 1.接收用户id,pwd【goroutine】
- 2.比较
- 3.返回结果
1.完成客户端发送消息长度,服务端可以正常收到该长度值
代码实现:
server.go
package main
import (
"fmt"
"net"
)
func process(conn net.Conn){
defer conn.Close()
// 循环读取
for {
fmt.Println("准备读取客户端传输数据...")
buf := make([]byte,1024)
n,err := conn.Read(buf[:4])
if err != nil{
fmt.Println("conn.Read err = ",err)
return
}
fmt.Printf("读取到的数据buf=%v,读取到的长度=%v\n",buf[0:4],n)
}
}
func main(){
listener,err := net.Listen("tcp","0.0.0.0:8889")
defer listener.Close()
if err != nil{
fmt.Println("net.Listener err = ",err)
return
}
fmt.Printf("服务器网络类型为:%v 地址为:%v\n",listener.Addr().Network(),listener.Addr().String())
for{
// Accept返回listen服务端的连接并等待下一个连接
conn,err := listener.Accept()
defer conn.Close()
if err != nil{
fmt.Println("listener.Accept err = ",err)
return
}
fmt.Println("客户端连接成功!ip地址为:",conn.RemoteAddr())
go process(conn)
}
}
login.go
package main
import (
"fmt"
"net"
"gotcp/chatroom/common/message"
"encoding/json"
"encoding/binary"
)
func login(userid int,userpwd string)(err error){
// 下一步制定协议
conn,err := net.Dial("tcp","0.0.0.0:8889")
defer conn.Close()
if err != nil{
fmt.Println("net.Dial err = ",err)
}
// 实例化一个消息struct并将消息类型放入并将用户信息序列化后放入
var msg message.Message
msg.Type = message.LoginMsgType
// 实例化一个用户信息struct并将用户信息放入实例中并序列化
var loginmsg message.LoginMsg
loginmsg.UserId = userid
loginmsg.UserPwd = userpwd
// 序列化usermessage
data,err := json.Marshal(loginmsg)
if err != nil{
fmt.Println("json.Marshal err = ",err)
return
}
// 将loginmsg序列化后转为string赋值给msg.Data
msg.Data = string(data)
// 将msg.struct序列化
data,err = json.Marshal(msg)
if err != nil{
fmt.Println("json.Marshal err = ",err)
return
}
relay := uint32(len(data))
var buf [4]byte
/* Big-Endian就是高位字节排放在内存的低地址端,
低位字节排放在内存的高地址端 */
// 将uint32类型的长度转化为byte数组并按BigEndian排列于字节数组中
// 先获取到relay的长度再转为表示长度的切片
binary.BigEndian.PutUint32(buf[:4],relay)
n,err := conn.Write(buf[0:4])
if err != nil || n != 4{
fmt.Println("conn.Write err = ",err)
}
fmt.Printf("客户端发送消息长度为:%d 发送内容为:%v 发送完毕!\n",len(data),string(data))
return
}
main.go
// 2019-11-7 下午二点 系统index首页
package main
import (
"fmt"
"os"
)
var (
userid int
userpwd string
)
func main(){
var loop = true
var work int
// 这里真是没想到,loop=true则为死循环,loop=false退出死循环
for loop{
fmt.Println("----------欢迎登录多人聊天系统----------")
fmt.Println(" 1 登录聊天系统")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println("请输入1-3选择服务")
fmt.Scanln(&work)
switch work{
case 1:
fmt.Println("---登录聊天系统---")
loop = false
case 2:
fmt.Println("---注册用户---")
loop = false
case 3:
fmt.Println("---退出系统---")
// os.Exit可以用于退出当前程序
os.Exit(0)
default :
fmt.Println("请输入正确选项1-3")
}
}
if work == 1{
fmt.Println("请输入用户id:")
fmt.Scanf("%d\n",&userid)
fmt.Println("请输入用户密码:")
fmt.Scanf("%s",&userpwd)
err := login(userid,userpwd)
if err != nil{
fmt.Println("登录失败")
}else{
fmt.Println("登录成功!")
}
}else if work != 1{
fmt.Println("其他选项...")
}
}
message.go
package message
// 声明两个常量表示LoginMsg和LoginResMsg类型
const(
LoginMsgType = "LoginMsg"
LoginResMsgType = "LoginResMsg"
)
// Message和LoginMsg用户存放消息及给消息打tag
type Message struct{
Type string `json:"type"` // 消息类型
Data string `json:"data"` // 消息内容
}
type LoginMsg struct{
UserId int `json:"userId"` // 用户id
UserPwd string `json:"userPwd"` // 用户密码
UserName string `json:"userName"` // 用户名称
}
// LoginResMsg用来表示返回消息发送情况
type LoginResMsg struct{
Code int `json:"code"` // 状态码200表示成功
Error string `json:"error"` // 错误信息
}
疑问点解析:
relay := uint32(len(data