Go 环境安装:
Go 的安装配置(go 1.14):
参看博客:
https://www.liwenzhou.com/posts/Go/go_menu/
https://www.liwenzhou.com/posts/Go/00_go_in_vscode/
https://blog.csdn.net/AdolphKevin/article/details/105480530
https://studygolang.com/pkgdoc
下载地址: https://golang.google.cn/dl/
安装好之后,配置GOPATH ,
下载 vs code 编辑器,
先在cmd 中:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
然后 ctrl shift p
输入>go:install
,
选择Go:Install/Update Tools
这个命令,
Go 的编译:
进入项目目录中,执行go mod init 此时会生成 go.mod ,
然后使用 go build 就生成了一个可执行文件,
Go 的Hello world:
package main import "fmt" func main() { fmt.Println("Hello world") }
Go mod 常见错误 解决:
gopkg.in/russross/blackfriday.v2: gopkg.in/russross/blackfriday.v2@v2.0.1: parsing go.mod: module declares its path as: github.com/russross/blackfriday/v2 but was required as: gopkg.in/russross/blackfriday.v2 (找不到它) replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
Go 基础知识:
Go 标识符 和 关键字 和 保留字
Go语言中标识符由字母数字和_
(下划线)组成,并且只能以字母和_
开头。
查看数据类型:
a := 1.23456 fmt.Printf("%T ", a)
%v 万能占位符,
a:= 10 fmt.Printf("%v ", a) b:= "tom" fmt.Printf("%v ", b) c:= false fmt.Printf("%v ", c)
a:= 10 fmt.Printf("%#v ", a) b:= "tom" fmt.Printf("%#v ", b) c:= false fmt.Printf("%#v ", c)
统计字符串中汉字的数量:
package main import ( "fmt" "unicode" ) func main() { s := "hello世界,我靠无情" count := 0 for _,c := range s{ if unicode.Is(unicode.Han,c){ count += 1 } } fmt.Println(count) }
go 语言for 循环实现 九九乘法表:
package main import "fmt" func main() { for i:=0;i<9;i++ { for j:=0;j<=i;j++ { fmt.Printf("%d x %d = %d ",i+1,j+1,(i+1)*(j+1)) } fmt.Println() } }
go 指针:
go 中的指针:
go的指针不支持 指针计算
指针的类型:
* int
* [2]int
指针数组 : [3]*int ,[3]*string ,[3]*(* int)
数组指针 : *[3]int *[3]string ,
多级指针:
package main import "fmt" func main() { a := 1 b := 2 arr := [...]*int{&a,&b} fmt.Println(arr) c := &a d := &b arr2 := [...]**int{&c,&d} fmt.Println(arr2) for _,val :=range arr2{ fmt.Println(*val) } for _,val :=range arr{ fmt.Println(*val) } }
Go 中的new :
new 和 make的区别:
package main import "fmt" func main() { var ptr *int fmt.Println(ptr) //fmt.Println(*ptr) // 报错 ptr = new(int) fmt.Println(ptr) fmt.Println(*ptr) // make 和 new 的区别 // 1,都是 用来申请内存的 // 2,make 是用来给 slice ,map ,chan 申请内存,它返回是 它们类型本身 // 3,new 一般是给基本数据类型申请内存,它返回是指针 }
map 按照 value 排序:
package main import ( "fmt" "strconv" ) func main() { m := make(map[string]int,10) for i:=0;i<5;i++ { m["age"+strconv.Itoa(i)] = 10 + i } m["age10"] = 20 m["age11"] = 6 // 下面对 m 按照 val 排序 keys := make([]string,0,10) for k :=range m{ keys = append(keys,k) } // 冒泡 排序 for i:=0;i<len(keys) - 1;i++{ for j:=0;j<len(keys) -1 -i;j++{ if m[keys[j]] > m[keys[j+1]]{ temp := keys[j] keys[j] = keys[j+1] keys[j+1] = temp } } } for _,key := range keys{ fmt.Println(key,m[key]) } }
Go 的函数传参永远是 值传递:
new 结构体 得到指针,
var 结构体 得到的是值
package main import "fmt" type Person struct { // 这是自定义类型 自定义了Person 这个类型 name string age int } func test(p *Person) { // go 函数传参永远是 值传递 //(*p).name = "jane" p.name = "jane" // 和上面的写法一样 } func main(){ //var p Person p := new(Person) p.name = "tom" p.age = 18 fmt.Println(p.name) test(p) fmt.Println(p.name) }
go 的 “类” 构造函数 和方法 :
1 package main 2 3 import "fmt" 4 5 type Person struct { // 这是自定义类型 自定义了Person 这个类型 6 name string 7 age int 8 } 9 // Person 的构造函数 10 func personConstructor(name string,age int) *Person { 11 return &Person{ 12 name:name, 13 age:age, 14 } 15 } 16 // Person 的方法 17 func (p *Person)run() { 18 fmt.Println("I am run,My name's:",p.name) 19 } 20 21 func main(){ 22 p := personConstructor("tom",18) 23 p.run() 24 }
go 自定义类型:
给 int 类型加个方法
package main import "fmt" type myInt int func (i myInt) hello(){ fmt.Println("我是一个Int") } func main(){ var a myInt a = 10 a.hello() fmt.Println(a) }
go 的 “继承”
package main import "fmt" type Animal struct { name string } func (animal *Animal)move(){ fmt.Println("I am moving!") } type Dog struct { name string *Animal // 通过嵌套匿名结构体的方式 实现 “ 继承 ” } func main(){ var dog = new(Dog) dog.move() }
结构体 与 JSON 互转
package main import ( "encoding/json" "fmt" ) type Person struct { Name string Age int } func main(){ var p = new(Person) p.Name = "tom" p.Age = 18 // 序列化 ret,err := json.Marshal(p) fmt.Println(err) fmt.Println(string(ret)) // 反序列化 var p2 = new(Person) json.Unmarshal(ret,&p2) fmt.Println(p2) fmt.Println(p2.Name) fmt.Println(p2.Age) }
go 利用接口实现多态:
package main import "fmt" type myInterface interface { eat() } type Person struct {} func (p *Person)eat(){ fmt.Println("人吃饭") } type Dog struct {} func (d *Dog)eat(){ fmt.Println("狗吃饭") } func eat(obj myInterface) { // 多态 obj.eat() } func main() { p := new(Person) d := new(Dog) eat(p) eat(d) }
go 接口:
查看一个对象是否实现了一个接口(实现接口中所有的方法):
package main import ( "fmt" ) type Animal interface { Say() } type Dog struct {} func (d *Dog)Say() { fmt.Println("Dog say") } func main() { d := Dog{} var xxx interface{} = &d _,ok := xxx.(Animal) // d 必须实现 Animal 接口中所有的方法时 才为true fmt.Println(ok) if ok{ d.Say() } }
空接口:
任何 类型的变量都实现了 空接口 ,
空接口类型 ,
package main import "fmt" func main() { // map 的value 类型是 空接口类型 m := make(map[string]interface{}) m["name"] = "tom" m["age"] = 18 m["hobby"] = []string{"basketball","movie"} fmt.Println(m) for k,v := range m { fmt.Println(k,v) } }
空接口类型 形参:
package main import ( "fmt" ) func myPrint(obj interface{}) { fmt.Println(obj) } func main() { myPrint("tom") myPrint(18) myPrint([]string{"tom","jack"}) myPrint([...]int{1,2,3}) myPrint(struct { name string age int }{name: "tom",age: 18}) }
空接口 类型断言:
package main import "fmt" func myPrint(obj interface{}) { s,ok := obj.(string) if ok{ fmt.Println("此时的 s 就是 string 类型了,就不是 接口类型了") fmt.Println(s) }else{ fmt.Println("非 string 类型") } } func main() { myPrint("tom") myPrint(18) myPrint([]string{"tom","jack"}) myPrint([...]int{1,2,3}) myPrint(struct { name string age int }{name: "tom",age: 18}) }
判断存在key 且 为 string 类型:
ret,ok := ctx.Input.Session("age").(string) // 存在age 并且要求 age 为 string 类型,否则返回false
swith 结合 空接口:
package main import "fmt" func myPrint(obj interface{}) { switch obj.(type) { case string: fmt.Println("是个string类型") case int: fmt.Println("是个int 类型") default: fmt.Println("其他") } } func main() { myPrint("tom") myPrint(18) myPrint([]string{"tom","jack"}) myPrint([...]int{1,2,3}) myPrint(struct { name string age int }{name: "tom",age: 18}) }
点括号的用法(类型转换):
接口变量调用
package main import "fmt" // Sayer 接口 type Sayer2 interface { say() } type dog struct {} type cat struct {} // dog实现了Sayer接口 func (d dog) say() { fmt.Println("汪汪汪") } // cat实现了Sayer接口 func (c cat) say() { fmt.Println("喵喵喵") } func main() { var x Sayer2 // 声明一个Sayer类型的变量x ret := x.(dog) // 将 接口变量x 的类型从 Sayer2 转为 dog ret.say() }
go 文件操作:
读文件:
使用的是os.Open()
bufio:
package main import ( "bufio" "fmt" "io" "os" ) func main() { fp,err := os.Open("zcb.go") if err != nil{ fmt.Println("open file error") return } defer fp.Close() reader := bufio.NewReader(fp) for { line,err := reader.ReadString(' ') if err == io.EOF{ if len(line)!=0{ fmt.Print(line) } fmt.Println("我读完了") break } if err != nil{ fmt.Println("read file err") return } fmt.Print(line) } }
ioutil:
读取整个文件:
package main import ( "fmt" "io/ioutil" "os" ) func main() { fp,err := os.Open("zcb.go") if err != nil{ fmt.Println("open file error") return } defer fp.Close() content,err := ioutil.ReadFile("zcb.go") if err != nil{ fmt.Println("read file error") return } fmt.Println(string(content)) }
写文件:
使用的是 os.OpenFile()
package main import ( "fmt" "os" ) func main() { /* os.O_WRONLY|os.O_CREATE|os.O_APPEND 只写 append 没有时创建 os.O_WRONLY|os.O_CREATE|os.O_TRUNC 只写 没有时创建,有时直接清空 */ fp,err := os.OpenFile("test.text",os.O_WRONLY|os.O_CREATE|os.O_TRUNC,0666) if err != nil{ fmt.Println("file open err") } defer fp.Close() _,err2 := fp.WriteString("hello world 世界你好啊") if err2 != nil{ fmt.Println("write file err") } }
读取用户输入:
如果用scan 的话,无法读取用户输入的空格数据,例如hello world 只能是 hello,
package main import "fmt" func main() { var motto string _,err := fmt.Scan(&motto) // 此时如果用户 hello world if err != nil{ fmt.Println("读取用户输入 失败") } fmt.Println(motto) }
所以这里可以使用bufio
package main import ( "bufio" "fmt" "os" ) func main() { reader := bufio.NewReader(os.Stdin) res,err := reader.ReadString(' ') if err!= nil{ fmt.Println("读取用户输入 失败") } fmt.Println(res) }
自定义 日志 包:
需求分析:
日志的四种级别:DEBUG INFO WARN ERROR
可以向不同位置写日志
支持开关控制(例如,上线后,只有WARN 和 ERROR 才可以输出 )
支持日志文件切割
完整日志记录包含时间,行号,文件名,级别,日志信息
切割文件时:
https://files.cnblogs.com/files/zach0812/%E7%AE%80%E5%8D%95%E6%97%A5%E5%BF%97%E5%8C%85go%E8%AF%AD%E8%A8%80.zip
异步收集日志功能实现:
https://files.cnblogs.com/files/zach0812/%E5%BC%82%E6%AD%A5%E6%94%B6%E9%9B%86%E6%97%A5%E5%BF%97%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0_go%E8%AF%AD%E8%A8%80.zip
go 并发:
1,goroutine
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func hello(i int){ defer wg.Done() fmt.Println("hello ",i) } func main() { for i:=0;i<1000;i++{ wg.Add(1) go hello(i) } fmt.Println("main") wg.Wait() }
2,channel 类型
必须使用make 初始化,(slice , map,channel 都要用 make 分配内存)
func main() { var wg sync.WaitGroup ch1 := make(chan int,2) for i:=0;i<2;i++{ ch1 <- i } wg.Add(1) go func(ch chan int) { defer wg.Done() time.Sleep(time.Second) x := <- ch fmt.Println(x) }(ch1) ch1 <- 10 // 已经满了,此时会阻塞 在这里~ fmt.Println("hello world") wg.Wait() }
worker pool (goroutine 池):
func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker:%d start job:%d ", id, j) time.Sleep(time.Second) fmt.Printf("worker:%d end job:%d ", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) // 开启3个goroutine for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 5个任务 for j := 1; j <= 5; j++ { jobs <- j } close(jobs) // 输出结果 for a := 1; a <= 5; a++ { <-results } }
需求01:
//使用goroutine和channel实现一个计算int64随机数各位数和的程序。
//开启一个goroutine循环生成1000个 int64类型的随机数,发送到jobChan
//开启24个goroutine从jobChan中取出随机数计算各位数的和,将结果发送到resultChan
//主goroutine从resultChan取出结果并打印到终端输出
package main import ( "fmt" "math/rand" "sync" ) func getInt64Sum(b int64) int { ret := int64(0) for { ret += b % 10 temp := b/10 if temp == 0{ break } b = temp } return int(ret) } func worker(jobs <-chan int64, results chan <-int) { defer wg.Done() for item := range jobs { results <- getInt64Sum(item) } } var wg sync.WaitGroup func main() { jobs := make(chan int64,1000) results := make(chan int,1000) wg.Add(1) go func() { // 开启一个goroutine 放1000个int64 的数 到jobs 中 defer wg.Done() for i:=0;i<100;i++{ ret := rand.Int63() jobs <- ret } close(jobs) }() // 开启24 个goroutine 去处理 jobs 的内容,并将结果放到 results 中 for i:=0;i<24;i++ { wg.Add(1) go worker(jobs,results) } wg.Wait() close(results) for res := range results{ fmt.Println(res) } }
需求02:
不断生成随机数,一直处理一直输出到终端
package main import ( "fmt" "math/rand" "sync" "time" ) func getInt64Sum(b int64) int { ret := int64(0) for { ret += b % 10 temp := b/10 if temp == 0{ break } b = temp } return int(ret) } func worker(jobs <-chan int64, results chan <-int) { defer wg.Done() for item := range jobs { results <- getInt64Sum(item) } } var wg sync.WaitGroup func main() { jobs := make(chan int64,1000) results := make(chan int,1000) wg.Add(1) go func() { defer wg.Done() for { time.Sleep(100*time.Millisecond) // 0.1s 生成一个 ret := rand.Int63() jobs <- ret } }() // 开启24 个goroutine 去处理 jobs 的内容,并将结果放到 results 中 for i:=0;i<24;i++ { wg.Add(1) go worker(jobs,results) } for res := range results{ fmt.Println(res) } wg.Wait() }
go socket编程 :
osi 七层: 应表会 传 网 数物
socket 通信
1,简单版:
server 端:
package main import ( "fmt" "net" ) /*tcp server*/ func main() { // 1 监听端口 listener,err := net.Listen("tcp","127.0.0.1:8000") if err != nil { fmt.Println("tcp server listen 127.0.0.1:8000 failed,err:",err) return } // 2 等待客户端来连接 conn,err := listener.Accept() if err != nil { fmt.Println("tcp server accept failed ,err:",err ) return } // 3 通信 var ret [128]byte n,err := conn.Read(ret[:]) if err != nil { fmt.Println("tcp server conn read failed ,err",err) return } fmt.Println(string(ret[:n])) }
client端
package main import ( "fmt" "net" ) /*tcp client*/ func main() { // 1 连接 服务端 conn,err := net.Dial("tcp","127.0.0.1:8000") if err != nil { fmt.Println("tcp client dial failed ,err:",err) return } // 2 通信 temp := "Hello server,I'm client! 你好啊" _,err = conn.Write([]byte(temp)) if err != nil { fmt.Println("tcp client write failed ,err:",err) } conn.Close() }
2,升级版:
server 端:
package main import ( "fmt" "net" ) func processConn(conn net.Conn){ // 3 通信 var ret [128]byte n,err := conn.Read(ret[:]) if err != nil { fmt.Println("tcp server conn read failed ,err",err) return } fmt.Println(string(ret[:n])) } /*tcp server*/ func main() { // 1 监听端口 listener,err := net.Listen("tcp","127.0.0.1:8000") if err != nil { fmt.Println("tcp server listen 127.0.0.1:8000 failed,err:",err) return } for { // 2 等待客户端来连接 conn,err := listener.Accept() if err != nil { fmt.Println("tcp server accept failed ,err:",err ) return } go processConn(conn) } }
client 端:
同上,
tcp 粘包 现象:
大端和 小端:
当一个变量是多个字节的时候会存在大端小端的问题,
该变量在内存中从左开始,依次高位到低位,为 大端
从 右开始,依次高位到低位,为 小端
开发人员 需要注意的东西:
注释日志单元测试
go net/http 包:
package main import "net/http" func f1(w http.ResponseWriter, r * http.Request) { s := "Welcome come to my site." w.Write([]byte(s)) } func main() { http.HandleFunc("/index",f1) http.ListenAndServe("127.0.0.1:8000",nil) }
http client 端
就是爬虫,
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { response ,_ := http.Get("http://127.0.0.1:8000/index") fmt.Println(response.Status) fmt.Println(response.Body) defer response.Body.Close() ret ,_ := ioutil.ReadAll(response.Body) fmt.Println(string(ret) ) }
go 单元测试 :
pass
go 获取本地的ip:
package main import ( "fmt" "net" "strings" ) // 获取本地对外的IP func GetOutBoundIP()(ip string,err error){ conn,err := net.Dial("udp","8.8.8.8:80") if err != nil { return } defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) fmt.Println(localAddr.String()) ip = strings.Split(localAddr.IP.String(),":")[0] return } func main() { ip,err := GetOutBoundIP() fmt.Println(ip) fmt.Println(err) }
Go 项目:
go 操作mysql :
安装驱动 :
1,go mod init main.go
2,go get -u github.com/go-sql-driver/mysql
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { // database source name 数据库信息 dsn := "root:123456@tcp(127.0.0.1:3306)/go_mysql" db, err := sql.Open("mysql", dsn) if err != nil { fmt.Printf("dsn :%s format err,err : %v ", dsn, err) // 并不会真正校验 是否成功连接 只是校验 格式是否正确 return } err = db.Ping() // 此时 会真正校验是否能成功连接 if err != nil { fmt.Printf("open %s failed,err: %v ", dsn, err) return } fmt.Println("connection success ") }
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) var db *sql.DB type user struct { // user 表对应的结构体 id int name string age int } func initDB() error { // database source name 数据库信息 dsn := "root:123456@tcp(127.0.0.1:3306)/sql_test" var err error db, err = sql.Open("mysql", dsn) if err != nil { return err } err = db.Ping() // 此时 会真正校验是否能成功连接 if err != nil { return err } db.SetMaxOpenConns(10) // 设置数据库连接池的 最大连接数 db.SetMaxIdleConns(5) // 最大闲置的 连接数 return nil } // 查询单个记录 func queryOne(id int) { sqlStr := `select id,name,age from user where id=?;` var u user //rowObj := db.QueryRow(sqlStr,id) //err := rowObj.Scan(&u.id,&u.name,&u.age) // 注:必须要 Scan 其内部有 defer 用于 释放连接 err := db.QueryRow(sqlStr,id).Scan(&u.id,&u.name,&u.age) // 为了防止忘记,上面的可以直接写为 一行 if err != nil { fmt.Printf("query failed,err:%v ",err) return } fmt.Println(u) } // 查询多个记录 func queryMany(id int) { sqlStr := `select id,name,age from user where id >= ?;` rows,err := db.Query(sqlStr,id) if err != nil { fmt.Printf("exec %s query failed,err :%v ", sqlStr, err) return } defer rows.Close() // 一定要关闭 rows // 循环取值 for rows.Next(){ var u user err := rows.Scan(&u.id,&u.name,&u.age) if err != nil { fmt.Printf("scan rows failed,err :%v ", err) return } fmt.Println(u) } } // 插入数据 删除数据 更新数据 都是用 db.Exec() 查询用的是 db.Query func insert() { sqlStr := `insert into user(name,age) values("jane",38)` res,err := db.Exec(sqlStr) if err != nil { fmt.Printf("exec sqlStr:%s failed,err :%v ",sqlStr,err) return } // 如果是插入数据,可以从res 拿到最后一个的id id,err := res.LastInsertId() if err != nil { fmt.Printf("get last insert id failed,err:%v ", err) return } fmt.Println(id) } func main() { err := initDB() if err != nil { fmt.Printf("init DB failed,err :%v ",err) return } fmt.Println("connection success ") // 查 // 1 查询单条记录 //queryOne(1) // 2 查询多条记录 //queryMany(1) // 增 删 改 insert() }
注: sqlx 查询的时候 更方便一点,需要安装 go get github.com/jmoiron/sqlx
go 操作redis:
go get -u github.com/go-redis/redis
go 操作nsq:
NSQ是目前比较流行的一个分布式的消息队列
如何退出 子goroutine :
1,定义全局变量
2,利用通道 (如果能从 通道中取到值 就退出,)
package main import ( "fmt" "sync" "time" ) func f() { defer wg.Done() FORLOOP:for { fmt.Println("hello go") time.Sleep(500*time.Millisecond) // 0.5s select { case <- exitChan: break FORLOOP // 退出 for 循环 default: } } } var wg sync.WaitGroup var exitChan =make(chan struct{},1) func main() { wg.Add(1) go f() time.Sleep(5*time.Second) // 5s exitChan <- struct {}{} // 退出子 goroutine wg.Wait() }
3,使用context 退出
package main import ( "context" "fmt" "sync" "time" ) func f(ctx context.Context) { defer wg.Done() FORLOOP:for { fmt.Println("hello go") time.Sleep(500*time.Millisecond) // 0.5s select { case <- ctx.Done(): break FORLOOP // 退出 for 循环 default: } } } var wg sync.WaitGroup func main() { ctx,cancel := context.WithCancel(context.Background()) wg.Add(1) go f(ctx) time.Sleep(5*time.Second) // 5s cancel() wg.Wait() }
并且可以一个cancel 可以退出 多层的 子 goroutine
package main import ( "context" "fmt" "sync" "time" ) func f2(ctx context.Context) { defer wg.Done() FORLOOP:for { fmt.Println("hello go in f2") time.Sleep(500*time.Millisecond) // 0.5s select { case <- ctx.Done(): break FORLOOP // 退出 for 循环 default: } } } func f(ctx context.Context) { defer wg.Done() go f2(ctx) FORLOOP:for { fmt.Println("hello go in f1") time.Sleep(500*time.Millisecond) // 0.5s select { case <- ctx.Done(): break FORLOOP // 退出 for 循环 default: } } } var wg sync.WaitGroup func main() { ctx,cancel := context.WithCancel(context.Background()) wg.Add(1) go f(ctx) time.Sleep(5*time.Second) // 5s cancel() wg.Wait() } /* hello go in f1 hello go in f2 hello go in f2 hello go in f1 hello go in f1 hello go in f2 hello go in f1 hello go in f2 hello go in f2 hello go in f1 hello go in f1 hello go in f2 hello go in f2 hello go in f1 hello go in f1 hello go in f2 hello go in f2 hello go in f1 hello go in f1 hello go in f2 */
时间相关:
func test() { // 加载时区 loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { fmt.Println(err) return } now := time.Now() // 1 表示下 2020.02.02 这个时间 t := time.Date(2020,2,2,0,0,0,0,loc) //fmt.Println(t) // 2 现在时间距离 20200202 过去了多久 ret := now.Sub(t) fmt.Printf("%f天 ",ret.Hours()/24) // 3 一年前的时间 t2 := now.Add(-365*24*time.Hour) fmt.Println(t2) // 4 一年后的时间 t3 := now.Add(365*24*time.Hour) fmt.Println(t3) }
网络编程:
osi 七层:
Open System Interconnection
开放式系统互联是把网络通信的工作分为7层,分别是物理层,数据链路层,网络层,传输层,会话层,表示层和应用层。
osi 七层 与 各协议的对照表: