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 main
import "fmt"
//函数外只能放置标识符(变量/常量/函数/类型)的申明
//fmt.Println("hell") //非法
//程序入口
func main() {
fmt.Println("hello world")
}
3.1 Go语言的25个关键字
基础数据类型:
int int8 int16 int32 int64
uint uint8 ... uint64
float32 float64
3.2 变量和常量
- 先申明再使用
- Go语言中非全局变量申明了必须使用(不使用无法编译)
Go语言的变量申明格式:
<font style="background-color:#FADB14;">var 变量名 变量类型</font>
批量申明:
var (
name string
age int
isOk bool
)
申明变量同时赋值:
var name string = "zhangsan"
不推荐
类型推导:
var age = 21
间短变量声明:(只能在函数中使用)
s3 := "李四"
匿名变量:_表示
常量:const
iota:类似枚举
- 在const关键字出现时将会被置为0,const常量中每新增一行,计数加1
const (
a1 = iota //a1=0
a2 //a2=1
_
a3 //a3=3
)
插队
const (
a1 = iota //0
a2 = 100 //100
a3 = iota //2
a4 //3
)
多个常量申明在一行
const (
d1, d2 = iota + 1, iota + 2 //d1=1,d2=2
d3, d4 = iota + 1, iota + 2 //d3=2,d4=3
)
定义数量级
const (
_ = iota
KB = 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 = 10
fmt.Printf("%d \n", a) //10
fmt.Printf("%d \n", a) //1010,%d表示二进制,转换成了二进制
fmt.Printf("%o \n", a) //转换成八进制
// 八进制,以0x开头
i := 077
fmt.Printf("%d \n", i)
//十六进制
i1 := 0x1234567
fmt.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 //0
TUESDAY //1
WEDNEDAY //2
THURSDAY //...
FRIDAY
SATUDAY
SUNDAY
M, N = iota, iota // 7,7
X, Y = iota + 1, iota // 9,8
)
/*
1. iota是常量组计数器
2. iota从0开始,没换行递增1
3. 常量组不赋值,默认与上一行表达式相同
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 string
age int
gender string
score float64
}
//定义方法绑定person类
func (p *Person) Eat() {
//尽量使用指针传递
fmt.Println(p.name + "吃的真香!")
}
4.17 继承
package main
import "fmt"
// 继承
//定义一个Human(人)类
type Human struct {
name string
age int
gender string
scode float64
}
//定义Human的方法
func (h *Human) Eat() {
fmt.Println(h.name + "吃的吧唧香....")
}
//定义一个学生类 -- 类的嵌套
type Student struct {
//包含Human类型的变量(类的嵌套)
hum Human
school string
}
//定义一个老师类 -- 继承Human
type Teacher struct {
//继承的时候虽然没有定义字段名字,但是会自动创建一个默认的同名字段
//这是为了在子类中依然可以操作父类,因为子类父类中可能存在同名的字段
Human //直接写父类名称,没有字段名字
subject string
}
func main() {
stu1 := Student{
hum: Human{
"狗娃",
19,
"男",
89.0,
},
school: "人民一中",
}
fmt.Println(stu1)
//类的继承
t1 := Teacher{}
t1.name = "强国"
t1.age = 19
t1.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 = names
fmt.Println("i代表切片数组:", i)
age := 20
j = age
fmt.Println("j代表数字:", j)
s := "hello"
k = s
fmt.Println("k代表字符串:", k)
//现在我们只知道k是interface类型,但是我们不知道具体代表什么类型?可以进行判断
kvalue, ok := k.(int)
if !ok {
fmt.Println("k不是int")
fmt.Printf("k的类型是:%T\n", k) //k的类型是:string
fmt.Printf("i的类型是:%T\n", i) //i的类型是:[]string
fmt.Printf("j的类型是:%T\n", j) //j的类型是:int
} else {
fmt.Println("k是int,k的值:", kvalue)
}
}
- 常用的场景:把interface当做一个函数的参数,(类似于print),使用switch来判断用户输入的不同类型,在根据不同的类型,做相应的逻辑处理
//创建具有三个数据类型的切片
array := make([]interface{}, 3)
array[0] = 1
array[1] = "hello"
array[2] = true
for _, 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语言中实现多态,需要实现定义的接口
//人类发起攻击,不同等级子弹效果不同
//创建接口 , 注意类型是interface
type IAttack interface {
//接口的函数可以有多个,但是只能有函数原型,不可以实现
Attack()
}
//玩家
type Human struct {
name string
level int
hurt 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 = &lowLevel
player.Attack()
//高等级玩家
highLevl := Human{
name: "狗蛋",
level: 6,
hurt: 10000,
}
player = &highLevl
player.Attack()
fmt.Println("-----多态--------")
doIAttack(&lowLevel)
doIAttack(&highLevl)
}
5、Go语言基础之流程控制
5.1 if else
age := 21
if 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 初始语句;条件表达式;结束语句{
循环体语句
}
// 变种1
i:= 5
for ;i<10;i++{
fmt.Println(i)
}
// 变种2
j:=5
for 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 main
import "fmt"
//结构体
type person struct {
name string
age int
gender string
hobby []string
}
func main() {
//申明person类型变量
var p person
//赋值
p.name = "zhoulin"
p.age = 21
p.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 string
age int
gender string
}
func f(x person) {
x.gender = "男" //修改的是副本的gender
}
func f2(x *person) {
// (*x).gender = "男" //根据内存地址找到原来的变量,修改的是原来的变量的值
x.gender = "男" //指针的语法糖,与上面相同找到原来的变量修改
}
func main() {
var p person
p.name = "zhoulin"
p.age = 17
p.gender = "女"
f(p)
fmt.Println(p) //{zhoulin 17 女}
f2(&p) //传递的是p的内存地址
fmt.Println(p) //{zhoulin 17 男}
}
7.3 构造函数
- 返回一个结构体变量的函数
//构造函数
type person struct {
name string
age 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 := 1
for {
fmt.Println("这是子go程:", count)
count++
// time.Sleep(500) //默认为微秒
time.Sleep(1 * time.Second)
}
}
func main() {
//启动子go程
go display()
//主go程
count := 1
for {
fmt.Println("============>这是主go程:", count)
count++
time.Sleep(1 * time.Second)
}
}
启动多个子go程,竞争资源
//用于子go程使用
func display(num int) {
count := 1
for {
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 := 1
for {
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写入数据向data
data := <-numChan
fmt.Println("子进程1,读取数据:", data)
}
}()
//子进程写入数据到管道numChan中
go func() {
for i := 0; i < 20; i++ {
numChan <- i
fmt.Println("子进程2写书数据:", i)
}
}()
//主进程写数据
for i := 20; i < 50; i++ {
//i写入数据到管道numChan
numChan <- i
fmt.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 <- i
fmt.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 <- i
fmt.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 <- i
fmt.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 <- i
fmt.Println("生产者生产:", i)
}
}
func main() {
//单向读通道
// var numChanReadOnly <-chan int
//单向写通道
// var numChanWriteOnly chan<- int
//生产者消费者模型
//C语言:数组+锁 thread1:写,thread2:读
//Go语言:goroutine + channel
//1.在主函数中创建一个双向channel:numChan
numChan := 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 main
import (
"fmt"
"time"
)
// 当程序总有多个channel协同工作时,ch1,chan2,在某时刻,ch1和chan2触发了,程序要做响应的处理
// 使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)
// select语法和Switch case很像,但是所有的分支条件都必须是通道io
func main() {
// var chan1, chan2 chan int
chan1 := make(chan int)
chan2 := make(chan int)
//启动1个go程,负责监听两个chan
go 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,写chan1
go func() {
for i := 0; i < 10; i++ {
chan1 <- i
// fmt.Println("写入chan1:", i)
time.Sleep(1 * time.Second / 2)
}
}()
//启动go2,写chan2
go 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 main
import (
"fmt"
"net"
"strings"
)
func Server() {
//1.创建监听
ip := "127.0.0.1"
port := 8848
address := fmt.Sprintf("%s:%d", ip, port)
// net.Listen("tcp", ":8848") // 简写,冒号前面默认是ip
listenner, 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 main
import (
"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. 写数据 --> Server
sendData := []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 main
import (
"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. 写数据 --> Server
sendData := []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 main
import (
"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 := 8848
address := fmt.Sprintf("%s:%d", ip, port)
// net.Listen("tcp", ":8848") // 简写,冒号前面默认是ip
listenner, 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开发:安全