tcp编程
一、端口分类
0号是保留端口
1-1024是固定端口(有名端口),被某些程序使用。
7:echo服务
21:ftp使用
22:ssh远程登录协议
23:telnet使用
25:smtp服务使用
80:iis使用
1025-65535是动态端口
端口使用注意事项:
计算机(尤其是做服务器)要尽可能少开端口
一个端口只能被一个程序监听
netstat -an可以查看机器有哪些端口在监听
netsta -anb可以查看监听端口的pid
二、服务端和客户端
服务端的处理流程:
(1)、监听端口
(2)、接收客户端的tcp链接,建立客户端和服务端的链接
(3)、创建goroutine,处理该链接的请求(通常客户端会通过链接发送请求包)
客户端的处理流程:
(1)、建立与服务端的链接
(2)、发送请求数据,接收服务端返回的结果数据
(3)、关闭链接
服务端server.go
package main import ( "fmt" "net" ) //服务器端功能:在8888端口监听,可以和多个客户端创建链接,链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上 func process(conn net.Conn) { defer conn.Close() for { buf := make([]byte, 1024) //等待客户端通过conn发送信息,如果客户端没有发送消息,那么协程就阻塞 fmt.Printf("服务器在等待客户端%s发送信息\n", conn.RemoteAddr().String()) n, err := conn.Read(buf) if err != nil { fmt.Printf("客户端退出 err=%v", err) return } //显示客户端发送的内容到服务器的终端 fmt.Println(string(buf[:n])) } } func main() { fmt.Println("服务器开始监听...") //tcp表示使用的网络协议是tcp,0.0.0.0:8888表示在本地监听8888端口 listen, err := net.Listen("tcp", "0.0.0.0:8888") if err != nil { fmt.Println("listen err=", err) return } defer listen.Close() //循环等待客户端来链接 for { fmt.Println("等待客户端来链接...") conn, err := listen.Accept() if err != nil { fmt.Println("Accept() err=", err) } else { fmt.Printf("Accept() success conn=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String()) } go process(conn) } }
客户端client.go
package main import ( "bufio" "fmt" "net" "os" ) //客户端功能:能链接到服务器端的8888端口 //客户端可以发送单行数据,然后就退出 //能通过终端输入数据(输入一行发送一行), 并发送给服务器端 //在终端输入 exit,表示退出程序 func main() { conn, err := net.Dial("tcp", "10.92.120.203:8888") if err != nil { fmt.Println("client dial err=", err) return } //客户端可以发送单行数据,然后就退出 reader := bufio.NewReader(os.Stdin) //io.Stdin代表标准输入 //从终端读取一行用户输入,并准备发送服务器 line, err := reader.ReadString('\n') if err != nil { fmt.Println("readString err=", err) } //将line发送给服务器 n, err := conn.Write([]byte(line)) if err != nil { fmt.Println("conn.Write err=", err) } fmt.Printf("客户端发送了%d字节的数据,并退出", n) }
对客户端client.go做改进:
package main import ( "fmt" "net" ) //服务器端功能:在8888端口监听,可以和多个客户端创建链接,链接成功后,客户端可以发送数据,服务器端接受数据,并显示在终端上 func process(conn net.Conn) { defer conn.Close() for { buf := make([]byte, 1024) //等待客户端通过conn发送信息,如果客户端没有发送消息,那么协程就阻塞 fmt.Printf("服务器在等待客户端%s发送信息\n", conn.RemoteAddr().String()) n, err := conn.Read(buf) if err != nil { fmt.Printf("客户端退出 err=%v", err) return } //显示客户端发送的内容到服务器的终端 fmt.Println(string(buf[:n])) } } func main() { fmt.Println("服务器开始监听...") //tcp表示使用的网络协议是tcp,0.0.0.0:8888表示在本地监听8888端口 listen, err := net.Listen("tcp", "0.0.0.0:8888") if err != nil { fmt.Println("listen err=", err) return } defer listen.Close() //循环等待客户端来链接 for { fmt.Println("等待客户端来链接...") conn, err := listen.Accept() if err != nil { fmt.Println("Accept() err=", err) } else { fmt.Printf("Accept() success conn=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String()) } go process(conn) } }
三、海量用户即时通讯系统
1、需求
用户注册
用户登录
显示在线用户列表
群聊(广播)
点对点聊天
离线留言
2、实现功能
(1)、显示客户端登录菜单
main.go
package main import ( "fmt" "os" ) 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 os.Exit(0) default: fmt.Println("你的输入有误,请重新输入") } } //接收用户的输入,显示新的提示信息 if key == 1 { fmt.Println("请输入用户的id") fmt.Scanf("%d\n", &userId) fmt.Println("请输入用户的密码") fmt.Scanf("%d\n", &userPwd) //登录函数在login.go文件中 err := login(userId, userPwd) if err != nil { fmt.Println("登录失败") } else { fmt.Println("登录成功") } } else if key == 2 { fmt.Println("进行用户注册的逻辑......") } }
client.go
package main import "fmt" //登录函数 func login(userId int, userPwd string) (err error) { fmt.Printf(" userId = %d userPwd = %s\n", userId, userPwd) return nil }
(2)、完成用户登录
客户端发送消息长度,服务器端正常接收长度值。
common/message/message.go
package message const ( LoginMesType = "LoginMes" LoginResMesType = "LoginResMes" ) type Message struct { Type string `json:"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"` //返回状态码 500表示用户未注册 200表示登录成功 Error string `json:"error"` //返回错误信息 }
server/main.go
package main import ( "fmt" "net" ) func process(conn net.Conn) { defer conn.Close() //读客户端发送的信息 for { buf := make([]byte, 1024*4) n, err := conn.Read(buf[:4]) if n != 4 || err != nil { fmt.Println("conn.Read err=", err) return } fmt.Println("读到的buf=", buf) } } func main() { //提示信息 fmt.Println("服务器在8889端口监听......") listen, err := net.Listen("tcp", "0.0.0.0:8889") defer listen.Close() 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) } }
client/login.go
package main import ( "common/message" "encoding/binary" "encoding/json" "fmt" "net" ) //登录函数 func login(userId int, userPwd string) (err error) { fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd) //链接到服务器 conn, err := net.Dial("tcp", "localhost:8889") if err != nil { fmt.Println("net.Dial err=", err) return } //准备通过conn发送消息给服务器 var mes message.Message mes.Type = message.LoginMesType //创建一个LoginMes结构体 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赋值给mes.Data字段 mes.Data = string(data) data, err = json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } //data就是要发送的消息 //先把data的长度发送给服务器 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 } fmt.Printf("客户端,发送消息的长度=%d 内容=%s", len(data), string(data)) return }
client/main.go
package main import ( "fmt" "os" ) 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 os.Exit(0) default: fmt.Println("你的输入有误,请重新输入") } } //接收用户的输入,显示新的提示信息 if key == 1 { fmt.Println("请输入用户的id") fmt.Scanf("%d\n", &userId) fmt.Println("请输入用户的密码") fmt.Scanf("%s\n", &userPwd) //登录函数在login.go文件中 err := login(userId, userPwd) if err != nil { fmt.Println("登录失败") } else { fmt.Println("登录成功") } } else if key == 2 { fmt.Println("进行用户注册的逻辑......") } }
客户端发送消息本身,服务端正常接收消息,并根据客户端发送的消息判断用户的合法性。
server/main.go
package main import ( "encoding/json" "fmt" "io" "net" "tcp/common/message" "tcp/common/utils" ) //处理登录请求 func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) { //1.从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 } var resMes message.Message resMes.Type = message.LoginResMesType var loginResMes message.LoginResMes 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 = utils.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.Println("消息类型不存在,无法处理......") } return } func process(conn net.Conn) { defer conn.Close() //读客户端发送的信息 for { //读取数据包,直接封装成一个函数readPkg() mes, err := utils.ReadPkg(conn) if err != nil { if err == io.EOF { fmt.Println("客户端退出,服务器也退出...") return } else { fmt.Println("readPkg err=", err) return } } fmt.Println("mes=", mes) err = serverProcessLogin(conn, &mes) if err != nil { return } } } func main() { //提示信息 fmt.Println("服务器在8889端口监听......") listen, err := net.Listen("tcp", "0.0.0.0:8889") defer listen.Close() 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) } }
client/login.go
package main import ( "encoding/binary" "encoding/json" "fmt" "net" "tcp/common/message" "tcp/common/utils" ) //登录函数 func login(userId int, userPwd string) (err error) { fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd) //链接到服务器 conn, err := net.Dial("tcp", "localhost:8889") if err != nil { fmt.Println("net.Dial err=", err) return } //准备通过conn发送消息给服务器 var mes message.Message mes.Type = message.LoginMesType //创建一个LoginMes结构体 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赋值给mes.Data字段 mes.Data = string(data) data, err = json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } //data就是要发送的消息 //先把data的长度发送给服务器 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 } fmt.Printf("客户端,发送消息的长度=%d 内容=%s", len(data), string(data)) //发送消息本身 _, err = conn.Write(data) if err != nil { fmt.Println("conn.Write(data) fail", err) return } //休眠20 //time.Sleep(20 * time.Second) //fmt.Println("休眠20秒...") //处理服务器端返回的消息 mes, err = utils.ReadPkg(conn) 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 }
client/main.go
package main import ( "fmt" "os" ) 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 os.Exit(0) default: fmt.Println("你的输入有误,请重新输入") } } //接收用户的输入,显示新的提示信息 if key == 1 { fmt.Println("请输入用户的id") fmt.Scanf("%d\n", &userId) fmt.Println("请输入用户的密码") fmt.Scanf("%s\n", &userPwd) //登录函数在login.go文件中 login(userId, userPwd) //if err != nil { // fmt.Println("登录失败") //} else { // fmt.Println("登录成功") //} } else if key == 2 { fmt.Println("进行用户注册的逻辑......") } }
common/utils/utils.go
package utils import ( "encoding/binary" "encoding/json" "fmt" "net" "tcp/common/message" ) func ReadPkg(conn net.Conn) (mes message.Message, err error) { buf := make([]byte, 1024*4) fmt.Println("读取客户端发送的数据") _, err = conn.Read(buf[:4]) if err != nil { //fmt.Println("conn.Read err=", err) //err=errors.New("read pkg header error") return } fmt.Println("读到的buf=", buf) //根据buf[:4]转成一个uint32类型 var pkgLen uint32 pkgLen = binary.BigEndian.Uint32(buf[0:4]) //根据pkgLen读取消息内容 n, err := conn.Read(buf[:pkgLen]) if n != int(pkgLen) || err != nil { //fmt.Println("conn.Read fail err=", err) //err=errors.New("read pkg header error") return } //pkgLen反序列化成message.Message err = json.Unmarshal(buf[:pkgLen], &mes) if err != nil { fmt.Println("json.Unmarsha err=", err) return } return } func WritePkg(conn net.Conn, data []byte) (err error) { //先发送一个长度给对方 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 }
common/message/message.go
package message const ( LoginMesType = "LoginMes" LoginResMesType = "LoginResMes" RegisterMesType = "RegisterMes" ) type Message struct { Type string `json:"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"` //返回状态码 500表示用户未注册 200表示登录成功 Error string `json:"error"` //返回错误信息 } type RegisterMes struct { }
代码结构:
client/main/main.go
package main import ( "fmt" "os" "tcp/client/process" ) var userId int var userPwd string var userName string func main() { //接收用户的选择 var key int //判断是否还继续显示菜单 //var loop = true for true { 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) //loop = false up := &process.UserProcess{} up.Login(userId, userPwd) case 2: fmt.Println("注册用户") fmt.Println("请输入用户ID:") fmt.Scanf("%d\n", &userId) fmt.Println("请输入用户密码:") fmt.Scanf("%s\n", &userPwd) fmt.Println("请输入用户名字(nickname):") fmt.Scanf("%s\n", &userName) up := process.UserProcess{} up.Register(userId, userPwd, userName) //loop = false case 3: fmt.Println("退出系统") //loop = false os.Exit(0) default: fmt.Println("你的输入有误,请重新输入") } } }
client/model/curUser.go
package model import ( "net" "tcp/common/message" ) //在客户端很多地方会使用到curUser,将其作为全局的 type CurUser struct { Conn net.Conn message.User }
client/process/server.go
package process import ( "encoding/json" "fmt" "net" "os" "tcp/client/utils" "tcp/common/message" ) //显示登录成功后的界面 func ShowMenu() { fmt.Println("--------恭喜XXX登录成功---------") fmt.Println("--------1 显示在线用户列表---------") fmt.Println("--------2 发送消息---------") fmt.Println("--------3 信息列表---------") fmt.Println("--------4 退出系统---------") fmt.Println("--------请选择(1-4)---------") var key int var content string fmt.Scanf("%d\n", &key) //创建SmsProcess实例:总会使用到SmsProcess实例,所以将其定义在switch外部 smsProcess := &SmsProcess{} switch key { case 1: //fmt.Println("显示在线用户列表") outputOnlineUser() case 2: fmt.Println("请输入群聊消息") fmt.Scanf("%s\n", &content) smsProcess.SendGroupMes(content) 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("客户端正在等待读取服务器发送的消息") mes, err := tf.ReadPkg() if err != nil { fmt.Println("tf.ReadPkg err=", err) return } //fmt.Printf("mes=%v\n", mes) switch mes.Type { case message.NotifyUserStatusMesType: //1. 取出.NotifyUserStatusMes var notifyUserStatusMes message.NotifyUserStatusMes json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes) //2. 把这个用户的信息,状态保存到客户map[int]User中 updateUserStatus(¬ifyUserStatusMes) //处理群发消息 case message.SmsMesType: outputGroupMes(&mes) default: fmt.Println("服务器端返回了一个未知消息类型") } } }
client/process/smsMgr.go
package process import ( "encoding/json" "fmt" "tcp/common/message" ) func outputGroupMes(mes *message.Message) { //显示即可 var smsMes message.SmsMes err := json.Unmarshal([]byte(mes.Data), &smsMes) if err != nil { fmt.Println("json.UnMarshal err=", err.Error()) return } //显示消息 info := fmt.Sprintf("用户ID:\t%d 对大家说:\t%s", smsMes.UserId, smsMes.Content) fmt.Println(info) fmt.Println() }
client/process/smsProcess.go
package process import ( "encoding/json" "fmt" "tcp/client/utils" "tcp/common/message" ) type SmsProcess struct { } //发送群聊消息 func (this *SmsProcess) SendGroupMes(content string) (err error) { var mes message.Message mes.Type = message.SmsMesType var smsMes message.SmsMes smsMes.Content = content smsMes.UserId = CurUser.UserId smsMes.UserStatus = CurUser.UserStatus data, err := json.Marshal(smsMes) if err != nil { fmt.Println("SendGroupMes json.Marshal fail =", err.Error()) return } mes.Data = string(data) data, err = json.Marshal(mes) if err != nil { fmt.Println("SendGroupMes json.Marshal fail =", err.Error()) return } tf := &utils.Transfer{ Conn: CurUser.Conn, } err = tf.WritePkg(data) if err != nil { fmt.Println("SendGroupMes err=", err.Error()) return } return }
client/process/userMgr.go
package process import ( "fmt" "tcp/client/model" "tcp/common/message" ) //客户端维护的map var onlineUsers map[int]*message.User = make(map[int]*message.User, 10) //在用户登录成功后完成对CurUser初始化 var CurUser model.CurUser //显示当前在线的用户 func outputOnlineUser() { //遍历onlineUser fmt.Println("当前在线用户列表:") for id, _ := range onlineUsers { fmt.Println("用户ID:\t", id) } } //处理返回的NotifyUserStatusMes func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) { user, ok := onlineUsers[notifyUserStatusMes.UserId] if !ok { user = &message.User{ UserId: notifyUserStatusMes.UserId, } } user.UserStatus = notifyUserStatusMes.Status onlineUsers[notifyUserStatusMes.UserId] = user outputOnlineUser() }
client/process/userProcess.go
package process import ( "encoding/binary" "encoding/json" "fmt" "net" "os" "tcp/client/utils" "tcp/common/message" ) type UserProcess struct { } //注册函数 func (this *UserProcess) Register(userId int, userPwd, userName string) (err error) { //链接到服务器 conn, err := net.Dial("tcp", "localhost:8889") if err != nil { fmt.Println("net.Dial err=", err) return } defer conn.Close() var mes message.Message mes.Type = message.RegisterMesType var registerMes message.RegisterMes registerMes.User.UserId = userId registerMes.User.UserPwd = userPwd registerMes.User.UserName = userName data, err := json.Marshal(registerMes) if err != nil { fmt.Println("json.Marshal err=", err) return } mes.Data = string(data) //将mes进行序列化 data, err = json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } tf := &utils.Transfer{ Conn: conn, } //发送data给服务器端 err = tf.WritePkg(data) if err != nil { fmt.Println("注册发送信息错误 err=", err) } mes, err = tf.ReadPkg() if err != nil { fmt.Println("ReadPkg err=", err) return } var registerResMes message.RegisterResMes err = json.Unmarshal([]byte(mes.Data), ®isterResMes) if registerResMes.Code == 200 { fmt.Println("注册成功,你需要登录") os.Exit(0) } else { fmt.Println(registerResMes.Error) os.Exit(0) } return } //登录函数 func (this *UserProcess) Login(userId int, userPwd string) (err error) { fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd) //链接到服务器 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 //创建一个LoginMes结构体 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赋值给mes.Data字段 mes.Data = string(data) data, err = json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } //data就是要发送的消息 //先把data的长度发送给服务器 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 } fmt.Printf("客户端,发送消息的长度=%d 内容=%s\n", len(data), string(data)) //发送消息本身 _, err = conn.Write(data) if err != nil { fmt.Println("conn.Write(data) fail", err) return } //处理服务器端返回的消息 tf := &utils.Transfer{ Conn: conn, } mes, err = tf.ReadPkg() fmt.Println("登录时获取到的服务端返回的消息mes=", 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 { //初始化CurUser CurUser.Conn = conn CurUser.UserId = userId CurUser.UserStatus = message.UserOnline //fmt.Println("登录成功") //可以显示当前在线用户列表,遍历loginResMes.UsersId fmt.Println("当前在线用户列表如下:") for _, v := range loginResMes.UsersId { //如果我们要求不显示自己在线,下面我们增加一个代码 if v == userId { continue } fmt.Println("用户id:\t", v) //完成 客户端的 onlineUsers 完成初始化 user := &message.User{ UserId: v, UserStatus: message.UserOnline, } onlineUsers[v] = user } fmt.Print("\n\n") //启动一个协程,该协程保持和服务器端的通讯.如果服务器有数据推送给客户端,则接收并显示在客户端的终端. go serverProcessMes(conn) //1. 显示我们的登录成功的菜单[循环].. for { ShowMenu() } } else { fmt.Println("登录失败", loginResMes.Error) } return }
client/utils/utils.go
package utils import ( "encoding/binary" "encoding/json" "fmt" "net" "tcp/common/message" ) type Transfer struct { Conn net.Conn Buf [8096]byte //传输时使用的缓冲 } func (this *Transfer) ReadPkg() (mes message.Message, err error) { _, err = this.Conn.Read(this.Buf[:4]) if err != nil { //fmt.Println("conn.Read err=", err) //err=errors.New("read pkg header error") return } //fmt.Println("读到的buf=", this.Buf) //根据buf[:4]转成一个uint32类型 var pkgLen uint32 pkgLen = binary.BigEndian.Uint32(this.Buf[:4]) //根据pkgLen读取消息内容 n, err := this.Conn.Read(this.Buf[:pkgLen]) if n != int(pkgLen) || err != nil { //fmt.Println("conn.Read fail err=", err) //err=errors.New("read pkg header error") return } //pkgLen反序列化成message.Message err = json.Unmarshal(this.Buf[:pkgLen], &mes) if err != nil { fmt.Println("json.Unmarsha err=", err) return } return } func (this *Transfer) WritePkg(data []byte) (err error) { //先发送一个长度给对方 var pkgLen uint32 pkgLen = uint32(len(data)) //var buf [4]byte binary.BigEndian.PutUint32(this.Buf[: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 }
common/message/message.go
package message const ( LoginMesType = "LoginMes" LoginResMesType = "LoginResMes" RegisterMesType = "RegisterMes" RegisterResMesType = "RegisterResMes" NotifyUserStatusMesType = "NotifyUserStatusMes" SmsMesType = "SmsMes" ) //定义几个用户状态的常量 const ( UserOnline = iota UserOffline UserBusyStatus ) type Message struct { Type string `json:"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"` //返回状态码 500表示用户未注册 200表示登录成功 Error string `json:"error"` //返回错误信息 UsersId []int //保存用户ID的切片 } type RegisterMes struct { User User `json:"user"` } type RegisterResMes struct { Code int `json:"code"` //返回状态码 400表示用户名已存在 200表示注册成功 Error string `json:"error"` //返回错误信息 } //为了配合服务器推送用户状态变化的消息 type NotifyUserStatusMes struct { UserId int `json:"userId"` Status int `json:"status"` //用户状态 } //增加一个SmsMes发送消息 type SmsMes struct { User //匿名的结构体,继承 Content string `json:"content"` //消息内容 }
common/message/user.go
package message //用户结构体 type User struct { //为了序列化和反序列化成功,必须保证用户信息的json字符串的key和结构体的字段对应的tag名字一致 UserId int `json:"userId"` UserPwd string `json:"userPwd"` UserName string `json:"userName"` UserStatus int `json:"userStatus"` Sex string `json:"sex"` }
server/main/main.go
package main import ( "fmt" "net" "tcp/server/model" ) func processor(conn net.Conn) { defer conn.Close() //创建一个总控 processor := &Processor{ Conn: conn, } err := processor.Process2() if err != nil { fmt.Println("客户端和服务器端通信的协程错误err=", err) return } } //对UserDao初始化 func initUserDao() { //pool本身就是全局变量 //需要注意初始化顺序问题:initPool在initUserDao之前初始化 model.MyUserDao = model.NewUserDao(pool) } func init() { //当服务器启动时,就初始化Redis链接池 initPool("localhost:6379", 16, 0, 300) initUserDao() } func main() { //提示信息 fmt.Println("服务器[新的结构]在8889端口监听......") listen, err := net.Listen("tcp", "0.0.0.0:8889") defer listen.Close() 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 processor(conn) } }
server/main/processor.go
package main import ( "fmt" "io" "net" "tcp/common/message" "tcp/server/process" "tcp/server/utils" ) type Processor struct { Conn net.Conn } //根据客户端发送消息种类不同,决定调用哪个函数来处理 func (this *Processor) ServerProcessMes(mes *message.Message) (err error) { //验证是否能够收到客户端发送的群聊消息 fmt.Println("mes=", mes) switch mes.Type { case message.LoginMesType: //处理登录 up := &process.UserProcess{ Conn: this.Conn, } err = up.ServerProcessLogin(mes) case message.RegisterMesType: //处理注册 up := &process.UserProcess{ Conn: this.Conn, } err = up.ServerProcessRegister(mes) case message.SmsMesType: //创建一个SmsProcess实例完成转发群聊消息 smsProcess := &process.SmsProcess{} smsProcess.SendGroupMes(mes) default: fmt.Println("消息类型不存在,无法处理......") } return } func (this *Processor) Process2() (err error) { //读客户端发送的信息 for { //读取数据包,直接封装成一个函数readPkg() 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 } } }
server/main/redis.go
package main import ( "github.com/garyburd/redigo/redis" "time" ) //定义全局pool var pool *redis.Pool func initPool(address string, maxIdle, maxActive int, idleTimeout time.Duration) { pool = &redis.Pool{ MaxIdle: maxIdle, //最大空闲链接数 MaxActive: maxActive, // 表示和数据库的最大链接数,0表示没有限制 IdleTimeout: idleTimeout, // 最大空闲时间 //初始化链接的代码, 链接哪个ip的redis Dial: func() (redis.Conn, error) { return redis.Dial("tcp", address) }, } }
server/model/error.go
package model import "errors" //根据业务逻辑的需要自定义一些错误 var ( ERROR_USER_NOTEXISTS = errors.New("用户不存在") ERROR_USER_EXISTS = errors.New("用户已存在") ERROR_USER_PWD = errors.New("密码不正确") )
server/model/userDao.go
package model import ( "encoding/json" "fmt" "github.com/garyburd/redigo/redis" "tcp/common/message" ) //在服务器启动后就初始化一个userDao实例 //将其做成全局的变量,在需要和Redis交互时直接使用即可 var ( MyUserDao *UserDao ) //定义UserDao结构体完成对User结构体的各种操作 type UserDao struct { pool *redis.Pool } //使用工厂模式,创建一个UserDao实例 func NewUserDao(pool *redis.Pool) (userDao *UserDao) { userDao = &UserDao{ pool: pool, } return } //1.根据用户ID返回一个User实例+err func (this *UserDao) getUserById(conn redis.Conn, id int) (user *message.User, err error) { //通过给定的ID去Redis查询用户 res, err := redis.String(conn.Do("HGet", "users", id)) if err != nil { //在users哈希中没有找到对应ID if err == redis.ErrNil { err = ERROR_USER_NOTEXISTS } return } user = &message.User{} //res反序列化成User实例 err = json.Unmarshal([]byte(res), &user) if err != nil { fmt.Println("json.Unmarshal err=", err) return } return } //Login完成对用户登录的校验: //如果用户的ID和pwd都正确,则返回一个user实例; //如果用户的ID或pwd有错误,则返回对应的错误信息 func (this *UserDao) Login(userId int, userPwd string) (user *message.User, err error) { //先从UserDao的链接池中取出一个链接 conn := this.pool.Get() defer conn.Close() user, err = this.getUserById(conn, userId) if err != nil { return } if user.UserPwd != userPwd { err = ERROR_USER_PWD return } return } //注册 func (this *UserDao) Register(user *message.User) (err error) { //先从UserDao的链接池中取出一个链接 conn := this.pool.Get() defer conn.Close() _, err = this.getUserById(conn, user.UserId) if err == nil { err = ERROR_USER_EXISTS return } //这时ID在Redis中还没有,可以完成注册 data, err := json.Marshal(user) //序列化 if err != nil { return } //入库 conn.Do("HSet", "users", user.UserId, string(data)) if err != nil { fmt.Println("保存注册用户错误 err=", err) return } return }
server/process/smsProcess.go
package process import ( "encoding/json" "fmt" "net" "tcp/common/message" "tcp/server/utils" ) type SmsProcess struct{} //转发消息 func (this *SmsProcess) SendGroupMes(mes *message.Message) { //遍历服务器端onlineUsers map[int]*UserProcess,将消息转发出去 //取出mes的内容SmsMes var smsMes message.SmsMes err := json.Unmarshal([]byte(mes.Data), &smsMes) if err != nil { fmt.Println("json.UnMarshal err=", err) return } data, err := json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } for id, up := range userMgr.onlineUsers { //过滤掉自己,不要把消息发送给自己 if id == smsMes.UserId { continue } this.SendMesToEachOnlineUser(data, up.Conn) } } func (this *SmsProcess) SendMesToEachOnlineUser(data []byte, conn net.Conn) { tf := &utils.Transfer{ Conn: conn, } err := tf.WritePkg(data) if err != nil { fmt.Println("转发消息失败 err=", err) } }
server/process/userMgr.go
package process import "fmt" //UserMgr实例在服务器端有且只有一个,在很多地方都会使用到,因此将其定义为全局变量 var ( userMgr *UserMgr ) type UserMgr struct { onlineUsers map[int]*UserProcess } //完成对userMgr初始化工作 func init() { userMgr = &UserMgr{ onlineUsers: make(map[int]*UserProcess, 1024), } } //完成对onlineUser添加 func (this *UserMgr) AddOnlineUser(up *UserProcess) { this.onlineUsers[up.UserId] = up } //删除 func (this *UserMgr) DelOnlineUser(userId int) { delete(this.onlineUsers, userId) } //返回当前所有在线的用户 func (this *UserMgr) GetAllOnlineUser() map[int]*UserProcess { return this.onlineUsers } //根据ID返回对应的值 func (this *UserMgr) GetOnlineUserById(userId int) (up *UserProcess, err error) { //从map中取出一直 up, ok := this.onlineUsers[userId] //要查找的用户的当前不在线 if !ok { err = fmt.Errorf("用户%d不存在", userId) return } return }
server/process/userProcess.go
package process import ( "encoding/json" "fmt" "net" "tcp/common/message" "tcp/server/model" "tcp/server/utils" ) type UserProcess struct { Conn net.Conn //表示conn是属于哪个用户的 UserId int } //通知所有在线用户 //userId要通知其他的在线用户,我上线 func (this *UserProcess) NotifyOthersOnlineUser(userId int) { //遍历onlineUsers,然后一个一个发送NotifyUserStatusMes for id, up := range userMgr.onlineUsers { //跳过自己 if id == userId { continue } //开始通知其他在线用户 up.NotifyMeOnline(userId) } } func (this *UserProcess) NotifyMeOnline(userId int) { var mes message.Message mes.Type = message.NotifyUserStatusMesType var notifyUserStatusMes message.NotifyUserStatusMes notifyUserStatusMes.UserId = userId notifyUserStatusMes.Status = message.UserOnline data, err := json.Marshal(notifyUserStatusMes) if err != nil { fmt.Println("json.Marshal err=", err) return } mes.Data = string(data) data, err = json.Marshal(mes) if err != nil { fmt.Println("json.Marshal err=", err) return } tf := &utils.Transfer{ Conn: this.Conn, } err = tf.WritePkg(data) if err != nil { fmt.Println("NotifyMeOnline err=", err) return } } func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error) { var registerMes message.RegisterMes err = json.Unmarshal([]byte(mes.Data), ®isterMes) if err != nil { fmt.Println("json.Unmarshal fail err=", err) return } var resMes message.Message resMes.Type = message.RegisterResMesType var registerResMes message.RegisterResMes err = model.MyUserDao.Register(®isterMes.User) if err != nil { if err == model.ERROR_USER_EXISTS { registerResMes.Code = 505 registerResMes.Error = model.ERROR_USER_EXISTS.Error() } else { registerResMes.Code = 506 registerResMes.Error = "注册发生未知错误..." } } else { registerResMes.Code = 200 } data, err := json.Marshal(registerResMes) if err != nil { fmt.Println("json.Marshal fail", err) return } resMes.Data = string(data) data, err = json.Marshal(resMes) if err != nil { fmt.Println("json.Marshal fail", err) return } tf := &utils.Transfer{ Conn: this.Conn, } err = tf.WritePkg(data) return } //处理登录请求 func (this *UserProcess) ServerProcessLogin(mes *message.Message) (err error) { //1.从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 } var resMes message.Message resMes.Type = message.LoginResMesType var loginResMes message.LoginResMes //需要去Redis数据库完成用户的登录验证 user, err := model.MyUserDao.Login(loginMes.UserId, loginMes.UserPwd) if err != nil { if err == model.ERROR_USER_NOTEXISTS { loginResMes.Code = 500 loginResMes.Error = err.Error() } else if err == model.ERROR_USER_PWD { loginResMes.Code = 403 loginResMes.Error = err.Error() } else { loginResMes.Code = 505 loginResMes.Error = "服务器内部错误..." } } else { loginResMes.Code = 200 //登录成功的用户的userId赋给this this.UserId = loginMes.UserId //用户登录成功,将其放入到userMgr中 userMgr.AddOnlineUser(this) writeLoginResMes(loginResMes,resMes,this) //通知其它的在线用户, 我上线了 this.NotifyMeOnline(loginMes.UserId) //将当前在线用户的ID放入到loginResMes.UserId for id, _ := range userMgr.onlineUsers { loginResMes.UsersId = append(loginResMes.UsersId, id) } fmt.Println(*user, "登录成功") } return } func writeLoginResMes(loginResMes message.LoginResMes, resMes message.Message, this *UserProcess) { //将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 } tf := &utils.Transfer{ Conn: this.Conn, } //发送data err = tf.WritePkg(data) }
server/utils/utils.go
package utils import ( "encoding/binary" "encoding/json" "fmt" "net" "tcp/common/message" ) type Transfer struct { Conn net.Conn Buf [8096]byte //传输时使用的缓冲 } func (this *Transfer) ReadPkg() (mes message.Message, err error) { //buf := make([]byte, 1024*4) fmt.Println("读取客户端发送的数据") _, err = this.Conn.Read(this.Buf[:4]) if err != nil { //fmt.Println("conn.Read err=", err) //err=errors.New("read pkg header error") return } //fmt.Println("读到的buf=", this.Buf) //根据buf[:4]转成一个uint32类型 var pkgLen uint32 pkgLen = binary.BigEndian.Uint32(this.Buf[:4]) //根据pkgLen读取消息内容 n, err := this.Conn.Read(this.Buf[:pkgLen]) if n != int(pkgLen) || err != nil { //fmt.Println("conn.Read fail err=", err) //err=errors.New("read pkg header error") return } //pkgLen反序列化成message.Message err = json.Unmarshal(this.Buf[:pkgLen], &mes) if err != nil { fmt.Println("json.Unmarsha err=", err) return } return } func (this *Transfer) WritePkg(data []byte) (err error) { //先发送一个长度给对方 var pkgLen uint32 pkgLen = uint32(len(data)) //var buf [4]byte binary.BigEndian.PutUint32(this.Buf[: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 }