Linux《网络基础》
在之前的学习当作我们已经了解了Linux当作系统部分的知识,学习了进程、进程间通信、信号、线程等知识,现在我们已经能在Linux当作使用各种工具进行代码的编写,那么接下来从本篇开始我们就将开始Linux当作网络部分的学习,在此我们将会学习到数据在网络当作是如何进行传输的,了解到Udp、Tcp等网络协议,学会使用socket套接字进行网络的编程。那么在了解以上提到的知识之前先需要来了解到网络相关的基础知识,相信通过本篇的学习会让你对网络有基本的认识。
1.计算机网络的背景
在之前的学习当中实际上我们了解的都是计算机系统相关的知识,但是我们知道在使用计算机的时候不单单是在一台计算机内完成对应的服务的,是会涉及到计算机和计算机之间、或者是计算机和服务器之间的通信,那么要了解数据是如何在不同的设备之间进行传输的就需要我们了解对应的网络原理以及相关的协议,那么在进行这些的学习之前先来了解一下计算机网络的发展背景,这会让我们更好的为之后的学习做好铺垫。
其实一开始的计算机都是相互独立的,那么这时要完成一项的工作就需要在同一台计算机上进行,这时效率就很低下,那么为了解决以上的问题人们就试着将要同时进行工作的计算机连接起来,那么这时就可以让不同的工作同时的进行。
以上的进行的操作在上个世纪的美国的大学或者是科研机构当中是很常见的,那么到了上个世纪的60年代阿帕网的诞生被认为是当前互联网的前身,到了之后的90年代阿帕网关闭正式的进入到了互联网的时代,实际上和之前我们了解计算机一样,互联网也可以认为是军转民的产物。
在此将不同计算机连接起来的规模大小可以将其分为以下的几种形式:
局域网:
广域网:
2.初始协议
在网络之间进行通信的时候如果不同的区域,不同的设备之间进行通信的方式是不一样的,那么这时就会增添非常多的成本,因此为了解决网络当中通信成本高的问题,就在网络当中引入了协议。
当前我们可以将协议认为是一种进行通信预定的协议。
例如一个现实当中的例子,如果你和朋友预定好互相打电话的时候,当电话铃响起一声的时候就是要出去玩,当电话铃声响两声的时候就是要出来写作业,响三声的时候就是要打电话交流了。
以上的例就是在现实当中规定好的一种约定,那么在计算机网络当中又是如何指定对应的约定的呢?
其实工作是有ISO国际标准化组织,IEEE电气和电子工程师协会等组织来进行的,这些组织的指定的全球统一的网络通信的协议,那么所有使用对应的通信方式的计算机都必须要遵守对应的协议。
这些组织不进行代码的编写只是指定对应的协议。
3.协议分层
协议本质上也是软件,为了更好的解耦合,更好的进行模块化,那么实际上协议也是设计为层状的结构的。
例如以下的示例当中对翻译的功能进行分层,那么实际上就只需要使用两层就可以实现
在以上当中使用两层就实现我们的要求,该示例当中的协议就分为语言层和通信设备层。
在ISO当中也将网络的从逻辑上划分为7层的模型:
将服务、 接口和协议这三个概念明确地区分开来, 概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯。
以上OSI 定的协议 7 层模型其实非常完善, 但是在实际操作的过程中, 会话层、 表示层是不可能接入到操作系统中的, 所以在工程实践中, 最终落地的是 5 层协议。
接下来来看TCP/IP当中提供的 5 层的层级结构, 每一层都呼叫它的下一层所提供的网络来完成自己的需求。
4.协议深入理解
以上我们已经了解了协议在网络通信当中的作用,那么接下来就来思考为什么在网络当中需要存在TCP/IP协议。
实际上即使是在单机当中也是会存在对应的协议,就例如其他的设备和内存进行通信就会有对应的内存协议;其他的设备和磁盘通信就会有磁盘相关的协议,只是在这些协议都是在主机当中各自的硬件当中,那么这时进行通信的成本以及问题就较少。
本质上网络通信和主机当中的通信本质上最大的区别就是主机之间的距离较远,那么这时原来计算机当中硬件的通信协议就不再适宜当前的环境,接下来就需要使用新的协议。
实际上在网络网络当中进行通信的设备也是冯诺依曼设备的其中一个部分,只不过是连接设备的数据线较长而已。
但是传输的距离变长了也就会引发出以下的问题,就是在传输的刚才当中容易引发数据丢失等的问题。
实际上以上的问题在是在不同的协议分层当中解决的。
那么在不同的操作系统当中又是如何实现网络的通信的呢,实际上在不同的系统当中虽然内核的实现等是不同的,但是网络的协议栈实现是相同的,这是因为网络是需要能实现跨平台的功能的,那么这也就使得不同的平台实现在内核当中的网络部分实现是需要相同的。有了这样的实现就可以让Linux、Windows不同的相同之间进行通信。
以上我们初步的了解到了协议实际上就是网络设备之间进行通信指定的协议,但是这只是初步的了解,那么接下来我们就需要从更加内核的角度来了解协议具体是什么。
实际上内核当中的协议就是对应的结构体,当两个进行通信的设备都定义出对应的结构体,就可以通过向结构体内填写对应的数据,之后再将对结构体发送进行通信的设备。
例如以下的示例:
以上再主机B当中能识别出date对象吗,实际上是完全可以的,因为在主机B当中也是定义出struct protocal 结构体,那么这时一定是能识别出来的。
因此协议简单来说就是主机之间都认识的结构化数据。
因为协议是分层的,那么在不同的层当中都有对应的协议,同层之间可以识别对方的协议。
实际上网络当中的协议是可以类比生活当中的快递单的,结构体就可以类比快递单当中的各个要填写的数据,那么两主机之间进行收发数据就可以看作是在收发快递单子。
5.网络传输基本流程
首先我们来了解局域网当中的主机是如何进行通信的,就类似在班级当中的两台电脑是如何进行通信的。
实际上在局域网当中进行网络的传输只需要MAC地址即可。
初始MAC地址
那么问题就来了这里提到的MAC地址是什么呢?
实际上MAC 地址(Media Access Control Address,媒体访问控制地址)是网卡的物理地址,用来在局域网(LAN)中唯一标识一台设备。
它相当于网络设备的“身份证”。
• MAC 地址用来识别数据链路层中相连的节点;
• 长度为 48 位, 及 6 个字节. 一般用 16 进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)
• 在网卡出厂时就确定了, 不能修改. mac 地址通常是唯一的(虚拟机中的 mac 地址不是真实的 mac 地址, 可能会冲突; 也有些网卡支持用户配置 mac 地址).
在之后数据链路层当中学习时我们将详细的了解,以下我们将大致的了解局域网当中数据是如何进行传输的。
例如以下的图示:
以下是在局域网当中基于MAC地址进行数据传输的基本特点:
• 以太网中, 任何时刻, 只允许一台机器向网络中发送数据
• 如果有多台同时发送, 会发生数据干扰, 我们称之为数据碰撞
• 所有发送数据的主机要进行碰撞检测和碰撞避免
• 没有交换机的情况下, 一个以太网就是一个碰撞域
• 局域网通信的过程中, 主机对收到的报文确认是否是发给自己的, 是通过目标mac 地址判定
以上我们在了解了局域网当中通信的原理,那么接下来就来集体的看看两个主机当中是如何进行数据的传输的。
实际上数据在两个主机之间的传输是需要通过以上的结构进行封装传输的,在每一层的栈当中都存在特定的协议,具体的封装格式就是如下所示:
在发送端每层当中都会将之前形成的有效载荷进行封装报头,在接受端每层当中会将对应的报文的有效载荷和报头进行拆解。
初识IP地址
以上我们知道了TCP/IP的作用是用来进行远距离的传输的,那么接下来我们就来初步的认知一下IP协议。
实际在IP协议当中是分为IPV4和IPV6的,在此我们进行学习的是IPV4,这也是在当前网络当中最常使用的,基本的内容如下:
• IP 地址是在 IP 协议中, 用来标识网络中不同主机的地址;
• 对于 IPv4 来说, IP 地址是一个 4 字节, 32 位的整数;
• 我们通常也使用 "点分十进制" 的字符串表示 IP 地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255
实际上和MAC类似,IP地址的作用也是用于在全球当中进行标识唯一的主机,那么这时你可能就会好奇了,那么之前不是有了MAC地址了吗,那么为什么还要有IP地址呢?
实际上IP和MAC的作用就是类似于在进行旅游的时候你的出发地是在福建而你的目的地是去江苏,那么你就需要途径浙江才能到达,这时江苏就类似IP,而浙江就类似MAC;在此江苏是你的最总目的地,浙江只是你的途经地。因此总结来说源IP和目的IP就可以认为是数据是从哪里来的,最后要到哪里,而源MAC和目的MAC就是用于标识上一站是从哪里来的,下一站要去哪里。
那么这时候你可能就会就会思考那为什么不直接使用IP来实现主机之间的通信呢,而是让IP基于用来的MAC地址进行对应的通信?
实际上这就涉及到了基于原来已经有的通信或者解决方案会让整体的设计会更加的简单。
以下就是两个主机之间进行传输整体传输结构
6.Socket编程预备
6.1 理解源IP和目的IP
在以上的学习当中我们就知道了IP地址是用于标识主机的唯一性,那么这时候我们就要来思考数据传输到主机是目的吗?
实际上数据是给人用的,就例如两个人在使用主机来聊天,那么在聊天的是人,接下来人又是如何看到对应的聊天文件的呢,使用的就是qq等的聊天软件,而当这些软件启动的时候实际上就是启动了对应的进程。因此总结来说就可以得到数据传输到主机不是目的,而是手段,传出给主机内的进程才是真正的目的。
但是接下来又有问题了,那就是在一台的主机当中是同时存在很多的进程的,那么当数据到达主机之后是如何转发到指定的进程当中的呢?
那么接下来就需要来标识主机当中进程的唯一性。
6.2 认识端口号
端口号(port)是传输层协议当中的内容。
端口号是一个2字节到16字节的整数,端口号是用于告诉操作系统当前的数据是要交给哪一个进程。那么有了端口号之后就可以使用 IP+端口号 来标识某主机当中的某一个进程。
注意:一个端口号只能被一个进程占用。
那么这时候就存在问题了,那就是端口号和之前我们了解到了进程ID有什么区别呢?
之前的进程ID也是可以在一台主机当中用来标识指定的进程,那么不是有了pid就可以确定指定的进程了吗,为什么还要引入port的概念。实际上在主机当中不是所有的进程都是会进行网络服务的,那么这时候在进行网络通信的时使用pid来确定进程,那么久会使得系统进程的管理和网络的管理强耦合。所以在设计的时候久没有使用这样的方案。
注:
在主机当中通常0~1023端口都是知名的端口号,HTTP,FTP,SSH等这些广为流传的协议,他们的端口号时固定的。
1024~65535时操作系统当中动态分配的端口号,客户端当中的程序就是从这个范围当中分配的。
6.3 理解Socket
以上我们有了IP和端口号久可以确定主机当中的指定进程,因此网络通信的本质实际上就是进程之间的通信。
在此将 ip+port 叫做socket套接字。
6.4 初识UDP和TCP
接下来先来初步的认识UDP和TCP的特点:
TCP:
1.传输层协议
2.有连接
3.可靠传输
4.面向字节流
UDP:
1.传输层协议
2.无连接
3.不可靠传输
4.面向数据报
6.5 网络字节序
通过之前的学习我们就了解了在计算机数据的存储是分为端和小端的,而在网络数据的传输当中规定将网络当中的数据传输都是需要按照大端来进行传输的。那么当一个主机当中进行网络数据的传输就需要向将主机序列当中的数据从小端转换为大端。
6.6 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通信的接口。
那么接下来就存在一个sockaddr的结构体,那么该结构体的属性是什么样的呢,该结构体的作用是什么呢?
实际上socket API是一层抽象的网络编程接口,使用于各种底层网络的协议,如IPv、IPv6等,各种网络协议的地址格式并不相同。
在此socket当中就提供一下的结构体来实现不同类型的通信,使用不同的结构体如下所示:
对于以上的结构体可以具体的关系如下所示:
本质上就可struct sockaddr本质上就是其他的父类,实现出C版的多态。
那么在此就可以看到sockaddr是所有地址结构的“外壳”,再bind,accep,connect函数当中的参数就是该类型,但实际上我需要传的是更具体地类型,并且将传地参数强制转换为该类型。
以下该结构体具体的属性:
struct sockaddr {sa_family_t sa_family; // 地址族,例如 AF_INET、AF_UNIXchar sa_data[14]; // 地址数据(通用存储区)
};
以下是sockaddr_in具体的属性:
struct sockaddr_in {sa_family_t sin_family; // 地址族:AF_INET(IPv4)in_port_t sin_port; // 端口号(网络字节序)struct in_addr sin_addr; // IP地址unsigned char sin_zero[8]; // 填充对齐
};
在此该结构体是专门用于IPv4网络通信,常用于TCP/UDP套接字,调用的时候需要将其进行强制类型的转换使其变为普通类型。
以下是sockaddr_un具体的属性:
struct sockaddr_un {sa_family_t sun_family; // 地址族:AF_UNIXchar sun_path[108]; // 文件路径,用于本地通信
};
以上的结构体正常使用于主机内进程的通信,在使用的时候也是需要将其强制类型转换为普通的类型。
以上sockst设计为以上的形式就是为了使用一套接口来满足不同场景的通信。