go 计算机网络编程,Go 并发、Socket、HTTP 编程

并发编程

1、并行和并发

并行(parallel): 指同一时刻,有多条指令在多个处理器上执行

并发(concurrency): 指同一个时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得宏观上具有多个 进程同时执行的效果,但是微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。(时间片轮转)

Go 从语言层面就支持了并发,同时,并发程序的内存管理有时候是很复杂的,而 Go 语言提供了自动垃圾回收机制。

Go 语言为并发编程而内置了上层 API 基于 CSP(communicating sequential processes,顺序通信进程)模型。这就意味着显示锁都可以避免的,因为 Go 语言通过安全的通道发送和接受数据以实现同步,大大的简化了并发程序的编写。(CSP通信的方式实现同步而不是锁)

一般情况下,一个普通的桌面计算机跑十几二十个线程就有点负载过大了,但是同样这台机器却可以轻松的让成百上千甚至过万个 goroutine 进行资源竞争。

2、goroutine

goroutine 是 Go 并发设计的核心。goroutine 说到底就是协程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些 goroutine 之间的内存共享。执行 goroutine 只需极少的栈内存(大概 4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine 比 thread 更易用、更高效、更轻便。

只需要在函数调用语句前添加 go 关键字,就可以创建并发执行单元。开发人员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上执行。

在并发编程里,我们通常想讲一个过程切分成几块,然后让每个 goroutine 各自负责一块工作。当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine。新的 goroutine 会用 go 语句来创建。

runtime.Goexit(): 退出此协程

runtime.GOMAXPROCS(n):设置并行计算的 CPU 核数的最大值,并返回之前的值

runtime.Gosched(): 用于让出 CPU 时间片,让出当前 goroutine 的执行权限,调度器安排其他等待的任务运行,并在下次某个位置从该位置恢复执行。这就像接力赛,A 拍了一会碰到代码 runtime.Gosched() 就把接力棒交给 B 了,A 歇着 B 继续跑。

注意:

主协程退出了,其他子协程也要跟着退出

有可能主协程退出了,但是子协程还没来得及调用

package main

import (

"fmt"

"time"

"runtime"

)

func test() {

defer fmt.Println("aaa") // 终止协程,该语句仍然会被执行

// return // 终止此函数

runtime.Goexit() // 终止坐在的协程

fmt.Println("bbb")

}

func NewTask() {

// do something

}

func main() {

go newTask() // 新建一个协程,新建一个任务(只要看见一个 go 便创建一个协程)

// do something

}

多任务很容易出现资源竞争,就需要channel 做协程同步

2、channel

本质上就是一个管道

价值:

使用通信来共享数据

使用通信来同步

goroutine 运行在相同的地址空间,因此访问共享内存必须做好同步。gotoutine 奉行通过 CSP(通信) 来共享内存,而不是共享内存来通信。

引用类型 channel 是 CSP 模式的具体实现,用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。

和 map 类似,channel 也一个对应 make 创建的底层数据结构的引用。

当我们复制一个 channel 或用于函数参数传递时,我们只是拷贝了一个 channel 的引用,因此调用者何时被调用者将引用同一个 channel 对象。和其他的引用类型一样,channel 的零值也是 nil。

定义一个 channel 时,也需要定义发送到 channel 的值的类型。channel 可以使用内置的 make() 函数来创建:

make (chan Type) // 无缓冲区,等价于 make (chan Type, 0)

make (chan Type, capacity) // 有缓冲区

当 capacity = 0 时,channel 是无缓冲阻塞读写的,当 capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity 个元素才阻塞写入。

channel 操作符

channel

x :=

x, ok :=

默认情况下,channel 接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得 goroutine 同步变的更加的简单,而不需要显式的 lock

var ch make(chan int) // 定义通道

func person1() {

Printer("hello")

ch

}

func person2() {

Printer("world")

}

3、无缓冲和有缓冲 channel

无缓冲的通道(unbuffered channel)是值在接受前没有能力保存任何值的通道。

这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送和接收操作的 gotoutine 阻塞等待。

这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作的单独存在。

bbe581a44ffc

使用无缓冲通道 goroutine 之间同步

在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执行或者接收。

在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这是,这个 goroutine 会在通道中被锁住,直到交换完成。

在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道接收数据。这个 goroutine 一样会在通道中被锁住,直到交换完成。

在第 4 步和第 5 步,进行交换,并最终,在第 6 步,两个 goroutine 将他们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做别的事情了。

无缓冲的 channel 创建格式:

make (chan Type) // 等价于 make(chan Type, 0)

0 表示容量,表示没有缓存,不能存东西

如果没有指定缓冲区容量,那么通道就是同步的,因此会阻塞到发送者准备好发送和接收着准备好接收。

bbe581a44ffc

有缓冲的通道在 goroutine 之间同步数据

有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换:有缓冲的通道没有这种保证。

在第 1 步,右侧的 goroutine 正在从通道接收一个值。

在第 2 步,右侧的这个 goroutine 独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。

在第 3 步,左侧的 goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。

最后,在第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

有缓冲的 channel 创建格式:

make(chan Type, capacity)

如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就是无阻塞地进行。

4、channel 关闭

channel 不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显示的结束 range 循环之类的,才去关闭 channel;

关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值);

关闭 channel 后,可以继续向 channel 接收数据;

对于 nil channel,无论收发都会被阻塞

ch := make(chan int) // 创建一个无缓存的 channel

// 不需要写数据的时候,关闭 channel

cose(ch)

// 如果 ok 为 true,说明管道没有关闭

num, ok :=

5、访问 channel 内容

迭代

range

for num := range ch {

fmt.Println("num =", num)

}

6、单向的 channel

默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以从里面接收数据。

但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。

单向 channel 变量的声明非常简单,如下:

var ch1 chan int // ch1 是一个正常的 channel,不是单向的

var ch2 chan

var ch3

chan

可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通 channel:

c := make(chan int, 3)

var send chan

var revc

send

生产者消费者应用:

package main

import "fmt"

// 此通道只能写,不能读

func producer(in chan

for i := 0; i < 10; i++ {

out

}

}

// 此 channel 只能读,不能写

func consumer(out

for num := range out {

fmt>Println("num = ", num)

}

}

func main() {

// 创建一个双向通道

ch := make(chan int)

// 生产者,生产数字,写入 channel

// 新开一个协程

go producer(ch) // channel 传参,引用传递

// 消费者,从 channel 读取内容,打印

consumer(ch)

}

7、Timer / Ticker 定时器

Timer 是一个定时器,代表未来的一个单一事件,你可以告诉 timer 你要等待多长时间,它提供一个 channel,在将来的那个时间那个 channel 提供了一个时间值。

// 创建定时器,2 秒后就会往 time 通道写内容(当前时间)

timer1 := timer.NewTimer(time.Second * 2)

fmt.Println("当前时间: ", time.Now())

// 2s 后,往 timer.C 写数据,有数据后,就可以读取

t :=

fmt.Printf("t= %V\n", t)

延迟:

// 定时2s,阻塞2s,2s后产生事件,往channel写内容

停止和重置:

timer.Stop() // 停止定时器

timer.Reset(1 * time.Second) // 重新设置为 1 秒

Ticker 是一个定时触发的计时器,它会以一个间隔(interval)往 channel 发送一个事件(当前时间),而 channel 的接收者可以以固定的时间间隔从 channel 中读取事件

ticker := time.NewTicker(1 : time.Second)

i := 0

for {

I++

fmt.Println("i = ", i)

if i == 5 {

ticker.Stop()

break

}

}_

8、Select

Go 里面提供了一个关键字 select,通过 select 可以监听 channel 上的数据流动。

select 的用法和 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。

与 switch 语句可以选择任何可使用相等比较的条件相比,select 有比较多的限制,其中最大的一条限制就是每个 case 语句必须是一个 IO 操作,大致的结构如下:

select {

case

// 如果 chan1 成功读到数据,则进行该 case 处理语句

case chan2

// 如果成功向 chan 2 写入数据,则进行该 case 处理语句

default:

// 如果上面都没有成功,则进入 default 处理流程

}

在一个 select 语句中,Go 语言会按照顺序从头到尾评估每一个发送和接收的语句。

如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从哪些可以执行的语句中任意选择一条来使用。

超时的实现:

import "time"

func main() {

ch := make(chan int)

quit := make(chan bool)

// 新开一个协程

go func() {

for {

select {

case num :=

fmt.Println("num = ", num)

case

fmt.Println("超时")

quit

}

}()

}

for i := 0; i < 5; i++ {

ch

time.Sleeep(time.Second)

}

fmt.Println("程序结束")

}

网络概述、Socket 编程

1、网络协议

协议可以理解为规则,是数据传输和数据的解释的规则。

为了减少协议复杂性,大多数网络模型采用分层来组织。每一层都有自己的功能,每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽。

越下面的层,越靠近硬件;越上面的层,越靠近用户。至于每一层叫什么名字,其实并不重要(除了辨识会问,哈哈)。但要具体指导每一层的作用。

OSI 七层协议:

物理层:主要是无力设备标准,如网线接口、光线接口。主要作用就是传输比特流,这一层的数据叫做比特。

数据链路层:定义如何让格式化数据数据以帧为单位进行传输,以及如何让控制对无力介质的访问。错误检测和纠正,保证数据的可靠传输。

网络层:位于不同位置两个主机系统提供连接和路径选择。(IP\ICMP\IGMP)

传输层:定义了一些传输数据的协议和端口号(WWW端口80等)。跟端口相关(TCP\UDP)

会话层:建立和维持会话(Session)

表示层:主要是提供格式化的表示和转换数据的服务。数据的压缩、加密、解密等都是在该层完成。

应用层:(FTP\Telnet\NFS)

几个常见协议:

ARP: 通过 IP 地址找 MAC 地址

RARP: 通过 MAC 地址找 IP 地址

IP: 因特网互联协议

TCP: Transmission Control Protocol, 是一种面向连接的、可靠的、基于字节流的传输层通信协议

UDP: User Datagram Protocol, 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。

HTTP: 超文本传输协议

ICMP: Internet Control Message Protocol, 因特网控制报文协议,用于在 IP主机、路由器之间的控制协议。

IGMP: Internet Group Message Protocol,提供互联网多点传送的功能,即将一个 ip 包拷贝到多个 host

bbe581a44ffc

TCP / IP 协议

网络通信条件:

网卡:MAC 地址(不需要用户处理):通过 IP 找 MAC

逻辑地址:IP 地址(需要用户指定),为了确定哪个电脑接收

端口:确定哪个程序接收

同一个程序/进程,只能绑定一个端口

不同系统,同一端口对应的程序可能不一样

bbe581a44ffc

封包和解包流程

2、Socket 编程

Socket 套接字,起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件“,都可以用”打开 open -> 读写 write/read -> 关闭 close”。网络的 Socket 数据涮出是一种特殊的 IO,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。

常见的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket,对应于无连接的 UDP 服务应用。

C/S模型:

客户端(Client): 主动请求服务

服务器(Server):被动提供服务

B/S模型:

浏览器(Browser):html

服务器(Server)

TCP 的 C/S 架构:

bbe581a44ffc

TCP 的 C/S 架构

TCP 服务器

package main

import (

"fmt"

"net"

)

func main() {

// 监听

ln, err := net.Listen("tcp", ":127.0.0.1:8080")

if err != nil {

fmt.Println("err = ", err)

return

}

defer listener.Close()

// 阻塞等待用户连接

for {

conn, err := listener.Accept()

if err != nil {

fmt.Println("err = ", err)

continue

}

// 接收用户的请求

buf := make([]byte, 1024)// 定义 1024 大小的缓冲区

n, err1 := conn.Read(buf)

if err1 != nil {

fmt.Println("errr1 = ", err1)

continue

}

fmt.Println("buf = ", string(buf[:n]))

}

defer conn.Close() // 关闭当前用户链接

}

TCP 客户端

package main

import (

"fmt"

"net"

)

func main() {

// 主动连接服务器

conn, err := net.Dial("tcp", "127.0.0.1:8000")

if err != nil {

fmt.Println("err = ", err)

return

}

defer conn.Close()

// 发送数据

conn.Write([]byte("are u ok?"))

}

3、Socket 并发

使用 goroutine 处理多个用户的 Socket 连接

// 处理用户请求

func HanleConn(conn net.Conn) {

// 函数调用完毕,自动关闭 conn

defer conn.Close()

// 获取客户端的网络地址信息

addr := conn.RemoteAddr().String()

// print addr

buf := make([]byte, 2048)

for {

// 读取用户数据

n, err := conn.Read(buf)

if err != nil {

// print err

return

}

// print n

// 输入 exit 退出连接

if "exit" == string(buf[:n]) {

// print addr

return

}

// 把数据转换为答谢,再发给用户

conn.Write([]byte(strings.ToUpper(string(buf[:n]))))

}

}

func main() {

// 监听

listen, err := net.Listen("tcp", "127.0.0.1:8000")

if err != nil {

// print err

return

}

defer listener.Close()

// 接收多个用户的请求

for {

conn, err := listener.Accept()

if err != nil {

// print err

return

}

// 处理用户请求,每来一个请求新建一个协程

go HandleConn(conn)

}

}

客户端多任务模式,即可以接受输入,也可以接受服务器 Socket 回复

func main() {

// 主动连接服务器

conn, err := net.Dial("tcp", "127.0.0.1:8000")

if err != nil {

// print err

return

}

// main 调用完毕,关闭连接

defer conn.Close()

go func {

// 从键盘输入内容,给服务器发送内容

str := nake([]byte, 1024)

for {

n, err := os.Stdin.Read(str) // 从键盘读取内容,放在 str

if err != nil {

// print err

return

}

// 把输入的内容发送给服务器

conn.Write(str[:n])

}

}()

// 接收服务器回复的数据

// 切片缓冲

buf := make([]byte, 1024)

for {

n, err := conn.Read(buf) // 接受的服务器的请求

if err != nil {

// print err

return

}

// print string(buf[:n])

}

}

4、使用 Socket 实现文件传输

获取文件属性

package main

import (

"fmt"

"os"

)

func main() {

list := os.Args

if len(list) != 2 {

// print useage: xxx file

return

}

fileName := list[1]

info, rr := os.Stat(fileName) // 获取文件属性

if err != nil {

// print err

return

}

// print info.Name(), info.Size()

}

文件发送端

package main

import (

"fmt"

"os"

"io"

"os"

)

func SendFile(path string, conn net.Conn) {

// 以只读方式打开文件

f, err := os.Open(path)

if err != nil {

// print err

return

}

defer f.Close()

// 读文件内容,读多少发送多少

buf := make([]byte, 1024 * 4)

for {

n, err := f.Read(buf)

if err != nil {

if err == io.EOF {

// 文件发送完成

} else {

print err

}

return

}

// 发送内容

conn.Write(buf[:n]) // 给服务器发送内容

}

}

func main() {

// 提示输入文件

// print

var path string

fmt.Scan(&path)

fileName := list[1]

info, rr := os.Stat(fileName) // 获取文件属性

if err != nil {

// print err

return

}

// 主动连接服务器

conn, err1 := net.Dial("tcp", "127.0.0.1:8000")

if err1 != nil {

// print err

return

}

defer conn.Close()

var n int

// 给发送方,先发送文件名

_, err = conn.Write([]byte(info.Name()))

if err != nil {

// print err

return

}

// 接收对方回复,如果回复ok,说明对方准备好,可以发送文件

var n int

buf := make([]byte, 1024)

n, err = conn.Read(buf)

if err != nil {

// print err

return

}

if "ok" == buf[:n] {

// 发送文件内容

SendFile(path, conn)

}

}

文件接收端

package main

import (

"fmt"

"os"

"io"

"os"

)

// 接收文件内容

func RecvFile(fileName string, conn net.Conn) {

// 新建文件

f, err := os.Create(fileName)

if err != nil {

// print err

return

}

buf := make([]byte, 1024 * 4)

// 接收多少,写多少

for {

n, err := conn.Read(buf)

if err != nil {

if err == io.EOF {

// 文件接收完毕

} else {

// print err

}

return

}

if n == 0 {

// 文件接收完毕

return

}

// 往文件写入内容

f.Write(buf[:n])

}

}

func main() {

// 监听

listener, err := net.Listen("tcp", "127.0.0.1:8000")

if err != nil {

// print err

return

}

defer listenner.Close()

// 阻塞等待用户连接

conn, err1 := listenner.Accept()

if err1 != nil {

// print err1

return

}

buf := make([]byte, 1024)

var n int

n, err2 = conn.Read(buf) // 读取对方发送的文件名

if err2 != nil {

// print errr2

return

}

defer conn.Close()

// fileName 保存一下

fileNmae := stirng(buf[:n])

// 回复 ok

conn.Write([]byte("ok"))

// 接收文件内容

Recv(fileName, conn)

}

5、使用 Socket 实现并发聊天室服务器

可以使用 NetCat 工具调试 Socket 连接

设计思路:

map: 保存在线用户

type Client struct {

c chan string

Name string

Addr string

}

var onlineMap[string] Client

主协程:

处理用户连接

将用户加入 map

告诉所有在线用户,谁上线了

message

go 新开一个协程:

for {

msg :=

// 遍历 map,看有多少个成员

for _, cli := range onlineMap {

cli.C

}

}

go 专门发送信息:

for msg := range cli.C {

write(msg)

}

go 专门接收用户的请求,把用户发过来的数据转发。用户发送过来的数据是 buf

mssage

对方下线,把当前用户从 map 中移除

具体实现:

main.go

package main

import (

"fmt"

"net"

"time"

)

type Client struct {

C chan string // 用于发送数据的管道

Name string // 用户名

Addr string // 网络地址

}

// 保存在线用户 cliAddr ===> Client

var onlineMap map[string]Client

// 消息

var massage = make(chan string)

// 新开一个协程,转发消息,只要有消息来了,就遍历 map,给 map 的每个成员发送消息

func Manager() {

for {

// 给 map 分配空间

onlineMap = make(map[string]Client)

msg :=

// 遍历 map, 给 map 每个成员发送消息

for _, cli range onlineMap {

cli.C

}

}

}

// 给当前客户端发送信息

func WriteMsgToClient(cli Client, conn net.Conn) {

for msg := range cli.C {

conn.Write([]byte(msg + "\n"))

}

}

// 生成 msg

func MakeMsg(cli Client, msg string) (buf string) {

buf = "[" + cli.Addr + "]" + cli.Name + msg

}

// 处理用户连接

func HandleConn(conn net.Conn) {

// 获取客户端的网络地址

cliAddr := conn.RemoteAddr().String()

// 创建一个结构体, 默认,用户名和网络地址一样

cli := Client{make(chan string), cliAddr, cliAddr}

// 把结构体添加到 map

onlineMap[cliAddr] = cli

// 新开一个协程,专门给当前客户端发送信息

go WriteMsgToClient(cli, conn)

// 提示,我是谁

cli.C

// 广播某个人在线

message

message

isQuit := make(chan bool) // 对方是否主动退出

hasData := make(chan bool) // 对方是否有数据发送

// 新建一个协程,接收用户发送过来的数据

go func() {

for {

buf := make([]byte, 2048)

n, err := conn.Read(buf)

if err != nil {

// print err

continue

}

// 对方断开或者出问题

if n == 0 {

isQuit

// print err

return

}

// 转发此内容

msg := string(buf[:n])

if len(msg) == 3 && msg == "who" {

// who 消息,查询在线用户

conn.Write([]byte("user list:\n"))

for _, tmp := range onlineMap {

msg = tmp.Affr + ":" + tmp.Name + "\n"

conn.Write([]byte(msg))

}

} else if len(msg) >= 8 && msg[:6] == "rename" {

// rename 消息

name = strings.Split(msg, "|")[1]

cli.Name = name

onlineMap[cliAddr] = cli

conn.Write([]byte("rename ok \n"))

} else {

// 普通消息,直接转发

message

}

// 表示有数据

hasData

}

}()

for {

// 通过 select 检测 channel 的流动

select {

case

// 当前用户从 map 移除

delete(onlineMap, cliAddr)

// 广播谁下线了

message

return

case

// 有数据,不处理

case

// 60s 之后超时处理

delete(onlineMap, cliAddr)

// 广播一下谁下线了

message

return

}

}

}

func main() {

// 监听

listener, err := net.Listen("tcp", ":8000")

if err != nil {

// print err

return

}

defer listener.Close()

// 新开一个协程,转发消息,只要有消息来了,就遍历 map,给 map 的每个成员发送消息

go Manager()

// 主协程,循环阻塞等待用户连接

for {

conn, err := listener.Accept()

if err != nil {

// print err

continue

}

go HandleConn(conn) // 处理用户连接

}

}

HTTP 编程

1、Web 的工作方式

Web 服务器的工作原理可以简单地归纳为:

客户端通过 TCP/IP 协议建立到服务器的 TCP 连接

客户端向服务器发送 HTTP 协议请求包,请求服务器里的资源文档

服务器向客户端发送 HTTP 协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理 “动态内容”,并将处理得到的数据返回给客户端

客户端与服务器断开。由客户端解释 HTML 文档,在客户端屏幕上渲染图形结果

bbe581a44ffc

2、HTTP 协议

HyperText Transfer Protocol, 超文本传输协议,详细规定了浏览器和万维网之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。

HTTP 协议通常承载于 TCP 协议之上,有事也承载于 TLS 或 SSL 协议层之上,这个时候,就成了我们常说的 HTTPS

bbe581a44ffc

URL(Unique Resource Location) ,用来表示网络资源,可以理解为网络文件路径

3、常见请求方式

GET: 获取资源,不适合上传数据,参数显示在浏览器地址栏,保密性差

POST:用于提交数据,长度没有限制,数据放在请求正文中

4、请求和响应报文格式

请求格式:

#GET/ HTTP/1.1 // 请求行

Host: 127.0.0.1:8000 // 请求头

Connectin: keep-alive

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0(Windows NT 12.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) 6

Accept: text/html. application/xhtml+xml, application/xml; q=0.9, image/webp, */*; q=0.8

Accept-Encoding:gzip, defalte, sdch, br

Accept-Language:zh-CN, zh; q=0.8

// 空行

# // 包体

常见请求头:

User-Agent: 请求浏览器类型

Accept: 可是识别响应内容列表

Accept-Language: 可接收自然语言

Accept-Encoding: 可接收的编码压缩格式

Accept-Charset: 可接收的应答的字符集

Host: 请求的主机名,允许多个域名同处一个 IP 地址,即虚拟主机

connection: 连接方式:close / keepalive

Cookie: 存储于客户端拓展字段,向同一域名的服务器发送属于该域名的 cookie

响应报文:

#HTTP/1.1 200 OK // 状态头

Date: SUb, 28 Jan 2018 06:11:59 GMT // 状态行

Content-Length: 12

Content-Type: text/plain; charset=utf-8

hello world

常见状态码:

- 200 OK :客户端请求成功

- 400 Bad Request:请求报文有语法错误

- 401 Unauthorized:未授权访问

- 403 Forbidden: 服务器拒绝服务

- 404 Nof Found: 资源不存在

- 500 Internal Server Error: 服务器内部错误

- 503 Server Unavailable: 服务器临时不能处理客户端请求

http 服务器响应示例:

package main

import (

"fmt"

"net/http"

)

// 服务器编写的业务逻辑处理程序

func myHandler(w http.ResponseWriter, r *http.Request) {

fmt.Fprintln(w, "hello world")

}

func main() {

http.HandleFunc("/go", myHandler)

// 在指定的地址进行监听,开启一个 HTTP

http.LisenAndServe("127.0.0.1:8000", nil)

}

5、HTTP 服务器编程

直接用 HTTP 内置的包实现 HTTP 服务器编程:

package main

import (

"fmt"

"net/http"

)

// w, 给客户端回复数据

// r, 读取客户端发送的数据

func HandConn(w http.ResponseWriter, r *Request) {

// r.URL 请求链接

// r.Method 请求的方式

// r.Body 请求体

// r.RomoteAddr 请求的 IP 地址

w.Write([]byte("hello go")) // 给客户端回复数据

}

func main() {

// 注册处理函数,用户联机额,自动调用指定的处理函数

http.HandleFunc("/", HandConn)

// 监听绑定

http:ListenAndServe(":8000", nil)

}

6、HTTP 客户端编程

package main

import (

"fmt"

"net/http"

)

func main() {

resp, err := http.Get("http://www.baidu.com")

if err != nil {

// print err

return

}

defer resp.Body.Close()

// print resp.Status, resp.StatusCode, resp.Header, resp.Body

// body 需要 io 的方式读取

var tmp string

buf := make([]byte, 4 * 1024)

for {

n, err := resp.Body.Read(buf)

if n == 0 {

// print err

break

}

tmp += string

}

// print tmp 网页内容

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值