20小时快速入门go语言视频 - Day7

笔记声明

本人智商较低,好记性不如烂笔头。笔记记录了网上一套Golang免费视频教程的知识点,以供未来自己翻阅查看。不写源视频的名称和网站地址了,免得被说是广告!




一、网络概述

1.1 网络协议

从应用的角度出发,协议可以理解为“规则”,是数据传输和数据的解释的规则。
定义一种规则,通信的双方都遵循。

1.2 分层模型

1.2.1 网络分层架构

为了减少协议涉及的复杂性,大多数网络模型均采用分层的方式来组织,每一层都有自己的功能。(就像建筑物一样,每一层都靠下面一层支持)。每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽

1.2.1.1 网络分层架构示意图

7 层架构、4 层架构。本质上是一样的,7 层更加细分化了,4 层更加容易理解。
在这里插入图片描述
在这里插入图片描述
越下面的层,越靠近硬件。越上面的层,越靠近用户。

1.2.1.2 7层架构的大概描述
  • 物理层:主要定义物理设备标准,如网线(光纤)的接口类型,各种传输介质的传输功率等。它的主要作用是传输比特流(由 0、1 转化为电流强弱来进行传输,到达目的地后再转化为 0、1,也就是常说的数模转换与模数转换)。这一层的数据叫做比特。
  • 数据链路层:定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串口通信中使用的 115200、8、N、1
  • 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet 的发展使得从世界各站点访问信息的用户数大大增加,网络层正是管理这种连接的层。
  • 传输层:定义了一些传输数据的协议和端口号。如:TCP 传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高、数据量大的数据。UDP 用户数据报协议,与 TCP 特性刚好相反,用于传输可靠性要求不高、数据量小的数据,QQ 就是使用了 UDP 协议。
    传输层主要是将从下层接收的数据进行分段和传输,到达目的地后再进行重组。常常把这一层叫做段。
  • 会话层:通过传输层建立数据传输的通道。主要在你的系统之间发起会话或接受会话请求(设备之间需要互相认识,可以是 IP,也可以是 MAC 或者是主机名)。
  • 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如:PC 程序与另一台计算机进行通信,其中一台计算机使用扩展二十一进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通用格式来实现多种数据格式之间的转换。
  • 应用层:最靠近用户的 OSI 层。这一层为用户的应用程序(例如:电子邮箱、文件传输和终端仿真)提供网络服务。
1.2.1.3 4层架构的大概描述
  • 链路层:主要处理 MAC 地址。
  • 网络层:主要处理 IP 信息。
  • 传输层:主要处理端口。
  • 应用层:应用层的协议(处理数据的方式)。
1.2.2 层与协议
1.2.2.1 概述

每一层都是为了完成一种功能,为了实现这些功能,就需要大家都遵守共同的规则。大家都遵守这规则,就叫做“协议”(protocol)。
网络的每一层定义了很多协议。这些协议的总称叫:TCP/IP 协议TCP/IP 协议是一个大家族,不仅仅只有 TCP 和 IP,它还包括其他协议。如下图所示:
在这里插入图片描述

  • ARP:正向地址解析协议(Address Resolution Protocol),通过已知的 IP,寻找对应主机的 MAC 地址。
  • RARP:反向地址转换协议,通过 MAC 地址确定 IP 地址。
  • IP:因特网互联协议(Internet Protocol)。
  • ICMP:Internet 控制报文协议(Internet Control Message Protocol),用于在 IP 主机、路由器之间传递控制消息。
  • IGMP:Internet 组管理协议(Internet Group Management Protocol),该协议运行在主机和组播路由器之间。
  • TCP:传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议。
  • UDP:用户数据报协议(User Datagram Protocol),是一种无连接的传输层协议,提供面向事务的简单不可靠信息传递服务。
1.2.2.2 拿一个生活中的例子来解释层与协议的关系

比如生产汽车,假设有底盘车间、轮胎车间、车顶车间。每个车间就相当于一个层,底盘生产好了,轮胎才能往上安,轮胎安好了,才能上车顶。每一车间需要下一个车间的支持来为上一车间提供材料,每个车间各司其职,无需知道其他车间的事情。那么给个车间都有自己的规章制度,底盘有底盘的生产规则、轮胎有轮胎的生产规则……这个就是层与协议的关系。

1.2.3 每层协议的功能

在这里插入图片描述

1.2.3.1 链路层

链路层是设备到设备(网卡到网卡)。以太网规定,连入网络的所有设备,都必须具有“网卡”接口。数据包必须是从一块网卡,传送到另一块网卡。(有网卡可以通信了)。通过网卡能够使不同的计算机之间连接,从而完成数据通信等功能。网卡的物理地址(MAC 地址),就是数据包的物理发送地址和物理接收地址。(理论上,MAC 地址全球唯一,实际上是可以修改的)

1.2.3.2 网络层

只有链路层,直接找 MAC 地址,就会导致广播风暴。比如你要跟另一台计算机通信,那么就必须要先去找到那台计算机的 MAC 地址。你往网络上发出一个广播:“哪台电脑是 A9-7E-45-A1-7F-B9”。现在是地球村,全球联网,那么这条广播就会发给全球所有联网的计算机。你在广播的时候,其他人也在广播,全球那么多台计算机,都在广播都在 say hi,完蛋了,什么都不用干了。
那么将一定区域内的 MAC 地址分段,然后给与一个新的网络地址(IP 地址)。广播的话,只对该段内的计算机发出广播,不会造成广播风暴。
网络层是主机到主机(IP 到 IP)。网络层的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做“网络地址”,也就是平时所说的“IP 地址”。
IP 地址好比我们的手机号码,通过手机号码能够找到用户所在的归属地。
网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到子网络中的目标网卡。网络层协议包含的主要信息是源 IP 和目的 IP。
MAC:物理地址。IP:逻辑地址。链路层中,有个 ARP 协议,就是通过 IP 找 MAC。

1.2.3.3 传输层

传输层是进程到进程(端口到端口)。传输层主要包含了源端口和目的端口。通过 IP 找到了 MAC 地址,也就找到了那台计算机。但是那台计算机上运行着很多个程序,怎么知道要把数据给哪个程序?这里就需要端口号。
端口号其实就是每一个使用网卡的程序的编号。假设 QQ 的端口号是 1234,微信的端口号是 5678。对方的计算机想把一串文字发送到我计算机上的 QQ 上,那么对方在发送的时候,只需要指定把信息发送到 1234 端口即可。

1.2.3.4 应用层

应用程序收到“传输层”的数据,接下来就要进行解读。互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。应用层的作用,就是规定应用程序的数据格式。

1.3 网络通信的条件

1.3.1 最基本的必要条件

除非是单机,需要网络通信,以下 3 个条件必须满足。

1.3.1.1 网卡,物理 MAC 地址

不需要用户处理,网卡中有个 ARP 协议,通过 IP 找 MAC。所以,MAC 不需要去动它。

1.3.1.2 逻辑地址,IP 地址

需用用户指定,因为是通过 IP 去找 MAC。为了确定哪台电脑接收。

1.3.1.3 端口

计算机上运行着很多程序,不指定的话怎么知道要把数据给哪个程序。为了确定哪个程序接收。
注意事项:
1.同一系统,一个程序只能绑定一个端口,必须保持唯一性。
2.不同系统,同一端口对应的程序可能不一样。比如:一台电脑上,1234 端口绑定的是 QQ,而另一台电脑上的 1234 端口可能绑定的是微信。

1.4 网络通信过程的组包和拆包

就像发收快递那样,卖家把快递包裹好给了快递小哥,小哥把快递送到当地的快递公司,快递公司把快递装进运输工具发出去,(一层层包裹的操作)。快递到了目的地,目的快递公司把包裹下达给小哥,小哥再把快递送到你手中,你拿到了快递并将它拆开,(一层层拆的操作)。
网络通信的流程也是一样的原理:
在这里插入图片描述


二、Socket 编程(套接字编程)

2.1 Socket 的基本概念

Socket 起源于 Unix,Unix 基本哲学之一就是“一切皆文件”,都可以用“打开 open -> 读写 read/write -> 关闭 close”模式来操作。Socket 就是该模式的一个实现,网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用 Socket(),该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。
常用的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用。数据报式 Socket 是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
Socket 编程就是网络编程,实际上就是网络通信的接口,Golang 底层都已经做好了。Socket 就是一种文件描述符,调包侠只需要像操作文件那样去关心如何 send/receive、write/read,直接调用函数、方法即可实现网络编程。
TCP 就类似打电话。(我方)第一句:喂,你是XXX不?(对方)第二句:是的。(我方)第三方:哦,事情是这样的……。也就是三次握手。
UDP 就类似发端性。短信发出去了就行了,不管对方是谁、有收没收到。

2.2 TCP 的 C/S 架构

在这里插入图片描述

2.3 Socket 编程 - 最基本的示例

最基本的示例只接收单客户端连接、处理单次任务。

2.3.1 服务器代码
import (
	"fmt"
	"net"
)

func main() {
	//Listen,开始监听
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		panic(err)
	}
	defer listener.Close() //关闭监听

	//Accept,阻塞在此,等待客户端连接进来
	conn, err := listener.Accept()
	if err != nil {
		panic(err)
	}
	conn.Close() //关闭连接

	//read,接收发送过来的数据
	buf := make([]byte, 1024) //指定缓冲区大小 1KB
	n, err := conn.Read(buf)  //n 表示读到了多少个数据
	if err != nil {
		panic(err)
	}

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

在这里插入图片描述

2.3.2 客户端代码
import (
	"fmt"
	"net"
)

func main() {
	//主动连接服务器
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		panic(err)
	}
	defer conn.Close() //关闭连接

	//发送数据
	buf := []byte("are you ok?")
	n, err := conn.Write(buf)
	if err != nil {
		panic(err)
	}

	fmt.Printf("written %d bytes to server.", n)
}

在这里插入图片描述

2.3.3 启动顺序

先启动服务器端,再启动客户端。如果服务器都没有启动,让客户端如何去连接到目标服务器?
在这里插入图片描述

2.4 Socket 编程 - 支持多个客户端连接

2.4.1 目录结构

在这里插入图片描述

2.4.2 服务器代码
//处理客户端的请求
func ServerHandle(conn net.Conn) {
	//函数结束必须断开连接
	defer conn.Close()

	addr := conn.RemoteAddr().String() //获取客户端 IP+Port 的地址,并转换为字符串
	fmt.Printf("client, [%s] connected success.\n", addr)

	for {
		buf := make([]byte, 2048) //指定缓冲区大小,2KB
		n, err := conn.Read(buf)  //Read,处理客户端发送过来的数据
		if err != nil {
			panic(err)
		}
		content := string(buf[:n])
		fmt.Printf("client, [%s] sent content: %v\n", addr, content)

		if content == "exit" { //遇到客户端输入了 "exit",便关闭该客户端的连接
			fmt.Printf("client, [%s] exit.\n", addr)
			return
		}

		conn.Write([]byte(strings.ToUpper(content))) //将新的内容响应给客户端
	}
}

func main() {
	//Listen
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		panic(err)
	}
	defer listener.Close()

	//Accept,可以接收多个客户端的连接
	for {
		conn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		defer conn.Close()

		//处理客户端的请求
		//给每个新的客户端开一个新的协程
		go ServerHandle(conn) //每次新连进来的 conn 都是不一样的,所以都要把新的 conn 传过去
	}
}
2.4.3 客户端代码
func main() {
	//主动连接服务器
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		panic(err)
	}
	defer conn.Close() //关闭连接

	//发送数据
	for {
		content := ""
		fmt.Scan(&content)

		buf := []byte(content)
		n, err := conn.Write(buf) //往服务端发送数据,就是写的操作
		if err != nil {
			panic(err)
		}

		content = string(buf[:n])
		if content == "exit" { //如果键入了 "exit",就退出程序
			fmt.Println("has entered 'exit', my client has exit.")
			os.Exit(0)
		}

		//服务端将新的内容响应回来时,客户端就是读的操作了
		n, err = conn.Read(buf)
		if err != nil {
			panic(err)
		}

		fmt.Printf("server, [%s] responsed:%s\n", conn.RemoteAddr().String(), string(buf[:n]))
	}
}
2.4.4 运行效果

先启动服务端,再启动客户端。
在这里插入图片描述


三、文件传输

3.1 文件传输的原理

1.客户端向服务器发送文件名,此时并不会发送文件内容,只是发过去文件的名称。
2.服务器接收到了这个文件名,将其保存起来,并响应给客户端,告知收到了这个文件名。
3.客户端得知服务器收到了这个文件名,就开始真正地把文件内容发送给服务器。(读一点发一点,具体读多少是看切片定义的大小)
4.服务器收到文件内容,把内容写入到文件中。
在这里插入图片描述

3.2 os.Stat()

os.Stat(filePath string),获取指定路径下的文件的信息。
简单示例:

import (
	"fmt"
	"os"
)

func main() {
	cmds := os.Args

	if len(cmds) != 2 {
		fmt.Println("usage:filename.xxx")
		return
	}

	fileName := os.Args[1]

	fi, err := os.Stat(fileName)
	if err != nil {
		fmt.Println("os.Stat, error:", err)
		return
	}

	fmt.Println("file name:", fi.Name())
	fmt.Println("file size:", fi.Size())
}

运行效果:
在这里插入图片描述
在这里插入图片描述

3.3 实现文件传输功能

3.3.1 目录结构

在这里插入图片描述

3.3.2 服务器端代码实现
import (
	"fmt"
	"io"
	"net"
	"os"
)

func SaveFileContent(fileName string, conn net.Conn) {
	buf := make([]byte, 10240)

	fw, err := os.Create(fileName) //创建文件,如果已存在就会覆盖
	if err != nil {
		fmt.Println("os.Create, error:", err)
		return
	}

	//不停地循环从客户端读取文件内容
	for {
		n, err := conn.Read(buf) //从客户端发过来的文件内容
		if err != nil {
			if err == io.EOF { //如果到了文件末尾,说明文件已全部成功读取完毕
				fmt.Println("文件接收完毕!")
				return
			} else {
				fmt.Println("conn.Read, error:", err)
			}
		}

		fw.Write(buf[:n]) //读到多少,写多少
	}
}

func main() {
	//开始监听
	listener, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Print("net.Listen, error:", err)
		return
	}
	defer listener.Close() //关闭监听器

	//阻塞等待客户端的连接
	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("listener.Accept, error:", err)
		return
	}
	defer conn.Close() //关闭连接

	//接收文件名,并响应"ok"
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		fmt.Println("conn.Read, error", err)
		return
	}

	fileName := string(buf[:n]) //得到路径+文件名

	_, err = conn.Write([]byte("ok")) //响应"ok",这个内容
	if err != nil {
		fmt.Println("conn.Write, error:", err)
		return
	}

	//获取多少内容,就保存多少内容
	SaveFileContent(fileName, conn)
}
3.3.3 客户端代码实现
import (
	"fmt"
	"io"
	"net"
	"os"
)

func TransportFileConntent(path string, conn net.Conn) {
	defer conn.Close() //文件传输完毕,函数结束,就要关闭连接

	buf := make([]byte, 10240) //10KB大小的缓存区

	f, err := os.Open(path) //打开文件
	if err != nil {
		fmt.Println("os.Open, error:", err)
		return
	}

	//不停地读取文件中的内容。除非 buf 大小与文件大小一致,否则就是只读取了一次,文件就会不完整
	for {
		n, err := f.Read(buf)
		if err != nil {
			if err == io.EOF { //如果已经到了文件的末尾,说明文件内容全部都已读取完毕
				fmt.Println("文件已发送完毕!")
				return //已读取完毕,结束函数
			} else {
				fmt.Println("f.Read, error:", err)
			}
		}

		//读到多少内容,就往服务器发送多少内容
		_, err = conn.Write(buf[:n])
		if err != nil {
			fmt.Println("conn.Write, error:", err)
		}
	}
}

func main() {
	//提示输入文件名
	fmt.Print("请输入文件名:")
	var path string
	fmt.Scan(&path)

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

	//连接到服务器
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		fmt.Println("net.Dial, error:", err)
	}
	defer conn.Close() //关闭连接

	//将文件名发送给服务器
	_, err = conn.Write([]byte(fi.Name()))
	if err != nil {
		fmt.Println("conn.Write, error:", err)
	}

	//等待服务器的响应,如果响应内容为"ok",说明服务器已准备好,可以开始发送真正的文件内容了
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		fmt.Println("conn.Read, error:", err)
		return
	}

	if string(buf[:n]) == "ok" {
		//读取文件内容,将内容发送给服务器,读多少发送多少
		TransportFileConntent(path, conn)
	} else {
		fmt.Println("server is not ok.")
	}
}
3.3.4 运行效果

先启动服务器端,再启动客户端。
在这里插入图片描述
最终,两个路径下的文件的大小一致,都能正常播放。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值