简单认识协议
人与人之间沟通,讲话,汉语,英语,都是沟通的方式,汉语和英语都是规定好的语言文字,这就是一种约定。
计算机与计算机之间的交流(交流数据)也需要这种约定,也叫做协议。
既然要交流,因为传输距离变长,那么有一些问题就会出现:
如何处理发来的数据?
长距离传输数据的丢失问题
如何定位主机?
如何确保数据能准确无误的传达到下一个主机?
这些问题,全部都要用协议去解决。
比如处理的数据在应用层有协议,防止数据丢失同TCP协议,定位主机用IP协议,数据准确传达用数据链路层协议。
那么如何理解协议呢?
比如说,一个快递,我们买的物品在里面,但是外面的包裹和快递单号的那张贴纸都不是我们要的东西。
但是这些又是必不可少的,包裹保证里面的物品不会损坏,贴纸上面的地址联系方式等等是确定卖家与买家的发货地址和收货地址。
本质来讲:计算机之间的传输媒介是光信号和电信号. 通过 "频率" 和 "强弱" 来表示 0 和 1 这样的信息.。要想传递各种不同的信息, 就需要约定好双方的数据格式。
协议分层
什么是分层?
就好像C++当中的基类和派生类一样,或者是某个程序有很多功能,每个功能有很多个模块,某个大项目有很多子项目一样。
协议本质也是软件,在设计上为了更好的进行模块化,解耦合,也是被设计成为层状结构的。
举个例子:
两个人在用电话机互相通话,看起来是两个人在进行交流,其实是两个电话机在进行交流,人把自己说的话交给电话机,转换成电话机能理解的语言然后和另一台电话机进行交流,之后在交给另一个人。
OSI分层
OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;
把网络从逻辑上分为了 7 层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;
OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;
它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯;

其实在网络角度,OSI 定的协议 7 层模型其实非常完善,但是在实际操作的过程中,会话层、表示层是不可能接入到操作系统中的,所以在工程实践中,最终落地的是 5 层(或者4层)协议。
TCP/IP模型
TCP/IP 是一组协议的代名词,它还包括许多协议,组成了 TCP/IP 协议簇. TCP/IP 通讯协议采用了 5 层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。(是因为TCP与IP太重要了,所以用他们命名)
物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的 wifi 无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.
数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线 LAN 等标准. 交换机(Switch)工作在数据链路层.
网络层: 负责地址管理和路由选择. 例如在 IP 协议中, 通过 IP 地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.
传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.
应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层.物理层我们考虑的比较少,我们只考虑软件相关的内容. 因此很多时候我们直接称为TCP/IP 四层模型.
那么这些分层和OS系统有什么关系呢?
用户不能直接访问硬件,让用户直接传数据给网卡,OS是软硬件的管理者,因为要保证硬件的安全。
这个也叫做网络协议栈,发送数据都要遵守这个规则。(从上面用户部分一直传到下面网卡,发送数据)
网络通信的本质就是贯穿协议栈的过程。
以太网(局域网协议的一种)
网络协议栈的基本工作原理
我们在用微信的时候,并不是所有人都会去更新软件,有的人喜欢实时更新,有的不到最后一刻不会更新。
也就是说服务器要兼容新老版本的通信问题。
那么应用层就需要知道类似的信息,加入我们要发送一个消息,你好二字,在他的前面要添加报头字段(也就是添加类似于版本信息的东西)
并且,我们发送消息,如果消息内容过多,如何确保发送过去顺序是正确的呢?这里就需要在传输层添加报头。
不仅仅如此,我们要发给谁,如何定位,还要告诉别人我是谁,这里要在网络层确定IP,添加报头。
最后,我们要说明如何发给其他人,并添加报头。
这种方式也叫做自顶向下封包。
这一系列完成之后,通过网卡发送到对方的网卡:
然后服务器再通过网卡依次解包,每一次拿到对应的报头就知道该怎么做,发给谁,看看版本是否合适等等信息。
也就是说,在逻辑上来讲,每一层都认为,自己是在与对方的同一层进行通信。
通信的本质就是不断的进行封装与解包的过程。
对于每一层而言,当前添加或者获取到的数据叫做报头,剩下的叫做有效载荷,报文=报头+有效载荷。
也就是说,几乎任何层的协议,都要提供一种能力,将报头和有效载荷进行分离的能力,并且决定将自己的有效载荷提供给上一层哪一个协议的能力。(交给上一层哪一个协议的叫做分用)
以太网通信
首先我们要清楚,在每台主机上,都要有自己的唯一标识。(IP)
就好像我们在一个班级里,都拥有自己的学号一样;如果老师要进行提问,当老师点到你的学号时,其他人就会无视老师刚才说的话,也就是丢掉报文。
MAC地址
在刚才的链路层,里面的报头添加的就是自己和要发送对方的Mac地址。
假设H1发送给H10一个信息,其他人能收到吗?答案是不会的,因为发送的时候里面有M10的地址,其他主机一看不是自己的地址就不会接受这个报文。
这就是局域网通信原理。
那么如果H1发给H10,H5发给H6,这两个发送同时进行,消息就会碰撞,然后谁也收不到。
所以一个消息在发送的时候,其他消息就会等待,延迟一段时间发送,避免消息碰撞。(所以需要检测局域网被没被使用,一直检测)
一个局域网,众多主机组成的域也叫做碰撞域。
如果碰撞域过大,影响效率(就像宿舍用网络的人多了就会变卡),这个时候就需要添加一个机器叫做交换机。
假设交换机在H8与H9之间,H1发给H6,H5发给H10,他们同时发送也没关系,因为交换机会检测,H1和H6都在左侧,H5和H10都在右侧,他们发送的广播就会被阻止传递到另一面。
所以局域网也会被看作临界资源。
并且,我们使用网络的时候,都是进程在使用,就像微信,只有启动之后才会互相发送消息。
跨网段的主机的数据传输. 数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器.
这是一张示意图:
同样Mac也是锁定地址,那么IP和Mac到底有什么区别呢?
这就好像我在吉林,决定去浙江旅游,这就是IP,从吉林到浙江,出发地点和决定地点就是IP。
那么去浙江的路上会有规划路线,假设我是坐火车,会经过很多个城市,比如吉林出发,下一站到辽宁,并没有到浙江,但是方向朝着终点走,这就是Mac地址。(Mac地址会一直变化,上一站从哪来,下一站到哪去,变化依据是我要去哪里,IP地址不会变)
在上面的图中,我们发消息,层层封装之后到了数据链路层,然后有了Mac的发送地址,之后这个报文到了以太网中,发现其他不是Mac R的地址就不会进入,看到了Mac是R就进入了路由器,然后询问路由器S在哪里,路由器刚好知道S的IP地址,直接就能将C通过令牌环网送到S当中;拿到了令牌环的令牌,也就是添加了令牌环的报头(也是令牌环的协议)。
最后在进行依次解包拿到信息。
也就是说,虽然到了以太网,Mac地址虽然和令牌环网的不相同,谁也不认识谁,但是因为有了IP,我们的底层差异化就会被屏蔽!靠的就是工作在IP层的路由器。
IP实现了全球主机的软件虚拟层,一切皆IP报文。
查询自己的IP
Linux
ifconfig
查到的是内网IP
IPV4的基本的格式就是[0,255].[0,255].[0,255].[0,255] 也就是四个字节
(IPV6是十六字节)
端口号
在进行网络通信的时候,是不是两台机器再进行通信呢?
首先要清楚,网络协议下三层解决的问题是数据安全可靠的运输到另外一台机器当中,真实发送数据其实是我们启动的微信,QQ等软件,也就是进程。(日常的网络通信也就是进程之间的通信)
每个进程都是独立的,那么通信的时候,机器不一定只有一个进程,OS是如何知道是哪个进程发送给哪个进程的呢?
这里也是通过添加报头的方式进行解决的,传输的时候把自己和对面的IP+端口号port带上。(IP+端口号可以分辨出是哪一台机器的某个应用)
也就是说,两个进程,只要用自己和对方的ip+port就能相互通信了,这种通信方式叫做:socket。
那么为什么要有端口号,而不去用OS中的PID呢?
1.不是所有进程都需要通信,但是所有进程都需要pid
2.万一OS将进程的PID更改了呢?网络部分就要全都更改,耦合性太高。
每个端口号都是用数组去控制分配的,通过哈希算法来进行查找。
那么如何知道对放的端口号呢?
因为软件是固定公司开发的,所以公司是知道端口号的,所以精心设计之后,端口号也就是被客户端众所周知的了。
一个端口号可以绑定多个进程吗?
不能,因为端口号是确定唯一通信进程的标识。
一个进程可以绑定多个端口号吗?
能。
简单认识TCP协议与UDP协议
TCP
传输层协议
有连接
可靠传输
面向字节流
UDP
传输层协议
无连接
不可靠传输(如果数据丢失UDP是不管的)
面向数据报
但是UDP比TCP快,因为安全可靠是要付出时间上的代价的。
网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
c
#include <arpa/ inet.h>
uint32_ t htonl (uint32_ t hostlong) ;
uint16_ t htons (uint16_ thostshort) ;
uint32 t ntohl (uint32_ tnetlong) ;
uint16 t ntohs (uint16_ t netshort) ;
这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
套接字
套接字编程种类:
1.域间套接字编程:用于同一台机器内(不重要)
2.原始套接字编程:用于编写网络工具
3.网络套接字编程:用户间的网络通信
这个分类是想将网络接口统一抽象化,参数类型必须是统一的。
socket接口
c
// 创建 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);
这些接口的struct sockaddr *addr,就是根据上面的图,指向哪个地址就用哪个方式(16位地址类型):