Linux-网络编程
1. 初始网络协议
“协议” 是一种约定. 打电话约定电话铃响的次数的约定
协议分层
协议本质也是软件, 在设计上为了更好的进行模块化, 解耦合, 也是被设计成为层状结构的
1.1 OSI 七层模型
- OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;
- 把网络从逻辑上分为了7层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;
- OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;
- 它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯;
- 但是, 它既复杂又不实用; 所以我们按照TCP/IP四层模型来讲解
1.2 TCP/IP四层(除了物理层外)模型
TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.
TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求
- 物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.
- 数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.
- 网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.
- 传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机
- 应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层.
一般而言
- 对于一台主机, 它的操作系统内核实现了从传输层到物理层的内容;
- 对于一台路由器, 它实现了从网络层到物理层;
- 对于一台交换机, 它实现了从数据链路层到物理层;
- 对于集线器, 它只实现了物理层;
但是并不绝对. 很多交换机也实现了网络层的转发; 很多路由器也实现了部分传输层的内容(比如端口转发);
1.3 为什么要有 TCP/IP 协议?
- 首先, 即便是单机, 你的计算机内部, 其实都是存在协议的, 比如: 其他设备和内存通信, 会有内存协议。 其他设备和磁盘通信, 会有磁盘相关的协议, 比如:SATA, IDE, SCSI 等。 只不过我们感知不到罢了。 而且这些协议都在本地主机各自的硬件中, 通信的成本、 问题比较少。
- 其次, 网络通信最大的特点就是主机之间变远了。 任何通信特征的变化, 一定会带来新的问题, 有问题就得解决问题, 所以需要新的协议咯。
所以, 为什么要有 TCP/IP 协议? 本质就是通信主机距离变远了
1.4 TCP/IP协议
- TCP/IP 协议的本质是一种解决方案
- TCP/IP 协议能分层,前提是因为问题们本身能分层
1.5 TCP/IP协议与操作系统
- 操作系统的标准是必须相同的。
- 网络部分的源代码必须是一样的,即你有一个结构体,我也有一个结构体,你有一个通信函数,我也有一个通信函数,所以主机2发的数据,主机1可以解析出来。
- 所谓协议,就是通信双方都认识的结构化的数据类型。
2. 局域网网络传输流程图
2.1 网络传输的基本流程
2.1.1 局域网(以太网为例)的通信原理(MAC 地址)
在一个局域网中的两台主机可以直接通信,每台主机(局域网),都要有一个唯一标识:Mac地址
光标亮起的是本机的 Mac 地址。
Mac 地址是在网卡中集成好的
- MAC 地址用来识别数据链路层中相连的节点;
- 长度为 48 位, 及 6 个字节. 一般用 16 进制数字加上冒号的形式来表示(例如:
08:00:27:03:fb:19)- 在网卡出厂时就确定了, 不能修改. mac 地址通常是唯一的(虚拟机中的 mac 地
址不是真实的 mac 地址, 可能会冲突; 也有些网卡支持用户配置 mac 地址).
- 局域网的任何时刻,都只允许一台机器向网络中发送数据
- 如果有多台同时发送, 会发生数据干扰, 我们称之为数据碰撞
- 所有发送数据的主机要进行碰撞检测和碰撞避免
- 没有交换机的情况下, 一个以太网就是一个碰撞域
- 局域网通信的过程中, 主机对收到的报文确认是否是发给自己的, 是通过目标mac 地址判定
- 这里可以试着从系统角度来理解局域网通信原理
- 每层协议都会有自己的报头
- 添加报头称为封装,封装相当于入栈过程;取消报头的过程称为解报,解包相当于弹栈过程。所以 TCP/IP 叫做网络协议栈。
- 同层看到的报文都是一样的
- 数据在网络中发送的时候,一定最终在硬件上跑;
- 报文 = 报头 + 有效载荷
- 报头部分, 就是对应协议层的结构体字段, 我们一般叫做报头
- 除了报头, 剩下的叫做有效载荷
网络协议的共性:
- 报头和有效载荷分离的问题——解包
- 除了应用层,每一层协议,都必须解决一个问题,自己的有效载荷,应该交给上层的那一种协议!——分用
TCP/IP 通讯过程
在网络传输的过程中, 数据不是直接发送给对方主机的, 而是先要自定向下将数据交付给下层协议, 最后由底层发送, 然后由对方主机的底层来进行接受, 在自底向上进行向上交付。
2.1.2 数据包封装和分用
数据封装过程
每一层添加当前层所用协议的报头
数据分用过程
删除当前层所用协议的报头。
2.2 跨网络传输流程图
2.2.1 网络中的地址管理——认识 IP 地址
IP 协议有两个版本, IPv4 和 IPv6. 我们整个的课程, 凡是提到 IP 协议, 没有特殊说明的,默认都是指 IPv4
- IP 地址是在 IP 协议中, 用来标识网络中不同主机的地址;
- 对于 IPv4 来说, IP 地址是一个 4 字节, 32 位的整数;
- 我们通常也使用 “点分十进制” 的字符串表示 IP 地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;
- 跨网段的主机的数据传输. 数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器
然后结合封装与解包, 体现路由器解包和重新封装的特点
- 路由器连接两个不同网段的主机,则就要配两个网卡
- 当自己网络层 路由 ,ip不在本网络时,则就直接交给路由器
- 网络层(就是 IP 层)向上(包括网络层)看到的所以的报文都一样,都至少是 IP 报文
- IP 可以屏蔽底层的网络差异
- 所有的网络都是 IP 网络
IP 地址 vs Mac 地址
ip:最终目标
Mac:下一个目标
- IP 地址在整个路由过程中, 一直不变(目前, 我们只能这样说明, 后面在修正)
- Mac 地址一直在变
- 目的 IP 是一种长远目标, Mac 是下一阶段目标, 目的 IP 是路径选择的重要依据, mac 地址是局域网转发的重要依据
Mac地址只是保存局域网中的唯一性
也就是相当于 西天取经
ip 地址:从长安来,到西天去
Mac 地址:途中经过的各个国家
提炼 IP 网络的意义和网络通信的宏观流程
IP 网络层存在的意义: 提供网络虚拟层, 让世界的所有网络都是 IP 网络, 屏蔽最底层网络的差异
3. Socket 编程预备
3.1 理解源 IP 地址和目的 IP 地址
IP 在网络中, 用来标识主机的唯一性。
数据传输是将数据从一个主机传输到另一个主机的 进程(qq,浏览器,迅雷) 中,而要识别哪一个进程(即进程的唯一性),我们引入了端口号。
3.2 认识端口号
端口号(port)是传输层协议的内容:
- 端口号是一个 2 字节 16 位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
端口号范围划分
- 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的.
- 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的
系统当中的进程 pid 也是唯一的,为什么不用 pid 当作选择主机中进程的标准,而要用port呢?
网络通信 最重要的是进程的唯一性。pid在网络协议栈中充当进程唯一性,让网络协议栈的 IP 交给对应的进程,技术上是可以的。但是pid是系统概念上的,而我们上面了解的 ip + port(端口号),都指的是网络上的概念,如果应用 pid 充当进程唯一性,会导致的最重要的问题是 系统和网络的强耦合。一旦操作系统中的pid发生更改,则网络中的也需要更改。所以我们要做到 系统和网络解耦合,系统是系统,网络是网络。所以会引入端口号。系统中进程有需要网络通信的,也有不需要网络通信的进程。
理解源端口号和目的端口号
传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号.
就是在描述 “数据是谁发的, 要发给谁”
理解 socket
- ip表示全网唯一的主机,port表示主机唯一的进程
- ip + port = 网络中唯一的进程
- 所以, 通信的时候, 本质是两个互联网进程代表人来进行通信, {srcIp,srcPort, dstIp, dstPort}这样的 4 元组就能标识互联网中唯二的两个进程
- 网络通信 的本质是 进程间通信
- 以上这种 ip + port 通信方式称为 套接字(socket)
3.3 传输层的典型代表
传输层是属于内核的, 那么我们要通过网络协议栈进行通信, 必定调用的是传输层提供的系统调用, 来进行的网络通信。
认识 TCP 协议
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
认识 UDP 协议
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
3.4 网络字节序
磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分.
- 小端:低权值位放在低地址,高权值位放在高地址
- 大端:低权值为放在高地址,高权值位放在低地址
那么如何定义网络数据流的地址呢?
TCP/IP 协议规定,网络数据流应该采用大端字节序,即低地址高字节
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
这些函数名很好记**,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数**。
- 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
4. socket 编程接口
socket 常见 API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr 结构
socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、 IPv6, 各种网络协议的地址格式并不相同
- IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型, 16 位端口号和 32 位 IP 地址.
- IPv4、 IPv6 地址类型分别定义为常数 AF_INET、 AF_INET6. 这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容.
- socket API 可以都用 struct sockaddr 类型表示, 在使用的时候需要强制转化成sockaddr_in*; 这样的好处是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数;