12.网络

13 阅读9分钟

1.1 网络

使用网络能够把多方电脑等设备链接在一起进行数据传递。网络编程就是让在不同的电脑上的软件能够进行数据传递,即进程之间的通信。

1.1.1 网络编程三要素

  • IP:网络中每台计算机的唯一标识,通过 IP 地址可以找到计算机。
  • 端口:标识进程的逻辑地址,通过端口找到计算机中指定的进程(应用软件)。
  • 协议:定义通信规则。

1.1.2 TCP/IP协议族

  1. 通信协议 通信协议是一组用于规定不同设备或计算机之间如何进行数据交换和通信的规则和约定。它定义了通信的各个方面,包括数据的格式、传输的顺序、错误检查机制、如何处理不同情况(如重传丢失的数据包)等。协议的目的是确保在网络中传输的数据能够被正确、可靠地理解和处理。 通信协议可以应用于计算机网络、电话网络、无线通信等领域。在不同的应用场景下,会使用不同的协议来实现数据交换、控制信息传递等任务。
  2. TCP/IP TCP/IP 协议族,简称 TCP/IP,是一组通信协议,用于互联网的数据传输和网络通信,定义了数据如何在不同的计算机之间传输和路由。是现代计算机网络中最常用的网络协议之一。TCP/IP 得名于该协议家族的两个核心协议:TCP(传输控制协议)和 IP(网际协议)。
  3. 分层网络模型 OSI 七层网络模型由国际标准化组织制定,但其实现过于复杂,且制定周期过长,在其整套标准推出之前,TCP/IP 模型已经在全球范围内被广泛使用。TCP/IP 模型定义了应用层、传输层、网络层、网络接口层这四层网络结构,但并没有给出网络接口层的具体内容,因此在学习和开发中,通常将网络接口层替换为 OSI 七层模型中的数据链路层和物理层来进行理解,这就是五层网络模型。
  4. 常见网络协议  (原笔记中有图片或表格占位符,此处省略)

1.2 IP

1.2.1 什么是IP

IP 地址由一串数字组成,用来标识一台电脑在网络中的位置。当设备连接网络,设备将被分配一个 IP 地址,用作标识。通过 IP 地址设备间可以互相通讯。IP 地址有两个主要功能:标识设备或网络,以及寻址。

  • Windows 下可以在命令提示符中使用 ipconfig 查看网络适配器的 IP。
  • Linux 下可以在终端中使用 ifconfig 或 ip addr 查看 IP。

1.2.2 子网掩码

IP 网络可以在 IPv4 和 IPv6 中划分子网。为此将 IP 地址识别成由两部分组成:网络前缀和主机编号。子网掩码(subnet mask)或无类别域间路由(CIDR)表示法确定了 IP 地址如何分为网络部分和主机部分。 子网掩码一词仅用于 IPv4 地址中。但是 IPv4 和 IPv6 都使用 CIDR 概念和符号。在此,在 IP 地址后面加斜杠和用于标识网络部分的位数(十进制)。例如:IPv4 地址及其子网掩码分别可以是 192.168.10.2 和 255.255.255.0。因为 IP 地址的前 24 位表示网络和子网,所以相同的 IP 地址和子网的 CIDR 表示法为 192.168.10.2/24

  • 主机编号全为 0,表示网络号。
  • 主机编号全为 1,表示网络广播。

1.2.3 IPv4地址的分类

(原笔记中有图片或表格占位符,此处省略)

1.2.4 公网与私网

公网 IP 在任何地方都可以访问。而私网 IP 只能在局域网内访问。 国际规定有一部分 IP 地址是用于局域网使用,也就是属于私网 IP,不在公网中使用的,它们的范围是:

  • 10.0.0.0 ~ 10.255.255.255
  • 172.16.0.0 ~ 172.31.255.255
  • 192.168.0.0 ~ 192.168.255.255

其中 127.0.0.1 ~ 127.255.255.255 用于回路测试,如 127.0.0.1 可以代表本机 IP 地址。

网络地址转换(NAT)是一种在 IP 数据包通过路由器或防火墙时重写来源或目的 IP 地址或端口的技术。这种技术普遍应用于有多台主机,但只通过一个公有 IP 地址访问互联网的私有网络中。1990 年代中期,NAT 是作为一种解决 IPv4 地址短缺以避免保留 IP 地址困难的方案而流行起来的,并成了家庭和小型办公室网络连接上的路由器的一个标准特征,因为对他们来说,申请独立的 IP 地址的代价要高于所带来的效益。

1.2.5 IPv4与IPv6

常见的 IP 地址分为 IPv4 与 IPv6 两大类。

  • IPv4 为 32 位长,通常书写时以四组十进制数字组成,并以点分隔,如:172.16.254.1
  • IPv6 为 128 位长,通常书写时以八组十六进制数字组成,以冒号分割,如:2001:db8:0:1234:0:567:8:1

随着互联网的快速成长,IPv4 的 42 亿个地址最终于 2011 年 2 月 3 日用尽。相应的科研组织已研究出 128 位的 IPv6,其 IP 地址数量最高可达 3.4×10383.4 \times 10^{38} 个,届时每个人家居中的每件电器,每件对象,甚至地球上每一粒沙子都可以拥有自己的 IP 地址。

1.3 端口

1.3.1 什么是端口

这里的端口指的是逻辑端口,即 TCP/IP 协议中的端口。端口用于进程(应用软件)在同一设备或不同设备之间通信。每个端口有一个对应的端口号。端口号有 65536 个。 可以使用 netstat -ano 查看端口信息。

1.3.2 端口号的分配

  1. 公认端口 (0~1023)  它们紧密绑定于一些服务。通常这些端口的通讯明确表明了某种服务的协议。端口号 0 是被保留的,不可使用。1~1023 系统保留,只能由 root 用户使用。
  2. 动态端口 (1024~65535)  之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。当一个系统进程或应用程序进程需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。当这个进程关闭时,同时也就释放了所占用的端口号。
  3. 常见端口
端口服务
0/TCP,UDP保留端口,不使用
7/TCP,UDPEcho(回显)协议
21/TCP,UDPFTP 文件传输协议
22/TCP,UDPSSH 安全远程登录协议
23/TCP,UDPTelnet 终端仿真协议
25/TCP,UDPSMTP 简单邮件传输协议
53/TCP,UDPDNS 域名服务系统
80/TCP,UDPHTTP 超文本传输协议
110/TCPPOP3 邮局协议第3版
137/TCP,UDPNetBIOS 名称服务
138/TCP,UDPNetBIOS 数据报文服务
139/TCP,UDPNetBIOS 会话服务
143/TCP,UDPIMAP 用于检索电子邮件
445/TCPMicrosoft-DS (Active Directory、Windows 共享、震荡波蠕虫等)
445/UDPMicrosoft-DS 服务器消息块(SMB)文件共享
666/UDP毁灭战士(FPS 游戏)
873/TCPRsync 文件同步协议
902VMware 服务器控制台
3306/TCP,UDPMySQL 数据库系统
3389/TCP远程桌面协议(RDP)

1.4 socket套接字

1.4.1 什么是socket

socket(套接字)是同一或不同电脑的进程(任务、应用软件)间通信的一个工具,进程之间想要进行网络通信需要基于 socket。只要与网络相关的应用程序或者软件都使用到了 socket

1.4.2 socket的使用

Python 中提供了 socket 模块用于创建套接字。

python
import socket
# AF_INET 用于 Internet 进程间通信;SOCK_STREAM 流式套接字,TCP
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# AF_INET 用于 Internet 进程间通信;SOCK_DGRAM 数据报套接字,UDP
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

1.5 UDP

1.5.1 什么是UDP

用户数据报协议(UDP:User Datagram Protocol)是一个简单的面向数据报的通信协议。UDP 只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份。

UDP 避免了协议栈中执行错误检查和纠正处理的开销,适用于对时间有较高要求的应用程序,因为某些场景下丢弃数据包比等待或重传导致延迟更可取。流媒体、在线游戏流量通常使用 UDP 传输。

1.5.2 UDP编程

1)UDP编程流程

image.png

2)案例

UDP 服务端:

"""udp服务端"""
import socket
# 创建udp套接字
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 绑定ip和端口
udp_socket.bind(("127.0.0.1", 8080))
while True:
    # 接收数据
    recv_data, client_addr = udp_socket.recvfrom(1024)
    client_ip = client_addr[0]
    client_port = client_addr[1]
    print(f"{client_ip}:{client_port}>> {recv_data.decode('utf-8')}")
    # 发送数据
    udp_socket.sendto("你好".encode("utf-8"), client_addr)
# 关闭套接字
udp_socket.close()

UDP 客户端:

"""udp客户端"""
import socket
# 创建udp套接字
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
while True:
    try:
        # 发送数据
        server_ip = "127.0.0.1"
        server_port = 8080
        msg = input(f"{server_ip}:{server_port}<< ")
        udp_socket.sendto(msg.encode("utf-8"), (server_ip, server_port))
        
        # 接收数据
        recv_data, client_addr = udp_socket.recvfrom(1024)
        client_ip = client_addr[0]
        client_port = client_addr[1]
        print(f"{client_ip}:{client_port}>> {recv_data.decode('utf-8')}")
    except KeyboardInterrupt:
        break
# 关闭套接字
udp_socket.close()

1.6 TCP

1.6.1 什么是TCP

传输控制协议(TCP:Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 协议的运行可划分为三个阶段:连接建立、数据传送和连接终止。

很多重要的机制保证了 TCP 的可靠性和强壮性,包括:

  • 使用序号,对收到的 TCP 报文段进行排序以及检测重复的数据。
  • 使用校验和检测报文段的错误,即无错传输。
  • 使用确认和计时器来检测和纠正丢包或延时。
  • 流控制。
  • 拥塞控制。
  • 丢失包的重传。

1.6.2 TCP编程

1)TCP编程流程

image.png

2)案例

TCP 服务端:

"""tcp服务端"""
import socket
# 创建tcp套接字
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 绑定ip和端口
tcp_socket.bind(("127.0.0.1", 8080))
# 设置监听
tcp_socket.listen(2)
# 等待客户端连接 (这里为了演示简单,放在了 while 外面,实战中通常支持多客户端)
client_socket, client_addr = tcp_socket.accept()
while True:
    # 接收数据
    recv_data = client_socket.recv(1024)
    if not recv_data: 
        break # 客户端断开连接时退出
    print(f"{client_addr[0]}:{client_addr[1]}>> {recv_data.decode('utf-8')}")
    
    # 发送数据
    client_socket.send("你好".encode("utf-8"))
# 关闭套接字
client_socket.close()
tcp_socket.close()

TCP 客户端:

"""tcp客户端"""
import socket
# 创建tcp套接字
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 连接服务器
server_ip = "127.0.0.1"
server_port = 8080
tcp_socket.connect((server_ip, server_port))
while True:
    try:
        # 发送数据
        msg = input(f"{server_ip}:{server_port}<< ")
        tcp_socket.send(msg.encode("utf-8"))
        
        # 接收数据
        recv_data = tcp_socket.recv(1024)
        print(f"{server_ip}:{server_port}>> {recv_data.decode('utf-8')}")
    except KeyboardInterrupt:
        break
# 关闭套接字
tcp_socket.close()