文章目录


一、网络的发展及其网络中的概念

刚开始的计算机大多处于独立模式,也就是说计算机之间相互独立。等到网络出现的时候就出现了网络互联,多台计算机连接在一起完成数据共享。随着发展计算机越多越多就出现了局域网LAN,这个局域网LAN可以让多台计算机通过交换机和路由器连接在一起,但是由于局域网的范围不够广泛,不久后就出现了广域网WAN,广域网WAN将远隔千里的计算机都连接在一起。所谓“局域网”和“广域网”只是一个相对的概念,实际上我们完全可以将大的广域网也看成局域网。

下面我们认识一下网络协议:

“协议”简单的说就是一种约定。计算机之间的传输媒介是光信号和电信号,通过“频率”和“强弱”来表示0和1这样的信息,要想传递各种不同的信息,就需要约定好双方的数据格式。这就比如我们的语言一样,各个地区都有自己的方言,交流的时候会让很多人听不懂,但是普通话的出现就解决了人与人沟通听不懂的问题,普通话就是人与人之间的协议。(定制协议可以尽可能的减少通信成本)

而在网络协议中,我们又分了好几个层次,为什么要分层呢?因为分层最大的好处就是封装。

OSI七层模型:

OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;
把网络从逻辑上分为了7层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;
七层从第一层到第七层分别是:物理层,数据链路层,网络层,传输层,应用层(应用层中包括了会话层,表示层,应用层)
物理层:以0,1代表电压的高低,灯光的闪灭,界定连接器和网线的规格。
数据链路层:互联设备之间传送和识别数据帧。
网络层:地址管理和路由选择
传输层:管理两个节点之间的数据传输,负责可靠传输(确保数据被可靠地传送到目标地址)
会话层:通信管理,负责建立和断开通信连接(数据流动的逻辑通路)管理传输层以下的分层
表示层:设备固有数据格式和网络标准数据格式的转换。
应用层:针对特定应用的协议
OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输; 它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯; 但是, 它既复杂又不实用; 所以我们按照TCP/IP四层模型来讲解。
TCP/IP 是一组协议的代名词,它还包括许多协议,组成了 TCP/IP 协议簇 .
TCP/IP 通讯协议采用了 5 层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求 .
物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wififi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.
数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.
网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.
传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.
应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层。

网络传输基本流程图:

因为物理层我们考虑的比较少. 因此很多时候也可以称为 TCP/IP四层模型.  

两台计算机通过TCP/IP协议通讯的过程如下所示:

下面我们通过上图来谈谈局域网中协议报头的概念:

1.协议报头: 协议在上图中每一层都会有,而每一个协议的最终表现就是协议都要有报头。

怎么理解报头呢?我们举个例子:比如我们网购买了一个水杯,等卖家给我们发货到我们手里时除了水杯还有快递盒和快递单,这个快递单就相当于我们的报头,快递单不是给我们买家看的,是给快递员看的,报头也一样。

2.协议通常是经过协议报头来表达的,就像快递单上的格式都是卖家的地址电话和买家的地址电话一样。

3.每一份数据最终在被发送或者在不同的协议层中都要有自己的报头。比如我们买的水平可能会附带水杯的说明书,这个说明书就是数据自己的报头。

下面是局域网的概念:

1.两台局域网的主机能够直接通信。

2.局域网通信的原理:每一台机器都要有自己的“名字”,每一台主机都有网卡,每一张网卡都有自己的地址,MAC地址(表明自己在局域网中的唯一性)。

我们在linux中输入命令:ifconfig就可以看到自己的Mac地址:

ether后面就是Mac地址,下面我们谈谈局域网是如何实现通信的:

 如上图所示,一共有7台主机,每台主机依靠物理地址(MAC地址)区分,当MAC1这台主机给Mac6这台主机发送消息时,所有的主机都会看到这条消息,比如这条消息传到了Mac2中,Mac2发现这个消息是要发送的地址是MAC6的(主机会在自己的底层协议做判断,可以判断出消息是不是发给自己的,如果消息不是自己的那么就将消息丢弃),所以不会对这个消息做出回应,只有MAC6收到了消息。

 如上图所示,当左边的用户要给右边的用户发送一条“你好”的消息时,实际上这条消息会从应用层向下交付到传输层,再从传输层交付到网络层,然后交付到链路层,我们之前说过,报头在每一层都会有,所以这条消息每次在被向下交付的过程中都会被加上报头,如下图:

到链路层后会将这条消息通过以太网发送到网络中(注意图中只有两台主机,实际上如果有多台主机这条消息发到网络中后每一台主机都会判断这条消息是否是发送给自己的,如果不是就丢弃)。这样我们就将这条消息发送到另一台主机的链路层中:

 注意以上的过程类似于栈结构,A主机链路层最后封装的被B主机先拿到。

 这条消息我们将它分为报头 + 有效载荷,在B主机链路层向服务器交付报文的时候会做一个工作,那么去掉报头(注意:每一层只能去除每一层特定的报头,比如链路层的桃心报头只会被B主机中的链路层去掉)。

经过层层去掉报头的操作后,我们的消息最终就会被B用户所收到。 所以整个过程就是:当我们发送消息时,会自顶向下(应用层到链路层)添加报头,接收消息时自底向上(链路层到应用层)去掉报头。而上面添加报头的过程被称为封装,去掉报头的过程被称为解包。

注意:在网络协议中,我们可以认为同层协议在直接通信,也可以理解为向下交付,这是两种不同的认知并且并不冲突。

数据包封装和解包分用
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报 (datagram),在链路层叫做帧(frame).
应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装
(Encapsulation).
首部信息中包含了一些类似于首部有多长, 载荷(payload)有多长, 上层协议是什么等信息.
数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部, 根据首部中的 "上层协议字段" 将数据交给对应的上层协议处理。

下面我们看看带路由器的数据传输过程:

 我们可以看到路由器既属于左边又属于右边,相当于将图上两台主机做了连接,如下图所示:

用户A发送消息还是像前面那样先要从上向下封装报头,不同的是消息经过以太网驱动程序封装报头后直接交给了路由器。

 我们可以看到,要交给路由器必须从底向上交付,一旦从底向上交付就必须解包,所以路由器拿到的是解包以太网报头后的消息,然后路由器发现这个消息要发到右边的主机,这里还是要先给到右边主机的最底层,由于是从顶向下所以又需要添加报头,如下图:

这里的流程就和我们前面讲的一样了直接从令牌环驱动程序开始解包向上传输消息最终被右边的用户收到:

这里我们讲一下局域网中的以太网和令牌环网:

以太网:只允许一个主机在任何一个时刻在局域网中发送消息,否则发生碰撞(局域网就是碰撞域),比如前面我们讲的7个主机的例子,在那个例子中不能如果是以太网则同一个时间点只能有一个主机在局域网中发送消息。

令牌环网:还是7个主机的例子,但是不同的是只有获得令牌的那个主机才能发送消息,这里的令牌与我们的互斥锁类似,就像只有申请到锁资源才能访问数据一样。 

下面我们学习一下网络中的地址管理:

认识 IP 地址
IP 协议有两个版本 , IPv4 IPv6.后面我们 凡是提到 IP 协议 , 没有特殊说明的 , 默认都是指 IPv4。
IP 地址是在 IP 协议中 , 用来标识网络中不同主机的地址 ;
对于 IPv4 来说 , IP 地址是一个 4 字节 , 32 位的整数 ;
我们通常也使用 " 点分十进制 " 的字符串表示 IP 地址 , 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;
认识 MAC 地址
MAC 地址用来识别数据链路层中相连的节点 ;
长度为 48 , 6 个字节 . 一般用 16 进制数字加上冒号的形式来表示 ( 例如 : 08:00:27:03:fb:19)
在网卡出厂时就确定了 , 不能修改 . mac 地址通常是唯一的 ( 虚拟机中的 mac 地址不是真实的 mac 地址 , 可能会冲突; 也有些网卡支持用户配置 mac 地址 )

 ether后面就是mac地址

inet后面就是IP地址,如上图所示。

下面我们认识一下网络编程套接字的相关概念:

 理解源IP地址和目的IP地址

为了好理解我们举一个例子:唐僧从东土大唐到西天取经,在这途中他要先从黑风岭到女儿国,这里的目的IP地址就是女儿国,源IP地址就是西天(可以理解为源IP就是终极目标)

但是我们光有 IP 地址就可以完成通信了吗 ? 其实不是,除了IP地址我们还需要端口号,端口号是用来标识各自主机上客户或者服务的唯一性。为了更好的表示一台主机上服务进程的唯一性,我们采用端口号port来标识服务器进程,用来确定客户端进程的唯一性。
端口号(port)是传输层协议的内容.
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用。

我们在学进程的时候说过,进程的pid是标识进程的,那么我们为什么还要用端口号来表示进程的唯一性呢?这里我们先说一个概念:网络通信的本质就是进程间通信(进程间通信有两个前提:1.需要让不同的进程先看到同一份资源(网络通信中这个资源就是网络)2.我们说过通信实际上就是IO,所以通信就两个目的:我把我的数据发出去或者我收到别人给我发的数据) ,这也就是为什么我们上面端口号的概念中出现了多次进程这个名词。1.实际上虽然pid是表示进程的,但是不是每个进程都进行网络通信的,所以不用pid来替代端口号。2.最重要的是,系统是系统,网络是网络,我们不能将他们的接口混为一谈。3.网络中客户端每次都能找到服务器进程,这就说明了服务器的唯一性不能做任何改变,所以我们的端口号port不能随意的去改变。(进程的pid每次重新运行程序都会不一样)。通过以上3点,我们解释了为什么网络通信需要端口号的存在。IP + port

我们刚刚说了一个端口号只能被一个进程绑定,那么一个进程可以绑定多个端口号吗?答案是可以。因为我们要的是从端口号到进程是唯一的,一个进程绑定多个端口号,那么从端口号到这一个进程也还是唯一的。

理解源端口号和目的端口号
要理解谁是源端口号很简单,只需要区分是哪个主机发送的数据,谁发送数据谁就是源端口号,谁接收数据谁就是目的端口号。
TCP协议特点:
传输层协议。
有连接
可靠传输
面向字节流

UDP协议
传输层协议
无连接
不可靠传输
面向数据报

网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
为使网络程序具有可移植性 , 使同样的 C 代码在大端和小端计算机上编译后都能正常运行 , 可以调用以下库函数做网络字节序和主机字节序的转换。
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

socket编程接口 :

// 创建 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);

 注意socket就是套接字,套接字可以分为:

1.网络套接字(应用于网络跨主机之间通信并且还支持本地通信)

2.原始套接字(适用于抓包等工具)

3.unix域间套接字(只能够本地通信)

sockaddr 结构

我们在上面的套接字编程接口中可以看到sockaddr这个类型,下面我们讲讲它的结构:

socket API 是一层抽象的网络编程接口 , 适用于各种底层网络协议 , IPv4 IPv6, 以及后面要讲的 UNIX Domain Socket. 然而 , 各种网络协议的地址格式并不相同。
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结构体指针做为参数;


总结

上面我们讲了网络基础的概念以及网络套接字的一些概念,这些知识是为了我们下一篇编写UDP服务器而做准备的,由于网络相关的知识都比较抽象,所以还是希望大家可以尽量的去理解网络中各种名词的解释。

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐