一、端口分类
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发送信息
", 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
", 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('
')
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发送信息
", 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
", 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(" 1 登录聊天室")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println(" 请选择(1-3)")
fmt.Scanf("%d
", &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
", &userId)
fmt.Println("请输入用户的密码")
fmt.Scanf("%d
", &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
", 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
", 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(" 1 登录聊天室")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println(" 请选择(1-3)")
fmt.Scanf("%d
", &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
", &userId)
fmt.Println("请输入用户的密码")
fmt.Scanf("%s
", &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
", 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(" 1 登录聊天室")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println(" 请选择(1-3)")
fmt.Scanf("%d
", &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
", &userId)
fmt.Println("请输入用户的密码")
fmt.Scanf("%s
", &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(" 1 登录聊天室")
fmt.Println(" 2 注册用户")
fmt.Println(" 3 退出系统")
fmt.Println(" 请选择(1-3)")
fmt.Scanf("%d
", &key)
switch key {
case 1:
fmt.Println("登录聊天室")
fmt.Println("请输入用户的id")
fmt.Scanf("%d
", &userId)
fmt.Println("请输入用户的密码")
fmt.Scanf("%s
", &userPwd)
//loop = false
up := &process.UserProcess{}
up.Login(userId, userPwd)
case 2:
fmt.Println("注册用户")
fmt.Println("请输入用户ID:")
fmt.Scanf("%d
", &userId)
fmt.Println("请输入用户密码:")
fmt.Scanf("%s
", &userPwd)
fmt.Println("请输入用户名字(nickname):")
fmt.Scanf("%s
", &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
", &key)
//创建SmsProcess实例:总会使用到SmsProcess实例,所以将其定义在switch外部
smsProcess := &SmsProcess{}
switch key {
case 1:
//fmt.Println("显示在线用户列表")
outputOnlineUser()
case 2:
fmt.Println("请输入群聊消息")
fmt.Scanf("%s
", &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
", 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: %d 对大家说: %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: ", 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
", 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
", 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: ", v)
//完成 客户端的 onlineUsers 完成初始化
user := &message.User{
UserId: v,
UserStatus: message.UserOnline,
}
onlineUsers[v] = user
}
fmt.Print("
")
//启动一个协程,该协程保持和服务器端的通讯.如果服务器有数据推送给客户端,则接收并显示在客户端的终端.
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
}