1、Go语言概述
- Google开源
- 编译型
- 21世纪的C语言
Go和C的比较:https://hyperpolyglot.org/c
特性:
- 语法简介
- 开发效率高 - 自带垃圾回收
- 执行性能好
2、Go语言开发环境搭建
2.1 下载
Go官方下载地址:https://golang.org/dl/
2.2 项目结构
Go语言的项目,需要特定的目录结构进行管理,不能随便写,一个标准的go工程需要三个目录:
- src:存放我们自己的源代码
- bin:编译之后的程序之后,使用标准命令
go install之后存放的位置 - pkg:缓存包
GOPATH:一般是我们的项目位置
GOROOT:类似JAVA_HOME
个人开发

企业开发

3.3 Go开发编辑器
VS Code下载地址:https://code.visualstudio.com/Download
3.4 编译 - go build
- 在项目目录下执行
go build - 其他路径下执行
go build,需要在后面加上项目路径(src目录只有的路径开始写),将保存到当前目录下 go build -o hello.exe,指定名称
3.5 go run
- 像执行脚本一样执行go代码
3.6 go install
go install分为两步:
go build- 将编译后的文件移动到
GOPATH/bin目录中
3.7 交叉编译
Go支持跨平台编译
例如:在windows编译一个能在Linux下运行的文件


3、Go语言的编码基本结构
package mainimport "fmt"//函数外只能放置标识符(变量/常量/函数/类型)的申明//fmt.Println("hell") //非法//程序入口func main() {fmt.Println("hello world")}
3.1 Go语言的25个关键字
基础数据类型:
int int8 int16 int32 int64uint uint8 ... uint64float32 float64
3.2 变量和常量
- 先申明再使用
- Go语言中非全局变量申明了必须使用(不使用无法编译)
Go语言的变量申明格式:
<font style="background-color:#FADB14;">var 变量名 变量类型</font>
批量申明:
var (name stringage intisOk bool)
申明变量同时赋值:
var name string = "zhangsan" 不推荐
类型推导:
var age = 21
间短变量声明:(只能在函数中使用)
s3 := "李四"
匿名变量:_表示
常量:const
iota:类似枚举
- 在const关键字出现时将会被置为0,const常量中每新增一行,计数加1
const (a1 = iota //a1=0a2 //a2=1_a3 //a3=3)
插队
const (a1 = iota //0a2 = 100 //100a3 = iota //2a4 //3)
多个常量申明在一行
const (d1, d2 = iota + 1, iota + 2 //d1=1,d2=2d3, d4 = iota + 1, iota + 2 //d3=2,d4=3)
定义数量级
const (_ = iotaKB = 1 << (10 * iota) //1左移10位 10000000000(二进制) --> 1024(十进制)MB = 1 << (10 * iota) //2左移10位GB = 1 << (10 * iota) //3左移10位TB = 1 << (10 * iota) //4左移10位PB = 1 << (10 * iota) //5左移10位)
4、基本数据类型
4.1 整型
func main() {// 十进制var a int = 10fmt.Printf("%d \n", a) //10fmt.Printf("%d \n", a) //1010,%d表示二进制,转换成了二进制fmt.Printf("%o \n", a) //转换成八进制// 八进制,以0x开头i := 077fmt.Printf("%d \n", i)//十六进制i1 := 0x1234567fmt.Printf("%d \n", i1)//查看变量的类型fmt.Printf("%T \n", i1)//申明int8类型的变量i3 := int8(9)fmt.Printf("%T \n", i3)}
4.2 浮点数
- Go语言中默认的浮点数是float64类型
4.3 复数
- complex64和complex128
- 复数有实部和虚部,complex64实部和虚部是32,complex128实部和虚部是64
4.4 布尔值
- true和false(默认值是false)
- Go语言中不允许将整形和强制转换为布尔型
- 无法参数运算,无法进行数据类型装换
4.5 占位符
//占位符func main() {var n = 100//查看类型fmt.Printf("%T /n", n)fmt.Printf("%v /n", n)fmt.Printf("%b /n", n)fmt.Printf("%d /n", n)fmt.Printf("%o /n", n)fmt.Printf("%x /n", n)var s = "你好"fmt.Printf("字符串:%s /n", s)fmt.Printf("字符串:%v /n", s)fmt.Printf("字符串:%#v /n", s)}
4.6 字符串

4.7 rune
func main() {s := "白萝卜"s1 := []rune(s) //转换s1[0] = '红'fmt.Println(string(s1))}
4.8 类型转换
4.9 定长数组 - 切片
4.10 不定长数组
4.11 字典 - map
4.12 枚举 - iota
- Go语言中没有枚举类型,但是我们可以使用const(常量关键字)+iota(常量累加器)来进行模拟
//模拟一个一周的枚举const (MONDAY = iota //0TUESDAY //1WEDNEDAY //2THURSDAY //...FRIDAYSATUDAYSUNDAYM, N = iota, iota // 7,7X, Y = iota + 1, iota // 9,8)/*1. iota是常量组计数器2. iota从0开始,没换行递增13. 常量组不赋值,默认与上一行表达式相同4. 如果同一行出现两个iota,那么两个iota的值相同5. 每个常量组的iota是独立的,遇到const会清0*/
4.13 结构体 - type + struct
- 概念类似java中的对象
type 结构体名称 struct{}
4.14 init()函数
- init()函数没有参数没有返回值
- 一个包中包含多个init时,调用顺序不确定
- init()函数不允许被用户显示调用
- 有时候引用一个包,可能只想使用这个包中的inti函数(mysql的init对驱动进行初始化)
- 可以使用_在import里处理
4.15 defer关键字 - 延迟
延迟、关键字,可以用于修饰语句,函数,确保这条语句可以在当前栈退出时执行
4.16 结构体(类)- 方法绑定 —> 封装
//Go语言中的结构体就好比java/C中的类//结构体的定义为:type 结构体名 struct{}//Go语言中的方法不是定义在类里面//Go语言是面向接口的不是面向类的//Go语言的方法通过绑定类实现的(定义在类的外面)//定义结构体(类)type Person struct {name stringage intgender stringscore float64}//定义方法绑定person类func (p *Person) Eat() {//尽量使用指针传递fmt.Println(p.name + "吃的真香!")}
4.17 继承
package mainimport "fmt"// 继承//定义一个Human(人)类type Human struct {name stringage intgender stringscode float64}//定义Human的方法func (h *Human) Eat() {fmt.Println(h.name + "吃的吧唧香....")}//定义一个学生类 -- 类的嵌套type Student struct {//包含Human类型的变量(类的嵌套)hum Humanschool string}//定义一个老师类 -- 继承Humantype Teacher struct {//继承的时候虽然没有定义字段名字,但是会自动创建一个默认的同名字段//这是为了在子类中依然可以操作父类,因为子类父类中可能存在同名的字段Human //直接写父类名称,没有字段名字subject string}func main() {stu1 := Student{hum: Human{"狗娃",19,"男",89.0,},school: "人民一中",}fmt.Println(stu1)//类的继承t1 := Teacher{}t1.name = "强国"t1.age = 19t1.subject = "语文"t1.Eat()fmt.Println(t1)}
4.18 Go中的权限
- Go语言中权限都是通过首字母大小写控制
- import —> 如果包名不同,那么只有大写字母开头的才是public的
- 类成员、方法 —> 只有首写字母大写才能在其他包中使用
4.19 接口
- Go语言中使用interface关键字创建接口
- interface 不仅仅是用来处理多态的,他可以接收任何类型的数据类型,有点类似于void
func main() {//定义三个接口类型var i, j, k interface{} //空接口names := []string{"duke", "lily"}i = namesfmt.Println("i代表切片数组:", i)age := 20j = agefmt.Println("j代表数字:", j)s := "hello"k = sfmt.Println("k代表字符串:", k)//现在我们只知道k是interface类型,但是我们不知道具体代表什么类型?可以进行判断kvalue, ok := k.(int)if !ok {fmt.Println("k不是int")fmt.Printf("k的类型是:%T\n", k) //k的类型是:stringfmt.Printf("i的类型是:%T\n", i) //i的类型是:[]stringfmt.Printf("j的类型是:%T\n", j) //j的类型是:int} else {fmt.Println("k是int,k的值:", kvalue)}}
- 常用的场景:把interface当做一个函数的参数,(类似于print),使用switch来判断用户输入的不同类型,在根据不同的类型,做相应的逻辑处理
//创建具有三个数据类型的切片array := make([]interface{}, 3)array[0] = 1array[1] = "hello"array[2] = truefor _, value := range array {switch v := value.(type) {case int:fmt.Printf("当前类型为%T,内容为:%d\n", v, v)case string:fmt.Printf("当前类型为%T,内容为:%s\n", v, value)case bool:fmt.Printf("当前类型为%T,内容为:%v\n", v, value)default:fmt.Printf("其他数据类型,当前类型为%T,内容为:%v\n", v, value)}}
4.20 多态
- Go语言的多态不需要继承,只需要实现相同的接口即可
//Go语言中实现多态,需要实现定义的接口//人类发起攻击,不同等级子弹效果不同//创建接口 , 注意类型是interfacetype IAttack interface {//接口的函数可以有多个,但是只能有函数原型,不可以实现Attack()}//玩家type Human struct {name stringlevel inthurt float64}//定义玩家方法绑定到HumanLowLevel类func (hum *Human) Attack() {fmt.Println("我是:", hum.name, ",我的等级是:", hum.level, ",我造成了", hum.hurt, "点伤害...")}//定义一个多态的通用接口,传入不同的对象,调用同样的方法,实现不同效果 ---> 多态func doIAttack(i IAttack) {i.Attack()}func main() {//定义一个包含IAttack接口变量var player IAttack//低等级玩家lowLevel := Human{name: "David",level: 1,hurt: 100,}// lowLevel.Attack()//给player赋值为lowLevel,接口一定要使用指针来赋值player = &lowLevelplayer.Attack()//高等级玩家highLevl := Human{name: "狗蛋",level: 6,hurt: 10000,}player = &highLevlplayer.Attack()fmt.Println("-----多态--------")doIAttack(&lowLevel)doIAttack(&highLevl)}
5、Go语言基础之流程控制
5.1 if else
age := 21if age > 35 {fmt.Println("人到中年")} else if age > 18 {fmt.Println("好青年")} else {fmt.Println("小盆友")}# 特殊写法减少程序内存的占用if age := 19; age > 35 { //作用域,age的作用范围仅限于if条件判断语句中fmt.Println("人到中年")} else if age > 18 {fmt.Println("好青年")} else {fmt.Println("小盆友")}
5.2 for
- Go语言中只有一种循环(for循环)
格式:
for 初始语句;条件表达式;结束语句{循环体语句}// 变种1i:= 5for ;i<10;i++{fmt.Println(i)}// 变种2j:=5for j<10{fmt.Println(j)j++}//无限循环for {fmt.Println("11")}//for range循环s := "hello沙河"for i, v := range s {fmt.Printf("%d %c \n", i, v)}
6、数据类型
6.1 切片
- 切片指向了一个底层的数组(切片不存值)
- 切片的长度是它的元素个数
- 切片的容量是底层数组从切片的第一个元素到数组的最后一个元素
- 切片是引用类型,都指向了一个底层数组(修改底层数组的值,切片也将被修改)
6.2 make()函数
make([]T, len, cap)
- 用来创建切片
- 函数的指针返回存在内存逃逸
6.3 append()
- 为切片追加元素
- 必须用原来的切片变量接收返回值
- 追加时容量不足,将底层数组换一个比原来数组大(策略:根据类型策略不同)的新数组
6.4 copy()
- 复制切片
6.5 sort
- 对切片进行排序
6.6 指针
- Go语言中不存在指针操作,只需记住两个符号
- &:取地址
- *:根据地址取值
6.7 make和new
- 都是用来申请内存的
- new很少用,一般用来给基本数据类型申请内存,string/int…,返回的是对应类型的指针(string、int)
- make用来给slice、map、chan申请内存,make函数返回的对应的这三个类型本身
6.8 map
map[keyType]valueType
7、结构体(类似java的类)
type 类型名 结构体类型{字段 数据类型字段 数据类型}
例如:
package mainimport "fmt"//结构体type person struct {name stringage intgender stringhobby []string}func main() {//申明person类型变量var p person//赋值p.name = "zhoulin"p.age = 21p.gender = "女"p.hobby = []string{"篮球", "足球", "乒乓球"}//访问变量fmt.Println(p) //{zhoulin 21 女 [篮球 足球 乒乓球]}fmt.Println(p.name) //zhoulin}
7.1 匿名结构体
- 多用域临时场景
var 变量名 struct{变量 数据类型...}
7.2 结构体指针和结构体初始化
- 结构体是值类型
- Go语言中函数传参永远是拷贝
- 结构体占用一块连续的内存空间
type person struct {name stringage intgender string}func f(x person) {x.gender = "男" //修改的是副本的gender}func f2(x *person) {// (*x).gender = "男" //根据内存地址找到原来的变量,修改的是原来的变量的值x.gender = "男" //指针的语法糖,与上面相同找到原来的变量修改}func main() {var p personp.name = "zhoulin"p.age = 17p.gender = "女"f(p)fmt.Println(p) //{zhoulin 17 女}f2(&p) //传递的是p的内存地址fmt.Println(p) //{zhoulin 17 男}}

7.3 构造函数
- 返回一个结构体变量的函数
//构造函数type person struct {name stringage int}//构造函数返回的是结构体还是结构体指针?//当结构体比较大的时候尽量使用结构体指针,减少程序的内存开销func newPerson(name string, age int) *person {return &person{name: name,age: age,}}func main() {p1 := newPerson("元帅", 18)p2 := newPerson("周林", 21)fmt.Println(p1)fmt.Println(p2)}
8、结构体
//方法type dog struct {name string}//构造函数func newDog(name string) dog {return dog{name: name,}}//方法是作用与特定类型的函数//接收者表示的是调用方法的具体变量,多用类型名首字母小写表示func (d dog) wang() {fmt.Printf("%s: 汪汪汪~", d.name)}func main() {d1 := newDog("大黄")d1.wang()}
- Go语言中如果标识符的首字母大写,表示对外部包可见(暴露的,共有的)
何时使用指针接收者:
- 值接收者,需要修改接收者中的值时需要指针接收者
- 尽量使用指针接收者
8、基础语法
8.1 switch
8.2 标签
8.3 枚举const-iota
8.4 结构体
8.5 init函数
8.6 defer(延迟)
9、类的相关操作
9.1 封装 — 方法绑定
9.2 类继承
9.3 interface(接口)
9.4 多态
10、并发相关
10.1 基础
- Go语言中不是线程,而是go程 ==> goroutine,go程是go语言原生支持的
- 每一个go程占用系统资源远远小于线程,一个go程大约需要4~5k的内存资源
- 一个程序可以启动大量的go程:
- 线程 ==> 几十个
- go程可以启动成百上千个,===>实现高并发,性能非常好
- 只需要在函数前面加上go关键字即可
//用于子go程使用func display() {count := 1for {fmt.Println("这是子go程:", count)count++// time.Sleep(500) //默认为微秒time.Sleep(1 * time.Second)}}func main() {//启动子go程go display()//主go程count := 1for {fmt.Println("============>这是主go程:", count)count++time.Sleep(1 * time.Second)}}
启动多个子go程,竞争资源
//用于子go程使用func display(num int) {count := 1for {fmt.Println("这是子go程:", num, "当前执行的次数:", count)count++// time.Sleep(500) //默认为微秒time.Sleep(1 * time.Second)}}func main() {//启动子go程for i := 1; i <= 3; i++ {go display(i)}//主go程count := 1for {fmt.Println("============>这是主go程:", count)count++time.Sleep(1 * time.Second)}}
10.2 提前退出go程序
/*GOEXIT:提前退出当前go程序return:返回当前函数exit:退出整个程序(进程)*/func main() {//匿名函数创建go程go func() {func() {fmt.Println("这是子go程内部的函数...")// return //返回当前函数// os.Exit(1) //退出程序runtime.Goexit() //退出当前go程}()fmt.Println("子go程结束!")}()fmt.Println("这是主go程!")time.Sleep(3 * time.Second)fmt.Println("OVER!!")}
10.3 无缓冲管道channel
func main() {/*当涉及多个go程时,c语言使用互斥量,上锁来保证资源同步,避免资源竞争Go语言也支持这种方式,但是Go语言更好的解决方案是使用管道(通道:channel)使用管道不需要我们去加锁例如:读写数据的同步*///创建装数字的管道,创建管道一定要用make,同map一样,否则是nil//不指定长度,此时是无缓存的管道// numChan := make(chan int) //五缓冲numChan := make(chan int, 10) //有缓冲//子进程读数据go func() {for i := 0; i < 50; i++ {//numChan写入数据向datadata := <-numChanfmt.Println("子进程1,读取数据:", data)}}()//子进程写入数据到管道numChan中go func() {for i := 0; i < 20; i++ {numChan <- ifmt.Println("子进程2写书数据:", i)}}()//主进程写数据for i := 20; i < 50; i++ {//i写入数据到管道numChannumChan <- ifmt.Println("主进程写入数据:", i)}}
10.4 有缓冲管道
死锁
func main() {//1.当缓冲写满后,写阻塞,等到读取后,再恢复写入//2.当缓冲区空时,读阻塞,直到有数据写入//3.如果管道没有使用make分配空间,那么管道默认是nil的,读写都会阻塞// numChan := make(chan int, 10)var names chan string //默认是nil的//读数据go func() {fmt.Println("数据是:", names)}()//写数据names <- "hello"}
初始化空间
func main() {//1.当缓冲写满后,写阻塞,等到读取后,再恢复写入//2.当缓冲区空时,读阻塞,直到有数据写入//3.如果管道没有使用make分配空间,那么管道默认是nil的,读写都会阻塞//4.对于管道,读写次数必须对等//不对等:一、主程序读的数大于写的,程序奔溃死锁;二、子程序读大于写,不会奔溃,内存泄露// numChan := make(chan int, 10)// var names chan string //默认是nil的// names = make(chan string, 10) //分配空间//合并上面names := make(chan string, 10)//读数据go func() {fmt.Println("数据是:", <-names)}()//写数据names <- "hello" //由于这里是names是nil,写操作会阻塞在这里time.Sleep(1 * time.Second)}
解决读写不对等 - for-range
- close()
func main() {numChan2 := make(chan int, 10)//写go func() {for i := 0; i < 50; i++ {numChan2 <- ifmt.Println("写入数据:", i)}close(numChan2)}()//读,遍历管道时,只返回一个参数//问题:for-range不知道管道是否已经写完了,所以会一直等待(奔溃)--->写时,显示关闭管道close//在写入端,将管道关闭,for-range遍历时关闭管道,会退出for v := range numChan2 {fmt.Println("读数据:", v)}fmt.Println("over!")}
10.5 for range遍历
func main() {numChan2 := make(chan int, 10)//写go func() {for i := 0; i < 50; i++ {numChan2 <- ifmt.Println("写入数据:", i)}close(numChan2)}()//读,遍历管道时,只返回一个参数//问题:for-range不知道管道是否已经写完了,所以会一直等待(奔溃)--->写时,显示关闭管道close//在写入端,将管道关闭,for-range遍历时关闭管道,会退出for v := range numChan2 {fmt.Println("读数据:", v)}fmt.Println("over!")}
10.6 管道总结
- 当管道写满了,写阻塞
- 当缓冲区读完了,读阻塞
- 如果管道没有使用make分配空间,管道默认是nil
- 从nil的管道读取数据、写入数据、都会阻塞(不会奔溃)
- 从一个已经close的管道读数据时,会反馈零值(不会奔溃)
- 向一个已经close的管道写数据是,会奔溃
- 关闭一个已经关闭的管道,程序会奔溃
- 关闭管道的动作一定要在写管道的一端,不应该放在读端,否则写端继续写,会奔溃
- 读写次数一定要对等,否则:
- 在多个go程中:资源泄露
- 在主go程中,程序奔溃(deadlock)
10.7 多go程序读写
略(上面已经实现)
10.8 判断管道是否已关闭
- 需要知道一个管道的状态,如果已经close了,读不怕(返回0),如果写(奔溃)
判断和map中是否有值很类似
- map:v,ok := m1[0]
- chan:v,ok := <- numchan
func main() {numChan := make(chan int, 10)//写go func() {for i := 0; i < 10; i++ {numChan <- ifmt.Println("写数据:", i)}close(numChan)}()//读// for v := range numChan {// fmt.Println("读数据:", v)// }for {v, ok := <-numChan //ok-idom判断模式if !ok {fmt.Println("管道已经关闭了,准备退出...")break}fmt.Println(v)}}
10.9 单向通道
- numChan := make(chan int,10) ==>双向通道,即可写,也可读
- 单向通道:为了明确语义,一般用于函数参数
- 单向读通道:var numChanReadOnly <- chan int
- 单向写通道:var numChanWriteOnly chan <- int
// consumer:消费者 --->只读func consumer(in <-chan int) {for v := range in {// in <- v //读通道不允许写操作fmt.Println("消费者消费:", v)}}// producer:生产者 --->只写func producer(out chan<- int) {for i := 0; i < 10; i++ {out <- ifmt.Println("生产者生产:", i)}}func main() {//单向读通道// var numChanReadOnly <-chan int//单向写通道// var numChanWriteOnly chan<- int//生产者消费者模型//C语言:数组+锁 thread1:写,thread2:读//Go语言:goroutine + channel//1.在主函数中创建一个双向channel:numChannumChan := make(chan int, 5)//2.将numChan传递给生产者,生产go producer(numChan) // 双向通道可以赋值给同类型的单向通道//3.将numChan传递给消费者,消费go consumer(numChan)time.Sleep(2 * time.Second)fmt.Println("OVER!")}
10.10 select
当程序总有多个channel协同工作时,ch1,chan2,在某时刻,ch1和chan2触发了,程序要做响应的处理
使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)
select语法和Switch case很像,但是所有的分支条件都必须是通道io
package mainimport ("fmt""time")// 当程序总有多个channel协同工作时,ch1,chan2,在某时刻,ch1和chan2触发了,程序要做响应的处理// 使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)// select语法和Switch case很像,但是所有的分支条件都必须是通道iofunc main() {// var chan1, chan2 chan intchan1 := make(chan int)chan2 := make(chan int)//启动1个go程,负责监听两个chango func() {for {fmt.Println("监听中...")select {case data1 := <-chan1:fmt.Println("从chan1读取成功,data1:", data1)case data2 := <-chan2:fmt.Println("从chan2读取成功,data2:", data2)default:fmt.Println("....")time.Sleep(1 * time.Second)}}}()//启动go1,写chan1go func() {for i := 0; i < 10; i++ {chan1 <- i// fmt.Println("写入chan1:", i)time.Sleep(1 * time.Second / 2)}}()//启动go2,写chan2go func() {for i := 0; i < 10; i++ {chan2 <- i// fmt.Println("写入chan1:", i)time.Sleep(1 * time.Second)}}()for {time.Sleep(3 * time.Second)fmt.Println("OVER!")}}
11、网络
1. 分层

| OIS七层 | TCP/IP四层 | 对应的网络协议 |
|---|---|---|
2. socket

Server Demo
- 只能接收一个连接,发送一个数据
package mainimport ("fmt""net""strings")func Server() {//1.创建监听ip := "127.0.0.1"port := 8848address := fmt.Sprintf("%s:%d", ip, port)// net.Listen("tcp", ":8848") // 简写,冒号前面默认是iplistenner, err := net.Listen("tcp", address)if err != nil {fmt.Println("net.Listen err:", err)return}//2.建立连接//err不会重新创建,而是复用conn, err := listenner.Accept()if err != nil {fmt.Println("listenner.Accept err:", err)return}fmt.Println("连接建立成功!")//3.创建一个容器,用于接收读取到的资源buf := make([]byte, 1024)//cnt真正读取client发来的数据长度cnt, err := conn.Read(buf)if err != nil {fmt.Println("conn.Read err:", err)return}fmt.Println("Client ===> Server,长度:", cnt, ",数据:", string(buf[:cnt]))//4.将数据转成大写:"hello" ==> "HELLO"//服务器对客户端进行响应upperData := strings.ToUpper(string(buf))cnt, err = conn.Write([]byte(upperData))if err != nil {fmt.Println("conn.Write err:", err)return}fmt.Println("Client <=== Server,长度:", cnt, ",数据:", upperData)// 5.关闭连接conn.Close()}func main() {Server()}
Client Demo
package mainimport ("fmt""net")func Client() {//1. 建立连接conn, err := net.Dial("tcp", ":8848")if err != nil {fmt.Println("net.Dial err:", err)return}fmt.Println("client与server连接建立成功!")//2. 写数据 --> ServersendData := []byte("helloworld")cnt, err := conn.Write(sendData)if err != nil {fmt.Println("Client ===> Server cnt:", cnt, ",data:", string(sendData))}//3. 接收Server返回数据buf := make([]byte, 1024)cnt, err = conn.Read(buf[:cnt])if err != nil {fmt.Println("conn.Read err:", err)}fmt.Println("Client <=== Server cnt:", cnt, ",data:", buf[:cnt])// 4. 关闭连接conn.Close()}func main() {Client()}
多连接
client
package mainimport ("fmt""net""time")func Client() {//1. 建立连接conn, err := net.Dial("tcp", ":8848")if err != nil {fmt.Println("net.Dial err:", err)return}fmt.Println("client与server连接建立成功!")for {//2. 写数据 --> ServersendData := []byte("helloworld")cnt, err := conn.Write(sendData)if err != nil {fmt.Println("Client ===> Server cnt:", cnt, ",data:", string(sendData))}//3. 接收Server返回数据buf := make([]byte, 1024)cnt, err = conn.Read(buf[:cnt])if err != nil {fmt.Println("conn.Read err:", err)}fmt.Println("Client <=== Server cnt:", cnt, ",data:", buf[:cnt])time.Sleep(1 * time.Second)}// 4. 关闭连接// _ = conn.Close()}func main() {Client()}
server
package mainimport ("fmt""net""strings")//处理具体业务的逻辑,需要将conn传递进来,每一个新连接,conn是不同的func handleFunc(conn net.Conn) {//3.创建一个容器,用于接收读取到的资源buf := make([]byte, 1024)for {fmt.Println("准备读取数据....")//cnt真正读取client发来的数据长度cnt, err := conn.Read(buf)if err != nil {fmt.Println("conn.Read err:", err)return}fmt.Println("Client ===> Server,长度:", cnt, ",数据:", string(buf[:cnt]))//4.将数据转成大写:"hello" ==> "HELLO"//服务器对客户端进行响应upperData := strings.ToUpper(string(buf))cnt, err = conn.Write([]byte(upperData))if err != nil {fmt.Println("conn.Write err:", err)return}fmt.Println("Client <=== Server,长度:", cnt, ",数据:", upperData)}// 5.关闭连接// _ = conn.Close()}func Server() {//1.创建监听ip := "127.0.0.1"port := 8848address := fmt.Sprintf("%s:%d", ip, port)// net.Listen("tcp", ":8848") // 简写,冒号前面默认是iplistenner, err := net.Listen("tcp", address)if err != nil {fmt.Println("net.Listen err:", err)return}//需求:server可以接收多个连接,每个连接可以处理多个请求//解:主go程负责监听,子go程负责处理//主for {fmt.Println("监听中...")//2.建立连接//err不会重新创建,而是复用conn, err := listenner.Accept()if err != nil {fmt.Println("listenner.Accept err:", err)return}fmt.Println("连接建立成功!")go handleFunc(conn)}}func main() {Server()}
3. http
编写web的语言:
- java
- php ==> 现在用go重写
- python ,豆瓣
- go ===> beego,gin主流的web框架
https协议:浏览器发送的就是http请求
- http是应用层的协议,底层还是依赖传输层:tcp(短连接),网络层(ip)
- 无状态的,每次请求都是独立的,下次请求需要重新建立连接
- https:
- http是标准协议,明文传输,不安全
- https不是标准协议,https:http + ssl(非对称加密,数字证书)
- 现在所有网站都会尽量要求使用https开发:安全
