1. 网络基础
总结来说:
- 计算机不能独立使用,必须进行协作,注定了计算机之间要进行连接通信,就产生了网络
- 网络是局部产生的,是从局部到整体的(网络互联 ----> 局域网 ----> 广域网 ----> 更大的网)
2. 初识协议
计算机之间的传输媒介是光信号和电信号,达成数据格式协议,就可以减少通信成本
一般具有定制协议或者标准的资格的组织或者公司都必须是业界公认的组织或公司,比如:ISO(国际标准化组织):ISO是由多个国家的标准化团体组成的国际组织,它在开放系统互连 (OSI)模型方面尤为著名。OSI模型定义了网络通信的七层协议结构,尽管在实际应用中,TCP/IP协议族更为普遍,但OSI模型仍然在学术和理论研究中占有重要地位
2.1 协议分层
- 协议本质也是软件,在设计上为了更好的进行模块化,解耦合
- 分层之后,可以替换掉任意一层
2.2 OSI七层模型
在网络角度,OSI定的协议7层模型其实非常完善,但是在实际操作的过程中,会话层、表示层是不可能接入到操作系统中的,所以在工程实践中,最终落地的是5层协议
2.3 TCP/IP五层(或四层)模型
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇
TCP/IP通讯协议采用了5层的层级结构
- 物理层:传光电信号,用网线、光纤、电磁波,决定传输速度与距离,集线器工作在此层。
- 数据链路层:在设备间传数据帧,做差错校验、冲突检测,交换机工作在此层。
- 网络层:负责 IP 寻址与路由选择,规划数据传输路径,路由器工作在此层。
- 传输层:实现端到端可靠传输,代表协议 TCP/UDP。
- 应用层:面向具体应用服务,如 FTP、SMTP 等,网络编程主要针对这一层
物理层考虑的比较少,只考虑软件相关的内容。因此很多时候直接称为 TCP/IP四层模型.
一般而言:
- 对于一台主机,它的操作系统内核实现了从传输层到物理层的内容
- 对于一台路由器,它实现了从网络层到物理层
- 对于一台交换机,它实现了从数据链路层到物理层
- 对于集线器,它只实现了物理层
但是并不绝对,很多交换机也实现了网络层的转发,很多路由器也实现了部分传输层的内容(⽐如端口转发
3. 再识协议
3.1 为什么要有协议?
- 本机通信 VS 网络通信,区别就是:通信距离变长了
- 通信距离变长会带来一系列问题:处理数据?数据丢失?定位目标主机?下一跳问题?
- 上述问题就需要分层处理,注定了层状结构
3.2 什么是TCP/IP协议?
- TCP/IP协议的本质是一种解决方案
- TCP/IP协议能分层,前提是因为问题们本身能分层
3.3 TCP/IP协议与操作系统的关系![]()
3.4 协议本质是什么?![]()
如上图:即使os不一样,主机b还是可以识别传来的数据并提取,为什么?因为双方都有同样的结构体类型,因为用同样的代码实现协议,就用了同样的数据类型,这就是一种协议/约定
所谓协议,就是通信双方都认识的结构化的数据类型
因为协议栈是分层的,所以每层都有双方都有协议,同层之间,互相可以认识对方的协议
4. 网络传输基本流程
4.1 局域网络传输流程图
1)局域网通信原理(以太网为例)
- 当两台主机在一个局域网,就可以直接通信
- 每台主机在局域网上,要有唯一的标识来保证主机的唯一性:mac地址
2)认识MAC地址
- MAC地址用来识别数据链路层中相连的节点
- 长度为 48比特位,即 6 个字节。一般用16 进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
- 在网卡出厂时就确定了,不能修改。mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址,可能会冲突.
- windows>ipconfig /all
- 局域网通信的过程中,主机对收到的报文确认是否是发给自己的,是通过目标mac地址判定
- 可以试着从系统角度来理解局域网通信原理
以下是同一个网段内的两台主机进行发送消息的过程:
所以进行上述传输流程的时候,要进行封装和解包
- 报头部分,就是对应协议层的结构体字段,一般叫做报头
- 除了报头,剩下的叫做有效载荷
- 故,报文 = 报头 + 有效载荷
- 不同的协议层对数据包有不同的称谓,在传输层叫做段(segment) ,在网络层叫做数据报(datagram),在链路层叫做帧(frame)
- 应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装
- 首部信息中包含了一些类似于首部有多长,载荷(payload)有多长,上层协议是什么等信息
- 数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的"上层 协议字段"将数据交给对应的上层协议处理
- 每层都有协议:
总结:在网络传输的过程中,数据不是直接发送给对方主机的,而是先要自定向下将数据交付给下层协议, 最后由底层发送,然后由对方主机的底层来进行接受,在自底向上进行向上交付,从而实现看似是用户层的通信
3)数据包封装和分用
所以学习协议就是学习如何做到解包,封包以及如何将有效载荷交给上层
4)认识IP地址
- IP 协议分IPv4、IPv6
- IP 地址用于唯一标识网络中的主机
- 日常采用点分十进制书写,四段数字各占 1 字节,例如192.168.0.1,取值 0~255
- 目的IP的意义
然后结合封装与解包,体现路由器解包和重新封装的特点
对比IP地址和Mac地址的区别:
- IP 地址:传输全程保持不变;MAC 地址:每经过一跳路由转发,不断变化
- 目的 IP:全局层面,代表最终长远目标,用于跨网段路由选路;
- MAC 地址:仅局域网内,代表下一跳设备,负责局域网内部数据转发
- IP:决定数据走哪条路(跨网路由);MAC:决定数据下一站给谁(局域网直连转发)
IP网络层存在的意义:提供网络虚拟层,让世界的所有网络都是 IP 网络,屏蔽最底层网络的差异
TCP和UCP
二、核心区别对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接(三次握手 / 四次挥手) | 无连接(直接发送) |
| 可靠性 | 可靠(确认、重传、排序、校验) | 不可靠(尽力而为,可能丢包 / 乱序) |
| 传输模式 | 字节流(无边界,像水管流水) | 数据报(有边界,独立数据包) |
| 流量 / 拥塞控制 | 有(滑动窗口、慢启动等) | 无 |
| 通信方式 | 一对一(点对点) | 支持一对一、一对多、多对多(广播 / 组播) |
| 首部开销 | 大(最小20 字节) | 小(固定8 字节) |
| 传输效率 | 低(延迟高、开销大) | 高(延迟低、速度快) |
| 典型应用 | HTTP/HTTPS, FTP, SMTP, 邮件,文件传输 | 视频通话、直播、游戏、DNS、实时音视频 |
5. Socket编程预备
5.1 端口号
1)什么是端口号
- 端口号属于传输层协议内容;
- 大小为2 字节、16 位整数;
- 作用:标识主机内进程,让系统区分数据交由哪个程序处理;
- IP 地址 + 端口号:可唯一确定网络中一台主机的某个进程;
- 同一时刻,一个端口号仅能被一个进程占用
2)端口号范围划分
- 0~1023:知名端口,固定分配给 HTTP、FTP、SSH 等常用应用层协议
- 1024~65535:动态端口,由操作系统分配,多用于客户端程序
3)端口号 与 PID 的区别 & 关系
- PID:操作系统全局唯一标识所有进程,是系统层级标识。
- 端口号:专门用于网络通信的进程标识,属于网络层级。
- 设计分离:不用 PID 做网络标识,避免网络与系统进程管理强耦合。
- 绑定规则:一个进程可绑定多个端口;一个端口同一时刻只能被一个进程占用
4)源端口 & 目的端口
- 源端口:标识数据发送方进程;
- 目的端口:标识数据接收方进程
5)理解socket
- IP 地址用来标识互联网中唯一的一台主机, port 用来标识该主机上唯一的一个网络进程
- IP+Port 就能表示互联网中唯一的一个进程
- 四元组:
源IP+源端口+目的IP+目的端口,唯一标识两端通信进程; - 网络通信本质:跨主机的进程间通信
- Socket(套接字) = IP + 端口
6. 传输层的典型代表
6.1 认识TCP协议
- 传输层协议
- 有连接:通信前需要建立连接
- 可靠传输:数据保证完整、有序、不丢失
- 面向字节流:数据以字节流形式传输
6.2 认识UDP协议
- 传输层协议
- 无连接:无需提前建立连接,直接发送数据
- 不可靠传输:不保证数据一定送达、有序
- 面向数据报:以独立数据报为单位收发
6.3 总结与选择
- 选 TCP:数据完整性 > 速度。适合文件、网页、邮件等不能出错的场景。
- 选 UDP:速度 / 实时性 > 完整性。适合视频、语音、游戏等可容忍少量丢包的场景
7. 网络字节序
- 送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
- 网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
- 如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略
大端如何发送:
- 先发:低地址 →
0x12(数据高位) - 再发:
0x34 - 再发:
0xab - 最后发:高地址 →
0xcd(数据低位
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:
- h 表示 host , n 表示 network , l (long)表示 32 位长整数, s (short)表示 16 位短整数
- 例如htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送
- 所有发送到网络上的数据,都必须是大端的!
8. socket 编程接口
8.1 常见函数
创建 socket ⽂件描述符 (TCP/UDP, 客户端 + 服务器) #include <sys/socket.h> int socket(int domain, int type, int protocol); 第一个参数:domain /af(地址族 / 协议族) AF_INET IPv4 协议(最常用) AF_UNIX / AF_LOCAL 本地进程通信 第二个参数:type(套接字类型) SOCK_DGRAM UDP 数据报套接字(最常用) SOCK_STREAM TCP 流式套接字(最常用) SOCK_RAW 原始套接字 第三个参数:protocol(具体协议号) 0 自动匹配(推荐) 返回值: 成功:返回一个非负整数,称为套接字描述符 失败:返回 -1绑定端⼝号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len); 第二个参数: 必须强制类型转换:(struct sockaddr*)& 返回值 成功:返回 0 失败:返回 -1 区分:std::bind(), C++ 标准库的函数工具,用来绑定函数、固定参数、修改函数调用方式套接字数据接收函数 #include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom( int sockfd, // 套接字文件描述符 void *buf, // 接收缓冲区 size_t len, // 缓冲区最大长度 int flags, // 控制标志 struct sockaddr *src_addr, // 输出:发送方地址 socklen_t *addrlen // 输入输出:地址缓冲区大小 ); flags: 0 默认阻塞模式,无特殊行为 返回值 成功:返回实际读到的字节数 出错:返回 -1UDP协议发送数据 #include <sys/socket.h> ssize_t sendto( int sockfd, // 套接字文件描述符 const void *buf, // 要发送的数据缓冲区 size_t len, // 数据长度 int flags, // 标志位(一般填 0) const struct sockaddr *dest_addr, // 目标地址(IP+端口) socklen_t addrlen // 地址结构体长度 ); 成功:返回 实际发送的字节数 失败:返回 -1启动服务器并监听连接 int listen(int sockfd, int backlog); 第二个参数 含义:TCP 连接队列的最大长度 作用:控制还没被服务器 accept () 处理的连接最多能排队多少个用于客户端向服务器发起网络连接 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);8.2 sockaddr结构
- IPv4 使用
sockaddr_in存储 IP + 端口 - Socket API 统一用
struct sockaddr*接收,靠AF_INET/AF_INET6区分地址类型,实现一套接口兼容所有协议
网络编程 4 件套 #include <sys/types.h> 系统基础类型(必加) #include <sys/socket.h> 套接字函数(socket/connect/bind) #include <netinet/in.h> sockaddr_in 结构体(核心) #include <arpa/inet.h> IP转换(inet_addr/inet_pton)sockaddr_in 结构
- 虽然socket api的接口是sockaddr,但是在基于IPv4编程时,使用的数据结构是sockaddr_in
- 这个结构里主要有三部分信息:地址类型,端口号,IP地址
// 来自头文件 <netinet/in.h> struct sockaddr_in { sa_family_t sin_family; // 1. 地址族(必须填 AF_INET) in_port_t sin_port; // 2. 端口号(必须用 htons() 转换) struct in_addr sin_addr; // 3. IP地址结构体,需要强转 char sin_zero[8]; // 4. 填充字节(全填0,为了对齐内存) }; // 里面嵌套的 IP 地址结构体 struct in_addr { uint32_t s_addr; // 真正存IPv4地址(32位整数) }; 参数: 1.sin_family(地址族):必须填 AF_INET 2. sin_port(端口号) :必须用 htons () 转换 因为端口号本身就只有16位长 3. sin_addr.s_addr(IP 地址) 4. 0