目录
一、网络概述
1 - 协议
- 协议:一组规则,要求使用协议的双方,必须严格遵守协议内容
- 典型协议
- 传输层:常见协议有TCP/UDP协议
- TCP传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
- UDP用户数据报协议(User Datagram Protocol)是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
- 应用层:常见的协议有HTTP协议,FTP协议
- HTTP超文本传输协议(Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络协议
- FTP文件传输协议(File Transfer Protocol)
- 网络层:常见协议有IP协议、ICMP协议、IGMP协议
- IP协议是因特网互联协议(Internet Protocol)
- ICMP协议是Internet控制报文协议(Internet Control Message Protocol)它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息
- IGMP协议是 Internet 组管理协议(Internet Group Management Protocol),是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间
- 网络接口层:常见协议有ARP协议、RARP协议
- ARP协议是正向地址解析协议(Address Resolution Protocol),通过已知的IP,寻找对应主机的MAC地址
- RARP是反向地址转换协议,通过MAC地址确定IP地址
- 传输层:常见协议有TCP/UDP协议
2 - 网络分层模型
- 网络分层架构:
- 为了减少协议设计的复杂性,大多数网络模型均采用分层的方式来组织。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽
- 越下面的层,越靠近硬件;越上面的层,越靠近用户。至于每一层叫什么名字,对应编程而言不重要
- 业内普遍的分层方式有两种:OSI七层模型 和TCP/IP四层模型
- OSI七层模型:物、数、网、传、会、表、应
- TCP/IP四层模型:链、网、传、应
- 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特
- 数据链路层:定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串口通信中使用到的115200、8、N、1
- 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层
- 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段
- 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)
- 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换
- 应用层:是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务
- 各层对应的协议
- 各层功能
- 链路层:以太网规定,连入网络的所有设备,都必须具有“网卡”接口。数据包必须是从一块网卡,传送到另一块网卡。通过网卡能够使不同的计算机之间连接,从而完成数据通信等功能。网卡的地址——MAC 地址,就是数据包的物理发送地址和物理接收地址
- 网络层:网络层的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做“网络地址”,这是我们平时所说的IP地址。这个IP地址好比我们的手机号码,通过手机号码可以得到用户所在的归属地。网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。网络层协议包含的主要信息是源IP和目的IP。于是,“网络层”出现以后,每台计算机有了两种地址,一种是 MAC 地址,另一种是网络地址。两种地址之间没有任何联系,MAC 地址是绑定在网卡上的,网络地址则是管理员分配的,它们只是随机组合在一起。网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理 MAC 地址
- 传输层:当我们一边聊QQ,一边聊微信,当一个数据包从互联网上发来的时候,我们怎么知道,它是来自QQ的内容,还是来自微信的内容?也就是说,我们还需要一个参数,表示这个数据包到底供哪个程序(进程)使用。这个参数就叫做“端口”(port),它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据。端口特点:a)对于同一个端口,在不同系统中对应着不同的进程;b)对于同一个系统,一个端口只能被一个进程拥有
- 应用层:应用程序收到“传输层”的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。“应用层”的作用,就是规定应用程序的数据格式
- 各层功能总结
- 链路层:采用ARP协议,源码mac—目标mac,ARP协议作用(借助IP获取mac地址)
- 网络层:采用IP协议,源IP—目标IP,IP协议作用(在网络环境中唯一标识一台主机)
- 传输层:采用TCP/UDP协议,port在一台主机上唯一标识一个进程
- 应用层:ftp/http等协议,应用层主要作用(对数据进行封装,解封装)
3 - 数据通信过程
- 数据通信过程
- ①.封装:应用层 —— 传输层 —— 网络层 —— 链路层(没有经过封装的数据,不能在网络环境中传递)
- ②.解封装 : 链路层 —— 网络层 —— 传输层 —— 应用层
- 数据通信过程总结
- ①.mac地址(不需要用户指定)【mac地址获取:(ARP 协议)Ip ——> mac】
- ②.IP 地址 (需要用户指定) —— 确定主机
- ③.port 端口号 (需要用户指定) —— 确定程序
- 不能使用系统占用的默认端口。我们使用5000+ 端口(8080)
- 65535为端口上限
- 两台计算机通过TCP/IP协议通讯的过程如下所示
二、Socket编程
1 - Socket概述
- 什么是Socket:Socket,英文含义是【插座、插孔】,一般称之为套接字,用于描述IP地址和端口。可以实现不同程序间的数据通信;网络通信过程中,socket一定是成对出现的
- Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的
- 套接字通讯原理示意
2 - 网络应用程序设计模式
- C/S模式:传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各自部署客户机和服务器来完成数据通信
- B/S模式:浏览器(Browser)/服务器(Server)模式。只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输
三、TCP通信
1 - TCP通信原理
-
TCP C/S架构
-
通信流程示意图
-
Listen函数原型
func Listen(network, address string) (Listener, error)
network:选用的协议:TCP、UDP, 如:“tcp”或 “udp”
address:IP地址+端口号, 如:“127.0.0.1:8000”或 “:8000”
- Listener 接口
type Listener interface {
Accept() (Conn, error)
Close() error
Addr() Addr
}
- Conn 接口
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
2 - 简单TCP实现
- 简单TCP服务端实现步骤
- ①.建监听socket listener := net.Listen(“TCP”, “IP+port”) IP+port —— 服务器自己的IP 和 port
- ②.启动监听 conn := listener.Accept() conn 用于 通信的 socket
- ③.conn.Read()
- ④.处理使用 数据
- ⑤.conn.Write()
- ⑥.关闭 listener、conn
package main
import (
"fmt"
"net"
)
func main() {
// 指定服务器的通信协议、IP地址、port;创建一个用于监听的socket(listener)
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
defer listener.Close()
fmt.Println("服务器等待客户端建立连接...")
// 阻塞监听客户端连接请求, 成功建立连接,返回用于通信的socket(conn)
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept() err:", err)
return
}
defer conn.Close()
fmt.Println("服务器与客户端成功建立连接!!!")
// 读取客户端发送的数据
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
conn.Write(buf[:n]) // 读多少写多少。原封不动
// 处理数据—— 打印
fmt.Println("服务器读到数据:", string(buf[:n]))
}
- 简单TCP客户端实现步骤
- ①.conn, err := net.Dial(“TCP”, 服务器的IP+port)
- ②.写数据给 服务器 conn.Write()
- ③.读取服务器回发的 数据 conn.Read()
- ④.conn.Close()
package main
import (
"fmt"
"net"
)
func main() {
// 指定 服务器 IP + port 创建 通信套接字。
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
// 主动写数据给服务器
conn.Write([]byte("Are you Ready?"))
buf := make([]byte, 4096)
// 接收服务器回发的数据
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("服务器回发:", string(buf[:n]))
}
- 客户端可以使用命令行来调用
3 - TCP-CS并发服务器
- 服务器并发实现步骤
- ①.创建监听套接字:
listener := net.Listen("tcp", 服务器的IP+port) // tcp 不能大写
- ②.
defer listener.Close()
- ③.for 循环阻塞监听客户端连接事件:
conn := listener.Accept()
- ④.创建go程对应每一个客户端进行数据通信:
go HandlerConnet()
- ⑤.实现
HandlerConnet(conn net.Conn)
- a)
defer conn.Close()
- b)获取成功连接的客户端 Addr:
conn.RemoteAddr()
- c)for 循环 读取 客户端发送数据:
conn.Read(buf)
- d)处理数据 小 —— 大洒洒水
strings.ToUpper()
- e)回写转化后的数据:
conn.Write(buf[:n])
- a)
- ①.创建监听套接字:
- 服务器关闭判断:
n, err := conn.Read(buf)
当n=0时代表客户端已关闭
package main
import (
"fmt"
"net"
"strings"
)
func HandlerConnect(conn net.Conn) {
defer conn.Close()
// 获取连接的客户端 Addr
addr := conn.RemoteAddr()
fmt.Println(addr, "客户端成功连接!")
// 循环读取客户端发送数据
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if "exit\n" == string(buf[:n]) || "exit\r\n" == string(buf[:n]) {
fmt.Println("服务器接收的客户端退出请求,服务器关闭")
return
}
if n == 0 {
fmt.Println("服务器检测到客户端已关闭,断开连接!!!")
return
}
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("服务器读到数据:", string(buf[:n])) // 使用数据
// 小写转大写,回发给客户端
conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
}
}
func main() {
// 创建监听套接字
listener, err := net.Listen("tcp", "127.0.0.1:8001")
if err != nil {
fmt.Println("net.Listen err:", err)
return
}
defer listener.Close()
// 监听客户端连接请求
for {
fmt.Println("服务器等待客户端连接...")
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err:", err)
return
}
// 具体完成服务器和客户端的数据通信
go HandlerConnect(conn)
}
}
4 - TCP-CS并发客户端
- 客户端并发实现步骤
- 匿名go程,获取 键盘输入,写给服务器
- for循环读取服务器回发数据(发送数据时,默认在结尾自带‘\r\n’)
package main
import (
"fmt"
"net"
"os"
)
func main() {
// 主动发起连接请求
conn, err := net.Dial("tcp", "127.0.0.1:8001")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
// 获取用户键盘输入( stdin ),将输入数据发送给服务器
go func() {
str := make([]byte, 4096)
for {
n, err := os.Stdin.Read(str)
if err != nil {
fmt.Println("os.Stdin.Read err:", err)
continue
}
//写给服务器, 读多少,写多少!
conn.Write(str[:n])
}
}()
// 回显服务器回发的大写数据
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
fmt.Println("检查到服务器关闭,客户端也关闭")
return
}
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
fmt.Println("客户端读到服务器回发:", string(buf[:n]))
}
}
- 127.0.0.1与192.168.x.x的IP访问区别
- 使用127.0.0.1,相当于从进程将数据发送到网卡,再从网卡发送到另外一个进程
- 使用192.168.x.x,相当于从进程将数据发送到网卡,再从网卡发送到路由器,再通过路由器发送到网卡,再从网卡发送到另外一个进程
四、TCP通信过程
- TCP通讯时序图:在图中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序。注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的
1 - 三次握手(连接建立)
- 三次握手过程
- ①.客户端发送一个带SYN标志的TCP报文到服务器。这是上图中三次握手过程中的段1。客户端发出SYN位表示连接请求。序号是1000,这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况。另外,规定SYN位和FIN位也要占一个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该用序号1001。mss表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大长度,就必须在IP层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度
- ②.服务器端回应客户端,是三次握手中的第2个报文段,同时带ACK标志和SYN标志。表示对刚才客户端SYN的回应;同时又发送SYN给客户端,询问客户端是否准备好进行数据通讯。服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。
- ③.客户必须再次回应服务器端一个ACK报文,这是报文段3。客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出。
2 - 数据传输
- 数据传输的过程
- ①.客户端发出段4,包含从序号1001开始的20个字节数据。
- ②.服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据。
- ③.客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序号8011开始的数据。
- 注意:在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发
2 - 四次挥手(连接关闭)
- 四次挥手概念:所谓四次挥手(Four-Way-Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务器任一方执行close来触发
- 四次挥手过程:由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭
- ①.客户端发出段7,FIN位表示关闭连接的请求
- ②.服务器发出段8,应答客户端的关闭连接请求
- ③.服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求
- ④.客户端发出段10,应答服务器的关闭连接请求
- 为什么建立连接是3次握手,关闭连接是4次挥手:建立连接的过程是三次握手,而关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止