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发送信息
", 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), &notifyUserStatusMes)
			//2. 把这个用户的信息,状态保存到客户map[int]User中
			updateUserStatus(&notifyUserStatusMes)
			//处理群发消息
		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), &registerResMes)
	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), &registerMes)
	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(&registerMes.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
}
原文地址:https://www.cnblogs.com/xidian2014/p/10681647.html