Golang学习笔记(Go语法基础+即时通信系统小项目)

fmt 包

导入fmt包:import "fmt"

输出:PrintlnPrintfPrint

注释代码:

// 单行注释
/*

多行注释

*/

定义变量:var a = "aaa" 变量定义,必须使用,不然会报错。

Printf 是格式化输出

var a int = 10
var b int = 3
var c int = 5
fmt.Printf("a = %v b = %v c = %v\n", a, b, c);
fmt.Printf("a = %d, a 的类型是 %T\n", a, a)

Go语言关键字

首先先认识一下Go语言中关键字,心里有个印象,让初学者有个眼熟就行。记不住没关系,我会在下面语法反复提到。在这里之所以提出来,就是让你们看一下,看的看的就记住了。

关键字 作用 一级分类 二级分类 三级分类
var 变量声明 基本结构 变量与常量 -
const 常量声明 基本结构 变量与常量 -
package 包声明 基本结构 包管理 -
import 包引用 基本结构 包管理 -
func 函数声明 基本组件 函数 -
return 函数返回 基本组件 函数 -
interface 接口 基本组件 自定义类型 -
struct 结构体 基本组件 自定义类型 -
type 定义类型 基本组件 自定义类型 -
map 基本组件 引用类型 -
range 基本组件 引用类型 -
go 流程控制 并发 -
select 流程控制 并发 -
chan 流程控制 并发 -
if 流程控制 单任务流程控制 单分支流程
else 流程控制 单任务流程控制 单分支流程
switch 流程控制 单任务流程控制 多分支流程
case 流程控制 单任务流程控制 多分支流程
default 流程控制 单任务流程控制 多分支流程
fallthrough 流程控制 单任务流程控制 多分支流程
for 流程控制 单任务流程控制 循环流程
break 流程控制 单任务流程控制 循环流程
continue 流程控制 单任务流程控制 循环流程
goto 流程控制 单任务流程控制
defer 流程控制 延时流程控制

数据类型

在 Go 编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

Go 语言按类别有以下几种数据类型:

类型 长度 默认值 说明
bool 1 false
byte 1 0 uint8
int,uint 4,8 0 默认整数类型,依据目标平台,32或64
int8,uint8 1 0 -128127,0127
int16,uint16 2 0 -215~(215)-1,0~(2^15)-1
int32,uint32 4 0 -231~(231)-1,0~(2^31)-1
int64,uint64 8 0 -263~(263)-1,0~(2^63)-1
float32 4 0.0
float64 8 0.0 默认浮点数类型
string "" 字符串,默认值为空字符串,而非NULL
array 数组
struct 结构体
interface nil 接口
function nil 函数
map nil 字典,引用类型
slice nil 切片,引用类型
channel nil 通道,引用类型

常量

常量的定义:

const PI = 3.1415926535
const PI float64 = 3.1415926535
const(
	PI = 3.1415926535
    N = 1005
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1,并且没有赋值的常量默认会应用上一行的赋值表达式。

变量

var 会自动初始化。

我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,而这个时候再在 Example 4.4.1 的最后一个声明语句写上 var 关键字就显得有些多余了,因此我们可以将它们简写为 a := 50b := false

这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

//方法一:声明一个变量 默认值是0
var a int

//方法二:声明一个变量,初始化一个值
var b int = 100

//方法三:在初始化的时候,可以省去数据类型,通过自动匹配当前的变量的数据类型
var c = 100

//方法四:(常用的方法)省去 var 关键字,直接自动匹配
e:= 100

//申明全局变量:方法一、方法二、方法三都可以,方法四不行。

字符串

HasPrefix() 判断字符串 s 是否以 prefix 开头:

strings.HasPrefix(s, prefix string) bool

HasSuffix() 判断字符串 s 是否以 suffix 结尾:

strings.HasSuffix(s, suffix string) bool

Contains() 判断字符串 s 是否包含 substr

strings.Contains(s, substr string) bool

函数

可以有多个返回值。

import 导包路径问题与 init 方法调用流程

image-20250304222659162

import匿名及别名导包方式

在导入的包前面加别名可以匿名导包

import (
	_ "./test-4/lib1"
	mylib2 "./test-4/lib2"
    . "./test-4/lib3"
)

defer

在函数调用最后执行,类似于 C++ 析构函数。

func main() {

	defer fmt.Println("main end1")
	defer fmt.Println("main end2")

	fmt.Println("main::hello go 1")
	fmt.Println("main::hello go 1")
}

main::hello go 1
main::hello go 1
main end2       
main end1
// defer 与 return 谁先谁后

// return 比 defer 先执行,多个 defer 按照压栈顺序来执行。

数组与动态数组

package main

import "fmt"

func main() {
	// 固定长度的数组
	var myArray1 [10]int

	myArray2 := [10]int{1, 2, 3, 4}

	for i := 0; i < len(myArray1); i++ {
		fmt.Println(myArray1[i])
	}

	for index, value := range myArray2 {
		fmt.Println("index =", index, "value = ", value)
	}

}
package main

import "fmt"

func main() {
	// 动态数组, 切片 slice
	myArray := []int{1, 2, 3, 4}

	fmt.Printf("myArray type is %T\n", myArray)

}

slice 的四种定义方式

package main

import "fmt"

func main() {
	// 申明 slice 是一个切片,并且初始化,默认值是 1,2,3, 长度 len 是 3
	// slice1 := []int{1, 2, 3}

	var slice2 []int
	// slice2 = make([]int, 3)

	// var slice3 []int = make([]int, 3)

	// slice4 := make([]int, 4);

	fmt.Printf("len = %d, slice = %v\n", len(slice2), slice2)

	if slice2 == nil {
		fmt.Println("slice2 是空切片")
	} else {
		fmt.Println("slice2 不是空切片")
	}
}

slice 切片追加与截取

lenslice 的长度。cap 是分配的空间,超出时会倍增开辟空间。

package main

import "fmt"

func main() {
	var numbers = make([]int, 3, 5)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

	numbers = append(numbers, 1)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

	numbers = append(numbers, 2)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

	numbers = append(numbers, 3)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

}
package main

import "fmt"

func main() {
	s := []int{1, 2, 3}
	s1 := s[0:2]
	fmt.Println(s1)

	// copy 可以深拷贝
	s2 := make([]int, 3)
	copy(s2, s)
	fmt.Println(s2)
}

map 的三种申明方式

package main

import "fmt"

func main() {
	var myMap1 map[string]string
	if myMap1 == nil {
		fmt.Println("myMap 是一个空 map")
	}
	// 1
	myMap1 = make(map[string]string, 10)
	myMap1["one"] = "java"
	myMap1["two"] = "c++"
	myMap1["three"] = "python"
	fmt.Println(myMap1)

	// 2
	myMap2 := make(map[int]string)
	myMap2[1] = "java"
	myMap2[2] = "c++"
	myMap2[3] = "python"
	fmt.Println(myMap2)

	// 3
	myMap3 := map[string]string{
		"one":   "php",
		"two":   "c++",
		"three": "python",
	}
	fmt.Println(myMap3)

}
package main

import "fmt"

func main() {
	mp := make(map[string]string)

	// 添加
	mp["China"] = "Beijing"
	mp["Japan"] = "Tokyo"

	// 遍历
	for key, val := range mp {
		fmt.Println("key = ", key)
		fmt.Println("val = ", val)
	}

	// 删除
	delete(mp, "China")

	// 遍历
	for key, val := range mp {
		fmt.Println("key = ", key)
		fmt.Println("val = ", val)
	}
}

map 传入函数时是引用传递。

类的封装

package main

import "fmt"

// 对 int 起别名
// type myint int

//如果类的首字母大写表示该类外部可以访问,否则外部不可以访问
type Hero struct {
	Name  string
	Ad    int
	Level int
}

func (this *Hero) Show() {
	fmt.Println("Name = ", this.Name)
	fmt.Println("Ad = ", this.Ad)
	fmt.Println("Level = ", this.Level)
}

func (this *Hero) GetName() string {
	return this.Name
}

func (this *Hero) SetName(s string) {
	this.Name = s
}

func main() {
	// var a myint = 10
	// fmt.Println(a)
	hero := Hero{Name: "zhang3", Ad: 100, Level: 1}

	hero.Show()

	hero.SetName("Lib")

	hero.Show()
}

类的继承

package main

import "fmt"

type Human struct {
	name string
	sex  string
}

func (this *Human) Eat() {
	fmt.Println("Human eat()...")
}

func (this *Human) Walk() {
	fmt.Println("Human walk()...")
}

// ========>

type SuperMan struct {
	Human

	level int
}

func (this *SuperMan) Eat() {
	fmt.Println("Superman Eat()...")
}

func (this *SuperMan) Fly() {
	fmt.Println("SuperMan Fly()...")
}

func main() {
	h := Human{"zhang3", "female"}
	h.Eat()
	h.Walk()

	s := SuperMan{Human{"Li4", "male"}, 88}
	s.Eat()  // 子类
	s.Walk() // 父类
	s.Fly()  // 子类
}

类的多态

interface 实现。

package main

import "fmt"

// interface 相当于一个指针类型
type AnimalIF interface {
	Sleep()
	GetColor() string
	GetType() string
}

type Cat struct {
	Color string
}

func (this *Cat) Sleep() {
	fmt.Println("Cat sleep()...")
}
func (this *Cat) GetColor() string {
	return this.Color
}
func (this *Cat) GetType() string {
	return "Cat"
}

type Dog struct {
	Color string
}

func (this *Dog) Sleep() {
	fmt.Println("Dog sleep()...")
}
func (this *Dog) GetColor() string {
	return this.Color
}
func (this *Dog) GetType() string {
	return "Dog"
}

func ShowAnimal(NewAnimal AnimalIF) {
	NewAnimal.Sleep()
	fmt.Println("Color = ", NewAnimal.GetColor())
	fmt.Println("Type = ", NewAnimal.GetType())
}

func main() {
	/*
		var animal AnimalIF
		animal = &Cat{"Green"}
		animal.Sleep()

		animal = &Dog{"Yellow"}
		animal.Sleep()
	*/
	cat := Cat{"Green"}
	dog := Dog{"Yellow"}
	ShowAnimal(&cat)
	ShowAnimal(&dog)
}

interface 作为万能变量

package main

import "fmt"

//interface{} 是万能数据类型
func myFunc(arg interface{}) {
	fmt.Println("mtFanc is called...")
	fmt.Println(arg)
	//interface{}
	val, ok := arg.(string)
	if !ok {
		fmt.Println("arg is not string type")
	} else {
		fmt.Println("arg is string, val =", val)
		fmt.Printf("val type is %T\n", val)
	}
}

type Book struct {
	auth string
	Name string
}

func main() {
	book := Book{"qwq", "wow"}
	myFunc(book)
	myFunc(100)
	myFunc("abc")
	myFunc(3.14)
}

golang反射reflect机制用法

我们可以把变量看成一个 pair<type, value> 的键值对,type 包含 static typeconcrete type

Reflect

reflect.ValueOf(interface{}) 	//得到当前变量的值。
reflect.TypeOf(interface{}) 	//得到当前变量的数据类型。
name.NumField()					//获取name struct的变量数量。
name.Field(i)					//获取name struct的第 i 个变量。
name.Field(i).interface{} 		//获取name struct的第 i 个变量并获得他的值。
name.NumMethod()				//获取name struct的方法数量。
name.Method(i)					//获取name struct的第 i 个方法。
name.Name						//获取name的 变量/方法 名字
name.Type						//获取name的 变量/方法 类型
package main

import (
	"fmt"
	"reflect"
)

type User struct {
	ID   int
	Name string
	Age  int
}

func (this User) Call() {
	fmt.Println("user is called...")
	fmt.Printf("%v\n", this)
}

func Get(input interface{}) {
	// 获取 input 的 type
	inputType := reflect.TypeOf(input)
	fmt.Println("inputType is :", inputType.Name())

	// 获取 input 的 value
	inputValue := reflect.ValueOf(input)
	fmt.Println("inputValue is :", inputValue)

	// 获取 input 里面的所有字段的 type
	for i := 0; i < inputType.NumField(); i++ {
		field := inputType.Field(i)
		value := inputValue.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	// 获取 input 里面的所有方法
	for i := 0; i < inputType.NumMethod(); i++ {
		m := inputType.Method(i)
		fmt.Printf("%s : %v\n", m.Name, m.Type)
	}
}

func main() {
	user := User{1, "abc", 18}
	Get(user)
}

golang反射解析结构体标签Tag

package main

import (
	"fmt"
	"reflect"
)

type resume struct {
	Name string `info:"name" doc:"我的名字"`
	Sex  string `info:"sex"`
}

func findTag(str interface{}) {
	t := reflect.TypeOf(str).Elem()

	for i := 0; i < t.NumField(); i++ {
		taginfo := t.Field(i).Tag.Get("info")
		tagdoc := t.Field(i).Tag.Get("doc")
		fmt.Println("info: ", taginfo, "doc:", tagdoc)
	}
}

func main() {
	var re resume

	findTag(&re)
}

结构体标签在json中的应用

package main

import (
	"encoding/json"
	"fmt"
)

type Movie struct {
	Title string   `json:"title`
	Year  int      `json:"year`
	Price int      `json:"price`
	Actor []string `json:"actor`
}

func main() {
	movie := Movie{"喜剧之王", 2000, 10, []string{"xinye", "zhangbozhi"}}

	// 编码过程,结构体 ---> json
	jsonStr, err := json.Marshal(movie)
	if err != nil {
		fmt.Println("json marshal error", err)
		return
	}
	fmt.Printf("jsonStr = %s\n", jsonStr)

	// 解码过程,jsonstr ---> 结构体
	myMovie := Movie{}
	err = json.Unmarshal(jsonStr, &myMovie)
	if err != nil {
		fmt.Println("json unmarshal error", err)
		return
	}
	fmt.Printf("%v\n", myMovie)
}

创建goroutine

runtime.Goexit() 退出当前的 goroutine

package main

import (
	"fmt"
	"time"
)

// 从 goroutine
func Mynew() {
	for i := 0; i < 10; i++ {
		fmt.Printf("Mynew i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}

// 主 goroutine
func main() {
	go Mynew()
	for i := 0; i < 10; i++ {
		fmt.Printf("Main i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}

channel的基本定义与使用

生产者-消费者关系,channelmain()func() 同步起来:

package main

import "fmt"

func main() {
	// 定义一个 channel
	c := make(chan int)

	go func() {
		defer fmt.Println("func() 结束...")

		fmt.Println("func() 运行...")

		c <- 666
	}()

	num := <-c
	fmt.Println("num =", num)
	fmt.Println("main() 结束...")
}

channel有缓冲与无缓冲同步问题

有缓冲的 channel :

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int, 3)

	fmt.Println("len(c) = ", len(c), "cap(c) = ", cap(c))

	go func() {
		defer fmt.Println("func() 结束...")

		for i := 0; i < 4; i++ {
			fmt.Println("func() 正在传递 i = ", i, "len(c) = ", len(c), "cap(c) = ", cap(c))
			c <- i
			fmt.Println("func() 传递 i = ", i, " 成功!")
		}
	}()
	time.Sleep(2 * time.Second)

	for i := 0; i < 4; i++ {
		num := <-c
		fmt.Println("num = ", num)
	}

	time.Sleep(2 * time.Second)
	fmt.Println("main() 结束...")
}

channel的关闭特点

通过 close(c) 关闭 channel

package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 5; i++ {
			c <- i
		}

		// 关闭 channel
		close(c)
	}()

	for {
		if num, ok := <-c; ok {
			fmt.Println("num =", num)
		} else {
			break
		}
	}

	fmt.Println("Main Finished...")
}

也可通过 range c 来迭代获取 c 的值。

package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 5; i++ {
			c <- i
		}

		// 关闭 channel
		close(c)
	}()

	// for {
	// 	if num, ok := <-c; ok {
	// 		fmt.Println("num =", num)
	// 	} else {
	// 		break
	// 	}
	// }

	for num := range c {
		fmt.Println("num =", num)
	}

	fmt.Println("Main Finished...")
}

channel与select

select {
    case <- chan1:
    	// 如果成功读到 chan1 数据,执行 ...
    case chan2 <- 1:
    	// 如果 chan2 成功写入数据,执行 ...
    default:
    	// 以上都不满足时,执行...
}
package main

import "fmt"

func fib(c, quit chan int) {
	x, y := 1, 0
	for {
		select {
		case c <- x:
			x, y = x+y, x
		case <-quit:
			fmt.Println("quit...")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}

		quit <- 1
	}()

	fib(c, quit)
}

GoModules模式基础环境说明

go env 						// 查看 go 环境
go env -w GO111MUDULE=on	// 打开 GoModules 开关

GoModules 初始化项目

mkdir 										//创建文件夹
go mod init github.com/aceld/module_test 	//创建 go mod 文件
go get module_name 							//手动下载模块

改变模块依赖关系

go mod edit -replace=old=new

即时通信系统

服务器监听:服务器监听是指服务器程序在一个特定的端口上等待来自客户端的连接请求,并且处理这些请求的过程。

一些“新”数据类型:

sync.RWMutex									//读写锁
conn											//通用的面向流的网络连接

一些“新”函数:

net.Listen("tcp", "IP:Port")					//监听 IP:Port 上的信息
func (l *TCPListener) Accept() (Conn, error)  	//等待下一个呼叫,并返回一个该呼叫的Conn接口
type Listener interface {
    // Addr返回该接口的网络地址
    Addr() Addr
    // Accept等待并返回下一个连接到该接口的连接
    Accept() (c Conn, err error)
    // Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
    Close() error
}
type Conn interface {
    // Read从连接中读取数据
    // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
    Read(b []byte) (n int, err error)
    // Write从连接中写入数据
    // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
    Write(b []byte) (n int, err error)
    // Close方法关闭该连接
    // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
    Close() error
    // 返回本地网络地址
    LocalAddr() Addr
    // 返回远端网络地址
    RemoteAddr() Addr
    // 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
    // deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
    // deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
    // 参数t为零值表示不设置期限
    SetDeadline(t time.Time) error
    // 设定该连接的读操作deadline,参数t为零值表示不设置期限
    SetReadDeadline(t time.Time) error
    // 设定该连接的写操作deadline,参数t为零值表示不设置期限
    // 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
    SetWriteDeadline(t time.Time) error
}







server端

server.go

package main

import (
	"fmt"
	"net"
)

type Server struct {
	IP   string
	Port int
}

// 创建一个Server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		IP:   ip,
		Port: port,
	}
	return server
}

func (ts *Server) Handler(conn net.Conn) {
	//...当前链接的业务
	fmt.Println("链接建立成功")
}

// 启动服务器的端口
func (ts *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ts.IP, ts.Port))
	if err != nil {
		fmt.Println("net.Listen() err", err)
	}
	// close listen soket
	defer listener.Close()
	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err", err)
			continue
		}

		//do handler
		go ts.Handler(conn)
	}
}

main.go

package main

func main() {
	server := NewServer("127.0.0.1", 8888)
	server.Start()
}

使用 go build -o server.exe main.go server.go 构建 server.exe

Linux :nc 127.0.0.1:8888 可验证。

Windows:curl 127.0.0.1:8888 可验证。

用户上线及广播功能

server.go

package main

import (
	"fmt"
	"net"
	"sync"
)

type Server struct {
	IP   string
	Port int

	//在线用户的的列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息广播的
	Message chan string
}

// 创建一个Server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		IP:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}
	return server
}

// 监听 Message 广播消息 channer 的 goroutine, 一旦有消息就发送给全部在线的 User
func (ts *Server) ListenMessager() {
	for {
		msg := <-ts.Message

		// 将msg发送给全部在线的User
		ts.mapLock.Lock()
		for _, cli := range ts.OnlineMap {
			cli.C <- msg
		}
		ts.mapLock.Unlock()
	}
}

// 广播消息的方法
func (ts *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
	ts.Message <- sendMsg
}

func (ts *Server) Handler(conn net.Conn) {
	//...当前链接的业务
	//fmt.Println("链接建立成功")

	user := NewUser(conn)

	//用户上线,将用户加入到 onlineMap 中
	ts.mapLock.Lock()
	ts.OnlineMap[user.Name] = user
	ts.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.BroadCast(user, "已上线")

	//当前 handle 阻塞
	select {}
}

// 启动服务器的端口
func (ts *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ts.IP, ts.Port))
	if err != nil {
		fmt.Println("net.Listen() err", err)
	}
	// close listen soket
	defer listener.Close()

	//启动监听 Message 的 goroutine
	go ts.ListenMessager()
	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err", err)
			continue
		}

		//do handler
		go ts.Handler(conn)
	}
}

user.go

package main

import "net"

type User struct {
	Name string
	Addr string
	C    chan string
	Conn net.Conn
}

// 创建一个用户的API
func NewUser(conn net.Conn) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name: userAddr,
		Addr: userAddr,
		C:    make(chan string),
		Conn: conn,
	}

	go user.ListenMessage()

	return user
}

// 监听当前 user channel 的方法,一旦有消息就发送给对端客户端
func (ts *User) ListenMessage() {
	for {
		msg := <-ts.C
		ts.Conn.Write([]byte(msg + "\n"))
	}
}

用户消息广播功能

server.go

package main

import (
	"fmt"
	"io"
	"net"
	"sync"
)

type Server struct {
	IP   string
	Port int

	//在线用户的的列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息广播的
	Message chan string
}

// 创建一个Server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		IP:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}
	return server
}

// 监听 Message 广播消息 channer 的 goroutine, 一旦有消息就发送给全部在线的 User
func (ts *Server) ListenMessager() {
	for {
		msg := <-ts.Message

		// 将msg发送给全部在线的User
		ts.mapLock.Lock()
		for _, cli := range ts.OnlineMap {
			cli.C <- msg
		}
		ts.mapLock.Unlock()
	}
}

// 广播消息的方法
func (ts *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
	ts.Message <- sendMsg
}

func (ts *Server) Handler(conn net.Conn) {
	//...当前链接的业务
	//fmt.Println("链接建立成功")

	user := NewUser(conn)

	//用户上线,将用户加入到 onlineMap 中
	ts.mapLock.Lock()
	ts.OnlineMap[user.Name] = user
	ts.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.BroadCast(user, "已上线")

	//接受客户端发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				ts.BroadCast(user, "下线")
				return
			}

			if err != nil && err != io.EOF {
				fmt.Println("Conn Read err:", err)
			}

			//提取用户的消息(去除 '\n')
			msg := string(buf[:n-1])

			//将得到的消息进行广播
			ts.BroadCast(user, msg)
		}
	}()

	//当前 handle 阻塞
	select {}
}

// 启动服务器的端口
func (ts *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ts.IP, ts.Port))
	if err != nil {
		fmt.Println("net.Listen() err", err)
	}
	// close listen soket
	defer listener.Close()

	//启动监听 Message 的 goroutine
	go ts.ListenMessager()
	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err", err)
			continue
		}

		//do handler
		go ts.Handler(conn)
	}
}

用户业务封装

server.go

package main

import (
	"fmt"
	"io"
	"net"
	"sync"
)

type Server struct {
	IP   string
	Port int

	//在线用户的的列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息广播的
	Message chan string
}

// 创建一个Server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		IP:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}
	return server
}

// 监听 Message 广播消息 channer 的 goroutine, 一旦有消息就发送给全部在线的 User
func (ts *Server) ListenMessager() {
	for {
		msg := <-ts.Message

		// 将msg发送给全部在线的User
		ts.mapLock.Lock()
		for _, cli := range ts.OnlineMap {
			cli.C <- msg
		}
		ts.mapLock.Unlock()
	}
}

// 广播消息的方法
func (ts *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
	ts.Message <- sendMsg
}

func (ts *Server) Handler(conn net.Conn) {
	//...当前链接的业务
	//fmt.Println("链接建立成功")

	user := NewUser(conn, ts)

	//用户上线
	user.Online()

	//接受客户端发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				user.Offline()
				return
			}

			if err != nil && err != io.EOF {
				fmt.Println("Conn Read err:", err)
			}

			//提取用户的消息(去除 '\n')
			msg := string(buf[:n-1])

			//将得到的消息进行广播
			user.DoMessage(msg)
		}
	}()

	//当前 handle 阻塞
	select {}
}

// 启动服务器的端口
func (ts *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ts.IP, ts.Port))
	if err != nil {
		fmt.Println("net.Listen() err", err)
		return
	}
	// close listen soket
	defer listener.Close()

	//启动监听 Message 的 goroutine
	go ts.ListenMessager()

	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err", err)
			continue
		}

		//do handler
		go ts.Handler(conn)
	}
}

user.go

package main

import "net"

type User struct {
	Name string
	Addr string
	C    chan string
	Conn net.Conn

	server *Server
}

// 创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name:   userAddr,
		Addr:   userAddr,
		C:      make(chan string),
		Conn:   conn,
		server: server,
	}

	go user.ListenMessage()

	return user
}

// 用户的上线业务
func (ts *User) Online() {

	//用户上线,将用户加入到 onlineMap 中
	ts.server.mapLock.Lock()
	ts.server.OnlineMap[ts.Name] = ts
	ts.server.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.server.BroadCast(ts, "已上线")
}

// 用户的下线业务
func (ts *User) Offline() {
	//用户上线,将用户从 OnlineMap 中删除
	ts.server.mapLock.Lock()
	delete(ts.server.OnlineMap, ts.Name)
	ts.server.mapLock.Unlock()

	//广播当前用户的下线消息
	ts.server.BroadCast(ts, "下线")
}

// 用户处理消息的业务
func (ts *User) DoMessage(msg string) {
	ts.server.BroadCast(ts, msg)
}

// 监听当前 user channel 的方法,一旦有消息就发送给对端客户端
func (ts *User) ListenMessage() {
	for {
		msg := <-ts.C
		ts.Conn.Write([]byte(msg + "\n"))
	}
}

在线用户查询

格式:user list

user.go

package main

import "net"

type User struct {
	Name string
	Addr string
	C    chan string
	Conn net.Conn

	server *Server
}

// 创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name:   userAddr,
		Addr:   userAddr,
		C:      make(chan string),
		Conn:   conn,
		server: server,
	}

	go user.ListenMessage()

	return user
}

// 用户的上线业务
func (ts *User) Online() {

	//用户上线,将用户加入到 onlineMap 中
	ts.server.mapLock.Lock()
	ts.server.OnlineMap[ts.Name] = ts
	ts.server.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.server.BroadCast(ts, "已上线")
}

// 用户的下线业务
func (ts *User) Offline() {
	//用户上线,将用户从 OnlineMap 中删除
	ts.server.mapLock.Lock()
	delete(ts.server.OnlineMap, ts.Name)
	ts.server.mapLock.Unlock()

	//广播当前用户的下线消息
	ts.server.BroadCast(ts, "下线")
}

// 给当前用户对应的客户端发送信息
func (ts *User) sendMsg(msg string) {
	ts.Conn.Write([]byte(msg))
}

// 用户处理消息的业务
func (ts *User) DoMessage(msg string) {
	if msg == "user list" {
		ts.server.mapLock.Lock()
		for _, user := range ts.server.OnlineMap {
			OnlineMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"
			ts.sendMsg(OnlineMsg)
		}
		ts.server.mapLock.Unlock()
	} else {
		ts.server.BroadCast(ts, msg)
	}
}

// 监听当前 user channel 的方法,一旦有消息就发送给对端客户端
func (ts *User) ListenMessage() {
	for {
		msg := <-ts.C
		ts.Conn.Write([]byte(msg + "\n"))
	}
}

修改用户名

格式:rename|username

user.go

package main

import (
	"net"
	"strings"
)

type User struct {
	Name string
	Addr string
	C    chan string
	Conn net.Conn

	server *Server
}

// 创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name:   userAddr,
		Addr:   userAddr,
		C:      make(chan string),
		Conn:   conn,
		server: server,
	}

	go user.ListenMessage()

	return user
}

// 用户的上线业务
func (ts *User) Online() {

	//用户上线,将用户加入到 onlineMap 中
	ts.server.mapLock.Lock()
	ts.server.OnlineMap[ts.Name] = ts
	ts.server.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.server.BroadCast(ts, "已上线")
}

// 用户的下线业务
func (ts *User) Offline() {
	//用户上线,将用户从 OnlineMap 中删除
	ts.server.mapLock.Lock()
	delete(ts.server.OnlineMap, ts.Name)
	ts.server.mapLock.Unlock()

	//广播当前用户的下线消息
	ts.server.BroadCast(ts, "下线")
}

// 给当前用户对应的客户端发送信息
func (ts *User) SendMsg(msg string) {
	ts.Conn.Write([]byte(msg))
}

// 用户处理消息的业务
func (ts *User) DoMessage(msg string) {
	if msg == "user list" {
		ts.server.mapLock.Lock()
		for _, user := range ts.server.OnlineMap {
			OnlineMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"
			ts.SendMsg(OnlineMsg)
		}
		ts.server.mapLock.Unlock()
	} else if len(msg) > 7 && msg[:7] == "rename|" {
		//rename|username 修改用户名
		newName := strings.Split(msg, "|")[1]
		_, ok := ts.server.OnlineMap[newName]
		if ok {
			ts.SendMsg("当前用户名被使用...\n")
		} else {
			ts.server.mapLock.Lock()
			delete(ts.server.OnlineMap, ts.Name)
			ts.server.OnlineMap[newName] = ts
			ts.server.mapLock.Unlock()
			ts.Name = newName
			ts.SendMsg("您已修改用户名为:" + ts.Name + "\n")
		}

	} else {
		ts.server.BroadCast(ts, msg)
	}
}

// 监听当前 user channel 的方法,一旦有消息就发送给对端客户端
func (ts *User) ListenMessage() {
	for {
		msg := <-ts.C
		ts.Conn.Write([]byte(msg + "\n"))
	}
}

超时强踢功能

server.go

package main

import (
	"fmt"
	"io"
	"net"
	"sync"
	"time"
)

type Server struct {
	IP   string
	Port int

	//在线用户的的列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息广播的
	Message chan string
}

// 创建一个Server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		IP:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}
	return server
}

// 监听 Message 广播消息 channer 的 goroutine, 一旦有消息就发送给全部在线的 User
func (ts *Server) ListenMessager() {
	for {
		msg := <-ts.Message

		// 将msg发送给全部在线的User
		ts.mapLock.Lock()
		for _, cli := range ts.OnlineMap {
			cli.C <- msg
		}
		ts.mapLock.Unlock()
	}
}

// 广播消息的方法
func (ts *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
	ts.Message <- sendMsg
}

func (ts *Server) Handler(conn net.Conn) {
	//...当前链接的业务
	//fmt.Println("链接建立成功")

	user := NewUser(conn, ts)

	//用户上线
	user.Online()

	//监听客户端发送的信息
	isLive := make(chan bool)

	//接受客户端发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				user.Offline()
				return
			}

			if err != nil && err != io.EOF {
				fmt.Println("Conn Read err:", err)
			}

			//提取用户的消息(去除 '\n')
			msg := string(buf[:n-1])

			//将得到的消息进行广播
			isLive <- true
			user.DoMessage(msg)
		}
	}()

	//当前 handle 阻塞
	for {
		select {
		case <-isLive:
			//当前用户是活跃的,应该重置定时器
			//不做任何事情,为了激活 select,更新下面的定时器

		case <-time.After(time.Second * 10):
			//已经超时
			//当当前的 User 强制关闭

			user.SendMsg("你被踢出了聊天室...\n")
			//撤销该 User 的资源
			close(user.C)

			//关闭连接
			conn.Close()

			//退出当前的 Handler
			return
		}
	}

}

// 启动服务器的端口
func (ts *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ts.IP, ts.Port))
	if err != nil {
		fmt.Println("net.Listen() err", err)
		return
	}
	// close listen soket
	defer listener.Close()

	//启动监听 Message 的 goroutine
	go ts.ListenMessager()

	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err", err)
			continue
		}

		//do handler
		go ts.Handler(conn)
	}
}

私聊功能

格式:to|username|message

package main

import (
	"net"
	"strings"
)

type User struct {
	Name string
	Addr string
	C    chan string
	Conn net.Conn

	server *Server
}

// 创建一个用户的API
func NewUser(conn net.Conn, server *Server) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name:   userAddr,
		Addr:   userAddr,
		C:      make(chan string),
		Conn:   conn,
		server: server,
	}

	go user.ListenMessage()

	return user
}

// 用户的上线业务
func (ts *User) Online() {

	//用户上线,将用户加入到 onlineMap 中
	ts.server.mapLock.Lock()
	ts.server.OnlineMap[ts.Name] = ts
	ts.server.mapLock.Unlock()

	//广播当前用户的上线消息
	ts.server.BroadCast(ts, "已上线")
}

// 用户的下线业务
func (ts *User) Offline() {
	//用户上线,将用户从 OnlineMap 中删除
	ts.server.mapLock.Lock()
	delete(ts.server.OnlineMap, ts.Name)
	ts.server.mapLock.Unlock()

	//广播当前用户的下线消息
	ts.server.BroadCast(ts, "下线")
}

// 给当前用户对应的客户端发送信息
func (ts *User) SendMsg(msg string) {
	ts.Conn.Write([]byte(msg))
}

// 用户处理消息的业务
func (ts *User) DoMessage(msg string) {
	if msg == "user list" {
		ts.server.mapLock.Lock()
		for _, user := range ts.server.OnlineMap {
			OnlineMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"
			ts.SendMsg(OnlineMsg)
		}
		ts.server.mapLock.Unlock()
	} else if len(msg) > 7 && msg[:7] == "rename|" {
		//rename|username 修改用户名
		newName := strings.Split(msg, "|")[1]
		_, ok := ts.server.OnlineMap[newName]
		if ok {
			ts.SendMsg("当前用户名被使用...\n")
		} else {
			ts.server.mapLock.Lock()
			delete(ts.server.OnlineMap, ts.Name)
			ts.server.OnlineMap[newName] = ts
			ts.server.mapLock.Unlock()
			ts.Name = newName
			ts.SendMsg("您已修改用户名为:" + ts.Name + "\n")
		}

	} else if len(msg) > 4 && msg[:3] == "to|" {
		//消息格式:to|username|message

		//1.获取对方的用户名
		remoteName := strings.Split(msg, "|")[1]
		if remoteName == "" {
			ts.SendMsg("消息格式不正确,请使用 to|username|message 的格式\n")
			return
		}

		//2.根据用户名得到对方 User 对象
		remoteUser, ok := ts.server.OnlineMap[remoteName]
		if !ok {
			ts.SendMsg("该用户名不存在\n")
			return
		}

		//3.获取消息,通过对方 User 发送出去
		content := strings.Split(msg, "|")[2]
		if content == "" {
			ts.SendMsg("无消息内容,请重发\n")
			return
		}
		remoteUser.SendMsg(ts.Name + "对您说:" + content + "\n")
		ts.SendMsg("您对" + remoteName + "说:" + content + "\n")

	} else {
		ts.server.BroadCast(ts, msg)
	}
}

// 监听当前 user channel 的方法,一旦有消息就发送给对端客户端
func (ts *User) ListenMessage() {
	for {
		msg := <-ts.C
		ts.Conn.Write([]byte(msg + "\n"))
	}
}

客户端实现

建立链接

client.go

package main

import (
	"fmt"
	"net"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
}

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
	}

	//链接server
	conn, err := net.Dial("tcp", net.JoinHostPort(serverIp, fmt.Sprintf("%d", serverPort)))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回对象
	return client
}

func main() {
	client := NewClient("127.0.0.1", 8888)
	if client == nil {
		fmt.Println(">>>>> 链接服务器失败...")
		return
	}

	fmt.Println(">>>>> 链接服务器成功...")

	//启动客户端的业务
	select {}
}

命令行解析

package main

import (
	"flag"
	"fmt"
	"net"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
}

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
	}

	//链接server
	conn, err := net.Dial("tcp", net.JoinHostPort(serverIp, fmt.Sprintf("%d", serverPort)))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回对象
	return client
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是:127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是:8888)")
}

func main() {
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 链接服务器失败...")
		return
	}

	fmt.Println(">>>>> 链接服务器成功...")

	//启动客户端的业务
	select {}
}

菜单显示

client.go

package main

import (
	"flag"
	"fmt"
	"net"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int
}

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
		flag:       999,
	}

	//链接server
	conn, err := net.Dial("tcp", net.JoinHostPort(serverIp, fmt.Sprintf("%d", serverPort)))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回对象
	return client
}

func (ts *Client) menu() bool {
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更新用户名")
	fmt.Println("0.退出")

	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3 {
		ts.flag = flag
		return true
	} else {
		fmt.Println(">>>>请输入合法范围内的数字<<<<")
		return false
	}
}

func (ts *Client) Run() {
	for ts.flag != 0 {
		for !ts.menu() {
		}

		//更具不同的模式处理不同的业务
		switch ts.flag {
		case 1:
			//公聊模式
			fmt.Println("公聊模式选择...")
		case 2:
			//私聊模式
			fmt.Println("私聊模式选择...")
		case 3:
			//更改用户名
			fmt.Println("更新用户名选择...")
		}
	}
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是:127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是:8888)")
}

func main() {
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 链接服务器失败...")
		return
	}

	fmt.Println(">>>>> 链接服务器成功...")

	//启动客户端的业务
	client.Run()
}

更新用户名

client.go

package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"os"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int
}

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
		flag:       999,
	}

	//链接server
	conn, err := net.Dial("tcp", net.JoinHostPort(serverIp, fmt.Sprintf("%d", serverPort)))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回对象
	return client
}

// 处理 server 回应的消息,直接显示到标准输出即可
func (ts *Client) DealResponse() {
	//一旦client.conn有数据,就直接copy到stdout标准输出上,永久阻塞监听
	io.Copy(os.Stdout, ts.conn)

}

func (ts *Client) menu() bool {
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更新用户名")
	fmt.Println("0.退出")

	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3 {
		ts.flag = flag
		return true
	} else {
		fmt.Println(">>>>请输入合法范围内的数字<<<<")
		return false
	}
}

// 更新用户名
func (ts *Client) UpdateName() bool {
	fmt.Println(">>>请输入用户名:")
	fmt.Scanln(&ts.Name)

	sendMsg := "rename|" + ts.Name + "\n"
	_, err := ts.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return false
	}
	return true
}

func (ts *Client) Run() {
	for ts.flag != 0 {
		for !ts.menu() {
		}

		//更具不同的模式处理不同的业务
		switch ts.flag {
		case 1:
			//公聊模式
			fmt.Println("公聊模式选择...")
		case 2:
			//私聊模式
			fmt.Println("私聊模式选择...")
		case 3:
			//更改用户名
			ts.UpdateName()

		}
	}
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是:127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是:8888)")
}

func main() {
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 链接服务器失败...")
		return
	}

	//单独开启一个 goroutine 去处理 server 回应的消息
	go client.DealResponse()

	fmt.Println(">>>>> 链接服务器成功...")

	//启动客户端的业务
	client.Run()
}

公聊与私聊

client.go

package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"os"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int
}

func NewClient(serverIp string, serverPort int) *Client {
	//创建客户端对象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
		flag:       999,
	}

	//链接server
	conn, err := net.Dial("tcp", net.JoinHostPort(serverIp, fmt.Sprintf("%d", serverPort)))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回对象
	return client
}

// 处理 server 回应的消息,直接显示到标准输出即可
func (ts *Client) DealResponse() {
	//一旦client.conn有数据,就直接copy到stdout标准输出上,永久阻塞监听
	io.Copy(os.Stdout, ts.conn)

}

func (ts *Client) menu() bool {
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更新用户名")
	fmt.Println("0.退出")

	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3 {
		ts.flag = flag
		return true
	} else {
		fmt.Println(">>>>请输入合法范围内的数字<<<<")
		return false
	}
}

// 公聊模式
func (ts *Client) PublicChat() {
	//提示用户输入消息
	var chatMsg string

	fmt.Println(">>>>请输入聊天内容, exit退出.")
	fmt.Scanln(&chatMsg)

	for chatMsg != "exit" {
		//发送给服务器

		//消息不为空则发送
		if chatMsg != "" {
			sendMsg := chatMsg + "\n"
			_, err := ts.conn.Write([]byte(sendMsg))
			if err != nil {
				fmt.Println("conn Write err:", err)
				break
			}

			chatMsg = ""
			fmt.Println(">>>>请输入聊天内容, exit退出.")
			fmt.Scanln(&chatMsg)
		}
	}
}

// 更新用户名
func (ts *Client) UpdateName() bool {
	fmt.Println(">>>请输入用户名:")
	fmt.Scanln(&ts.Name)

	sendMsg := "rename|" + ts.Name + "\n"
	_, err := ts.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return false
	}
	return true
}

// 查询在线用户
func (client *Client) SelectUsers() {
	sendMsg := "user list\n"
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn Write err:", err)
		return
	}
}

// 私聊模式
func (ts *Client) PrivateChat() {

	var remoteName string
	var chatMsg string

	ts.SelectUsers()
	fmt.Println(">>>>请输入聊天对象[用户名], exit退出.")
	fmt.Scanln(&remoteName)

	for remoteName != "exit" {

		fmt.Println(">>>>请输入聊天内容, exit退出.")
		fmt.Scanln(&chatMsg)

		for chatMsg != "exit" {
			//发送给服务器

			//消息不为空则发送
			if chatMsg != "" {
				sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"
				_, err := ts.conn.Write([]byte(sendMsg))
				if err != nil {
					fmt.Println("conn Write err:", err)
					break
				}

				chatMsg = ""
				fmt.Println(">>>>请输入聊天内容, exit退出.")
				fmt.Scanln(&chatMsg)
			}
		}

		ts.SelectUsers()
		fmt.Println(">>>>请输入聊天对象[用户名], exit退出.")
		fmt.Scanln(&remoteName)
	}
}

func (ts *Client) Run() {
	for ts.flag != 0 {
		for !ts.menu() {
		}

		//更具不同的模式处理不同的业务
		switch ts.flag {
		case 1:
			//公聊模式
			ts.PublicChat()
		case 2:
			//私聊模式
			ts.PrivateChat()
		case 3:
			//更改用户名
			ts.UpdateName()

		}
	}
}

var serverIp string
var serverPort int

// ./client -ip 127.0.0.1 -port 8888
func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是:127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是:8888)")
}

func main() {
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 链接服务器失败...")
		return
	}

	//单独开启一个 goroutine 去处理 server 回应的消息
	go client.DealResponse()

	fmt.Println(">>>>> 链接服务器成功...")

	//启动客户端的业务
	client.Run()
}
posted @ 2025-03-19 16:12  XiaoMo247  阅读(27)  评论(0)    收藏  举报