Go语言网络编程

分层模型

每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽

层与协议

链路层

源MAC,目的MAC

以太网规定,连入网络的所有设备,都必须具有“网卡”接口,网卡的地址是MAC地址。设备到设备

  1. ARP:地址解析协议,通过已知IP,寻找对应主机MAC地址
  2. RARP:地址转换协议,通过MAC地址确定IP地址

网络层

源IP,目的IP

  1. IP:因特网互联协议
  2. ICMP: Internet控制报文协议,用于在IP主机、路由器之间传递控制消息
  3. IGMP: Internet组管理协议,运行主机和组播路由器之间

传输层

源端口,目的端口(网络程序的一个编号)

  1. TCP: 传输控制协议,面向连接的、可靠的、基于字节流的传输层通信协议
  2. UDP:用户数据报协议,无连接的传输层协议,提供面向事务的简单不可靠信息传送服务

应用层

提取有用信息,关键信息,先规定好格式,再进行读取

  1. FTP:TCP分化,文件传输协议
  2. Telnet: TCP分化
  3. TFTP:UDP分化
  4. NFS:UDP分化

网络通信条件

  1. 网卡,mac地址(不需要用户处理)
  2. 逻辑地址,ip地址(需要用户指定)
  3. 端口,找应用程序

Socket编程

C/S模型

就是客户服务器模型,客户端主动请求服务,服务器被动提供服务

1616398161(1)

简单并发服务器

TCP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func HandleConn(conn net.Conn)  {
defer conn.Close()

//获取客户端的网络地址信息
conn.RemoteAddr().String()
addr := conn.RemoteAddr().String()
fmt.Printf("[%s]:连接成功\n",addr)

for {
//读取用户数据
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("读取信息err = ", err)
return
}
fmt.Printf("[%s]:%s\n", addr, string(buf[:n-1]))
if string(buf[:n-1]) == "exit"{ //输入的换行符也计算了,要剔除
fmt.Println(addr,"退出了")
return
}
//把数据转化为大写,再给用户发送
_, err = conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
if err != nil {
fmt.Println("信息转换err = ", err)
return
}
}
}

func main() {
//监听
listener,err:=net.Listen("tcp","127.0.0.1:8000")
if err!=nil{
fmt.Println("监听err = ",err)
return
}
defer listener.Close()
//阻塞,等待用户链接

//接收多个用户
for {
conn , err:=listener.Accept()
if err!=nil{
fmt.Println("接收信息err = ",err)
return
}

//处理用户请求
go HandleConn(conn)//每来一个用户就新建一个协程
}
}

TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func main() {
//主动连接服务器
conn,err:=net.Dial("tcp","127.0.0.1:8000")
if err!=nil{
fmt.Println("连接服务器err = ",err)
return
}

//main函数调用完毕关闭连接
defer conn.Close()

str := make([]byte,1024)
//接收服务器回复的数据
go func() {
//从键盘输入内容给服务器发送内容
for{
n,err := os.Stdin.Read(str)//从键盘读取内容,放在str
if err!=nil{
fmt.Println("信息输入err = ",err)
return
}

//把输入的内容给服务器发送
_,err = conn.Write(str[:n])
if err!=nil{
fmt.Println("发送信息err = ",err)
return
}
}
}()
//定义一个切片缓存
buf:=make([]byte,1024)
for {
n,err:=conn.Read(buf)
if err!=nil{
fmt.Println("接收信息错误或退出了err = ",err)
return
}
//接收服务器的请求
fmt.Println(string(buf[:n-1])) //打印接收到的内容
}
}

TCP文件传输

发送方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
func SendFile(path string , conn net.Conn)  {
//以只读方式打开文件
f , err := os.Open(path)
if err!=nil{
fmt.Println("os.Open err = ",err)
return
}
defer f.Close()

//读文件内容,读多少就给接受方发多少
buf := make([]byte , 1024*4)
for{
n,err:=f.Read(buf)
if err!=nil{
if err == io.EOF{
fmt.Println("文件发送完毕")
}else {
fmt.Println("f.Read err = ",err)
}
return
}
//发送内容
conn.Write(buf[:n])
}
}

func main() {
//提示输入文件
fmt.Printf("请输入需要传输的文件:")
var path string
fmt.Scan(&path)

//获取文件名
info,err := os.Stat(path)
if err!=nil{
fmt.Println("os.Stat err = ",err)
return
}

//主动连接服务器
conn,err:=net.Dial("tcp","127.0.0.1:8000")
if err!=nil{
fmt.Println("net.Dial err = ",err)
return
}
defer conn.Close()

//给接收方先发送文件名
_ ,err = conn.Write([]byte(info.Name()))
if err!=nil{
fmt.Println("conn.Write err = ",err)
return
}

//接收对方的回复,如果回复ok,说明对方准备好接收文件
buf := make([]byte , 1024)
n , err := conn.Read(buf)
if err!=nil{
fmt.Println("conn.Read err = ",err)
return
}
if string(buf[:n]) == "ok"{
//发送文件内容
SendFile(path,conn)
}
}

接收方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
func RecvFile(fileName string , conn net.Conn)  {
//新建文件
f , err := os.Create(fileName)
if err!=nil{
fmt.Println("os.Create err = ",err)
return
}

//接收多少就写多少
buf := make([]byte , 1024*4)
for{
//接收对方发送过来的文件内容
n,err := conn.Read(buf)
if err != nil{
if err == io.EOF{
fmt.Println("文件接收完毕")
}else {
fmt.Println("conn.Read err = ", err)
}
return
}
//往文件写入内容
f.Write(buf[:n])
}
}

func main() {
//监听
listener , err := net.Listen("tcp","127.0.0.1:8000")
if err!=nil{
fmt.Println("net.Listen err = ",err)
return
}
defer listener.Close()

//阻塞等待
conn,err := listener.Accept()
if err!=nil{
fmt.Println("listener.Accept err = ",err)
return
}
defer conn.Close()

buf := make([]byte , 1024)
n , err := conn.Read(buf) //读取对方发送的文件名
if err!=nil{
fmt.Println("conn.Read err = ",err)
return
}
fileName := string(buf[:n])
//给对方回复ok
conn.Write([]byte("ok"))

//接收文件内容
RecvFile(fileName,conn)

}

并发聊天服务器

分析:

  1. map保存在线用户,key对应IP和端口,value对应结构体,包括通道、用户名、地址
  2. 主协程处理用户连接
    1. 将用户加入到map
    2. 告诉所有在线的用户谁上线了
  3. 新开协程传输信息
    1. 接收用户的请求,把用户发送过来的数据转发
    2. 把发送过来的数据添加到通道
    3. 用户下线,移除map中的用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
type Client struct {
C chan string //用于发送数据的管道
Name string //用户名
Addr string //网络地址
}

//全局变量,保存在线用户
var onlineMap = make(map[string]Client)

var message = make(chan string)

func Manager(){
//给map分配空间
onlineMap = make(map[string]Client)
//无消息会阻塞
for{
msg := <-message
//遍历map
for _,cli:=range onlineMap{
cli.C <- msg
}
}
}

func WriteMsgToClient(cli Client , conn net.Conn) {
for msg :=range cli.C{
_,err:=conn.Write([]byte(msg + "\n"))
if err!=nil {
fmt.Println("conn.Write err = ", err)
return
}
}
}

func MakeMsg(cli Client,msg string) (buf string) {
buf = "[" + cli.Addr + "]" + cli.Name + ": " + msg

return
}

func HandleConn(conn net.Conn) {
defer conn.Close()
//获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
//创建一个结构体
cli := Client{
make(chan string),
cliAddr,
cliAddr,
}
//把结构体添加到map
onlineMap[cliAddr] = cli

//新开一个协程,专门给当前客户端发送信息
go WriteMsgToClient(cli,conn)
//广播某用户在线
message <- MakeMsg(cli,"online")

isQuit := make(chan bool)//对方是否主动退出
hasData := make(chan bool)//对方是否有数据发送

//新开一个协程,接收用户发送过来的数据
go func() {
buf := make([]byte,2048)
for {

n, err := conn.Read(buf)
if n == 0 { //对方断开
isQuit <- true
fmt.Printf(conn.RemoteAddr().String()+"断开连接\n")
return
}
if err != nil {
fmt.Println("conn.Read err = ", err)
return
}
msg := buf[:n-1]

//查看当前在线人数
if len(msg) == 3 && string(msg) == "who"{
//遍历map,给当前用户发送所有成员
conn.Write([]byte("user list:\n"))
for _,tmp := range onlineMap{
msg = []byte(tmp.Addr + ":" + tmp.Name + "\n")
conn.Write(msg)
}
}else if len(msg) >= 8 && string(msg[0:7]) == "rename|"{
name :=strings.Split(string(msg) , "|")[1]
cli.Name = name
onlineMap[cliAddr] = cli
conn.Write([]byte("rename success\n"))
}else {
//转发此内容
message <- MakeMsg(cli, string(msg))
}
hasData <- true //有数据
}

}()
for{
//通过select检测channel的流动
select {
case <- isQuit:
delete(onlineMap,cliAddr)
message <- MakeMsg(cli,"offline")
return
case <- hasData:
case <- time.After(10*time.Second)://60s超时时间
delete(onlineMap,cliAddr)
message <- MakeMsg(cli,"time out")
fmt.Printf(conn.RemoteAddr().String()+"超时断开\n")
return
}
}
}

func main() {
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err = ", err)
return
}
defer listener.Close()

//新开一个协程,转发消息,只要有消息来了,遍历map,给每个map成员都发送此消息
go Manager()
//主协程,循环阻塞等待用户连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err = ", err)
//不能用return 因为return会直接退出,而一个用户出现错误不代表后面的用户都出现错误
continue
}
//处理用户连接
go HandleConn(conn)

}
}

HTTP编程

Web工作方式

  • 客户机通过TCP/IP协议建立到服务器的TCP连接
  • 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
  • 服务器向客户机发送HTTP应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动动态语言的解释引擎负责处理动态内容,并将处理得到的数据返回给客户端
  • 客户机与服务器断开,由客户端解释HTML文档,在客户端屏幕上渲染图形结果

HTTP协议

超文本传输协议,通常承载于TCP协议之上,有时也承载于TLS或者SSL协议层之上,是一种网络协议,它详细规定了浏览器和万维网之间互相通信的规则。

SSL/TLS

安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性。

HTTP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//w给客户端回复数据
//r读取客户端发送的数据
func HandleConn(w http.ResponseWriter , r *http.Request) {
fmt.Println("r.Method : ",r.Method)
fmt.Println("r.URL : " , r.URL)
fmt.Println("r Header : ",r.Header)
//给客户端回复数据
w.Write([]byte("hello world"))
}

func main() {
//注册处理函数,用户连接自动调用指定的处理函数
http.HandleFunc("/" , HandleConn)

//监听绑定
http.ListenAndServe(":8000" , nil)
}

HTTP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
resp , err := http.Get("http://127.0.0.1:8000")
if err!=nil{
fmt.Println("http.Get err : " , err)
return
}

defer resp.Body.Close()

fmt.Println("Status : " , resp.Status)
fmt.Println("Header : " , resp.Header)

buf := make([]byte , 4*1024)
var tmp string
for {
n, err := resp.Body.Read(buf)
if n == 0 {
fmt.Println("读完了或resp.Body.Read err : ", err)
break
}
tmp += string(buf[:n])
}
fmt.Println(tmp)
}

简单爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
func DoWork(start , end int)  {
fmt.Printf("正在爬取%d到%d的页面 .... \n",start , end)

//声明要爬取的网页
for i := start ; i <= end ; i++{
url := "https://tieba.baidu.com/f?kw=%E9%AD%94%E5%85%BD%E4%B8%96%E7%95%8C&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
fmt.Printf("正在爬取第%d页\n",i)

//开始爬取
result , err := HttpGet(url)
if err!=nil {
fmt.Println("HttpGet err : " , err)
continue
}
//把内容写入文件
fileName := "第"+strconv.Itoa(i)+"页"+".html"
f , err := os.Create(fileName)
if err!=nil {
fmt.Println("os.Create err : " , err)
continue
}
f.Write([]byte(result))
f.Close()
}
}

func HttpGet(url string) (result string , err error) {
resp , err := http.Get(url)
if err!=nil{
fmt.Println("http.Get err : " , err)
return
}
defer resp.Body.Close()

//读取Body内容
buf := make([]byte , 1024*4)
for{
n,err := resp.Body.Read(buf)
if n == 0 {
fmt.Println("读完了或resp.Body.Read err : ", err)
break
}
result += string(buf[:n])
}
return result ,err
}

func main() {
var start,end int
fmt.Println("请输入起使页: ")
fmt.Scan(&start)
fmt.Println("请输入终止页: ")
fmt.Scan(&end)

DoWork(start,end)
}

并发爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
func DoWork(start , end int)  {
fmt.Printf("正在爬取%d到%d的页面 .... \n",start , end)
page := make(chan int)


//声明要爬取的网页
for i := start ; i <= end ; i++{
go SpiderPage(i,page)
}

for i := start ; i <= end ; i++{
fmt.Printf("第%d个页面爬取完成" , <-page)
}
}

func SpiderPage(i int , page chan<- int) {
url := "https://tieba.baidu.com/f?kw=%E9%AD%94%E5%85%BD%E4%B8%96%E7%95%8C&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
fmt.Printf("正在爬取第%d页\n",i)

//开始爬取
result , err := HttpGet(url)
if err!=nil {
fmt.Println("HttpGet err : " , err)
return
}
//把内容写入文件
fileName := "第"+strconv.Itoa(i)+"页"+".html"
f , err := os.Create(fileName)
if err!=nil {
fmt.Println("os.Create err : " , err)
return
}
f.Write([]byte(result))
f.Close()

page <- i
}

func HttpGet(url string) (result string , err error) {
resp , err := http.Get(url)
if err!=nil{
fmt.Println("http.Get err : " , err)
return
}
defer resp.Body.Close()

//读取Body内容
buf := make([]byte , 1024*4)
for{
n,_ := resp.Body.Read(buf)
if n == 0 {
//fmt.Println("读完了或resp.Body.Read err : ", err)
break
}
result += string(buf[:n])
}
return result ,err
}

func main() {
var start,end int
fmt.Println("请输入起使页: ")
fmt.Scan(&start)
fmt.Println("请输入终止页: ")
fmt.Scan(&end)

DoWork(start,end)
}