【网络】套接字socket编程预备知识

1.源IP地址和目的IP

计算机网络中的源地址和目的地址是用来标识网络中的不同主机的。

源地址是指发送数据包的主机的地址,而目的地址则是指接收数据包的主机的地址,在数据包传输过程中,每经过一个路中器感交换机,都会根据目的地址讲行转发,直到到达目的主机。

但是我们先来理解一下

**目标主机中存在很多进程,网络通信实际是不同主机中的进程在进行通信,**并非主机与主机直接通信

我们如果仅仅使用 IP 只能定位到目标主机,并且**目标主机不是最终目的地,最终目的地是目标主机里的某个进程,数据包传到B主机的传输层的时候,上层的进程很多,就不知道该传给上层的哪个进程了。**这个时候要想定位这个目标进程,需要依靠 端口号。

抛开网络其他知识,将信息从主机 A 中的进程 A 发送至主机 B 中的 进程 B,这不就是 进程间通信 吗?之前学习的 进程间通信 是通过 匿名管道、命名管道、共享内存 等方式实现,而如今的 进程间通信 则是通过 网络传输 的方式实现

进程间通信的本质是让两个进程看到同一份资源,那么今天这个同一份资源就是网络。

2.端口号(Port)

2.1.什么是端口号

数据链路和IP中的地址,分别指的是MAC地址和IP地址。前者用来识别同一链路中不同的计算机,后者用来识别TCP/IP网络中互连的主机和路由器。

在传输层中也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。

传输层会解析这个端口号,然后将数据传向特定进程。

端口号由其使用的传输层协议决定。因此,不同的传输协议可以使用相同的端口号。例如,TCP与UDP使用同一个端口号,但使用目的各不相同。这是因为端口号上的处理是根据每个传输协议的不同而进行的。

数据到达IP层后,会先检查IP首部中的协议号,再传给相应协议的模块。如果是TCP则传给TCP模块、如果是UDP则传给UDP模块去做端口号的处理。即使是同一个端口号,由于传输协议是各自独立地进行处理,因此相互之间不会受到影响。

2.2.IP+端口号标识进程

两台主机进行网络通信,首先需要定位。

我们知道IP能标识一台主机,端口号Port能标识主机里特定进程,那么IP+Port就能标识唯一的一个进程!!!!

实际上两台计算机通信其实是通过IP地址、端口号、协议号进行通信识别

我们可以一步一步去推断

  • 仅凭目标端口识别某一个通信是远远不够的。

如图所示,①和②的通信是在两台计算机上进行的。它们的目标端口号相同,都是80。 例如打开两个Web浏览器,同时访问两个服务器上不同的页面,就会在这个浏览器跟服务器之间产生类似前面的两个通信。在这种情况下也必须严格区分这两个通信。因此可以根据源端口号加以区分。

**上图中③跟①的目标端口号和源端口号完全相同,但是它们各自的源IP地址不同。**此外,还有一种情况上图中并未列出,那就是IP地址和端口全都一样,只是协议号(表示上层是TCP或UDP的一种编号)不同。这种情况下,也会认为是两个不同的通信。

**因此,TCP/IP或UDP/IP通信中通常采用5个信息来识别(这个信息可以在Unix或Windows系统中通过netstat -n 命令显示。) 一个通信。**它们是"源IP地址"、"目标IP地址"、"协议号"、"源端口号"、"目标端口号"。只要其中某一项不同,则被认为是其他通信。

也就是说,必须包含下面这些东西

网络传输中的必备信息组 [目的IP 源 IP || 目的 Port 源 Port]

  • 目的 IP需要把信息发送到哪一台主机
  • IP信息从哪台主机中发出
  • 目的 Port将信息交给哪一个进程
  • Port信息从哪一个进程中发出

这个就是套接字socket啊

2.3.端口号和PID的区别和关系

1.端口号 用于标识进程,进程 PID 也是用于标识进程,为什么在网络中,不直接使用进程 PID 呢?

  1. 进程 PID 隶属于操作系统中的进程管理,如果在网络中使用 PID,会导致网络标准中被迫中引入进程管理相关概念(进程管理与网络强耦合)
  2. 进程管理 属于 OS 内部中的功能,OS 可以有很多标准,但网络标准只能有一套,在网络中直接使用 PID 无法确保网络标准的统一性
  3. 并不是所有的进程都需要进行网络通信,如果端口号、PID 都使用同一个解决方案,无疑会影响网络管理的效率

所以综上所述,网络中的 端口号 需要通过一种全新的方式实现,也就是一个 2 字节的整数 port,进程 A 运行后,可以给它绑定 端口号 N,在进行网络通信时,根据 端口号 N 来确定信息是交给进程 A 的

  • 端口号与进程 PID 并不是同一个概念

进程 PID 就好比你的身份证号,端口号 相当于学号,这两个信息都可以标识唯一的你,但对于学校来说,使用学号更方便进行管理

  • 2.主机(操作系统)是如何根据 端口号 定位具体进程的?

这个实现起来比较简单,创建一张哈希表,维护 <端口号, 进程 PID> 之间的映射关系,当信息通过网络传输到目标主机时,操作系统可以根据其中的 [目的 Port] ,直接定位到具体的进程 PID,然后通过PID来对这个进程进行通信

整个过程就是端口号->PID->进程

  • 3.我们怎么知道我们要通信的那个进程的目标端口号?

这个端口号是公开的,所以一开始就能知道

  • 4. 一个进程可以绑定多个 端口号 吗?一个 端口号 可以被多个进程绑定吗?

端口号 的作用是配合 IP 地址标识网络世界中进程的唯一性,如果一个进程绑定多个 端口号,依然可以保证唯一性 (因为无论使用哪个 端口号,信息始终只会交给一个进程);但如果一个 端口号 被多个进程绑定了,在信息递达时,是无法分辨该信息的最终目的进程的,存在二义性

所以一个进程可以绑定多个端口号,一个 端口号 不允许被多个进程绑定,如果被绑定了,可以通过 端口号 顺藤摸瓜,找到占用该 端口号 的进程

如果某个端口号被使用了,其他进程再继续绑定是会报错的,提示 该端口已被占用

3.认识传输层协议------TCP/UDP

传输层是用户层下面的第一层,我们使用网络通信时调用的接口基本就是这层的

主流的传输层协议有两个:TCPUDP

两个协议各有优缺点,可以采用不同的协议,实现截然不同的网络程序,关于 TCP 和 UDP 的详细信息将会放到后面的博客中详谈,先来看看简单这两种协议的特点

TCP 协议:传输控制协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

字节流就像水龙头,用户可以根据自己的需求获取水流量

UDP 协议:用户数据协议

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

数据报则是相当于包裹,用户每次获取的都是一个或多个完整的包裹

关于可靠性

  1. TCP 的可靠传输并不意味着它可以将数据百分百递达,而是说它在数据传输过程中,如果发生了传输失败的情况,它会通过自己独特的机制,重新发送数据,确保对端百分百能收到数据;
  2. 至于 UDP 就不一样,数据发出后,如果失败了,也不会进行重传,好在 UDP 面向数据报,并且没有很多复杂的机制,所以传输速度很快

总的来说TCP可靠性高,速度相对慢,UDP可靠性低,速度快。

总结起来就是:TCP 用于对数据传输要求较高的领域,比如金融交易、网页请求、文件传输等,至于 UDP 可以用于短视频、直播、即时通讯等对传输速度要求较高的领域

如果不知道该使用哪种协议,优先考虑 TCP,如果对传输速度又要求,可以选择 UDP

4.网络字节序

4.1.高低位和高低地址

当我们了解了整数在内存中存储后,我们调试看⼀个细节:

cpp 复制代码
#include <stdio.h>
int main()
{
 int a = 0x11223344;
 
 return 0;
}

我们发现内存里存的怎么是44332211呢?

这个就和大小端有关系了

C语言中的大小端(Endianness)指的是字节顺序的不同方式,即如何将多字节的数据类型(如整数、浮点数)在内存中存储。

  • 数字的高低位

先来了解数字的高低位

cpp 复制代码
0x12345678
高位     低位

越靠近1这边的位就叫高位,越靠近8那边的位叫低位

  • 高低地址

什么是高地址,什么是低地址,举举例说明?

可以把主存看成一本空白的作业本,你现在要在笔记本上记录一些内容,他的页码排序是

cpp 复制代码
第一页 : 0x0000001
第二页 : 0x0000002
...
最后一页: 0x0000092

下面有两种使用情况

第一种:如果你选择从前向后记录(用完第一页,用第二页,类推)这就是先使用低地址,后使用高地址.

cpp 复制代码
0x0000001 -> 0x0000002-> ... -> 0x0000092

业内有这样表述:动态内存分配时堆空间向高地址增长,说的就是这种情况.

这个向高地址增长就是先使用低地址,后使用高地址的意思.

第二种:如果你选择从后往前记录(先用笔记本的最后一页,用完后使用倒数第二页,类推) 这就是先使用高地址,后使用低地址

cpp 复制代码
0x0000092 -> ... ->0x0000002 -> 0x0000001

业内表述:0xbfac 5000-0xbfad a000是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,栈空间是向低地址增长的.

这个向低地址增长就是先使用高地址,后使用低地址的意思.
高地址低地址容易与高位低位产生混淆.

我们从小就知道,数字是从左往右读的,这符合人类的普遍阅读习惯。

比如十进制数 65535,数位从高到低依次是万,千,百,十,个。对十进制数而言,最左边是高位数位,最右边是低位数位

我们类比到16进制数 0x12345678,也从左往右来阅读这个数。那么,对十六进制数而言,最左边就是高位字节,最右边是低位字节

4.2.大小端字序

首先,你可能需要对内存有一些基本的认识:

  1. 一个内存单元可以存储一个字节的内容,因此内存单元也常常被称为字节单元。
  2. 一个内存单元可以存储8个比特,即8个二进制数。但是,如果换算成16进制,一个内存单元仅能容纳2个16进制数。

如图所示,我们在画内存示意图 的时候,我们用一个绿色矩形表示一个内存单元,每一个内存单元都有一个内存地址,方便计算机的处理器找到这块内存单元。

另外,我们也习惯于内存地址将低地址端放在下面,高地址端放在上面。

这个习惯,我猜测可能与古人建房子的习惯类似,所谓"万丈高楼平地起",最先建的总是低层,然后再建高层。高层究竟建多高,这个总是不断发展和变化的,但是最底层总是从零开始,这个是相对稳定的。

还是以十六进制数 0x12345678 为例,当它以大端字节序 存储在内存中时,低地址端 0x0000 存储该数的高位字节 0x12;高地址端 0x0003 存储的是该数的低位字节 0x78

0x12345678小端字节序 存储在内存中时,低地址端 0x0000 存储该数的低位字节 0x78;高地址端 0x0003 存储的是该数的高位字节 0x12

4.3.从网络传输看待字节序

在网络出现之前,使用大端或小端存储都没有问题,网络出现之后,就需要考虑使用同一种存储方案了,因为网络通信时,两台主机存储方案可能不同,会出现无法解读对方数据的问题。

所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式

  • 对于char型数据只占一个字节,无所谓大端和小端。
  • 而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。

接收网络数据时按符合接受主机的环境接收。

简而言之,网络字节序是大端字节序。

发送数据时,将 主机字节序 转化为 网络字节序 ,接收到数据后,再转回 主机字节序 就好了,完美解决不同机器中的大小端差异,可以用下面这批库函数进行转换,在发送/接收时,调用库函数进行转换即可

cpp 复制代码
#include <arpa/inet.h>

// 主机字节序转网络字节序
uint32_t htonl(uint32_t hostlong); // l 表示32位长整数
uint32_t htons(uint32_t hostshort); // s 表示16位短整数

// 网络字节序转主机字节序
uint32_t ntohl(uint32_t netlong); // l 表示32位长整数
uint32_t ntohs(uint32_t netshort); // s 表示16位短整数

我们通常编写和阅读的电脑文件中,总是习惯于从上到下,从左到右。此时低地址在左边或者在上边,高地址在右边或者下边。

十进制数 40190 等于十六进制数 0x9cfe,十进制数 5222 等于十六进制数 0x1466。

我们聚焦抓包的内容的第 4 行,即起始地址为 0x0040,最后一个地址为 0x004f

  • 0x9cfe 的高位字节 0x9c 保存在低地址端 0x0045,低位字节是 0xfe 保存在高地址端 0x0046
  • 0x1466 的高位字节 0x14 保存在低地址端 0x0047,低位字节是 0x66 保存在高地址端 0x0048
相关推荐
乔巴不是狸猫17 分钟前
第11周作业
linux
Bessssss1 小时前
centos权限大集合,覆盖多种权限类型,解惑权限后有“. + t s”问题!
linux·运维·centos
silver6872 小时前
Linux 下的 GPT 和 MBR 分区表详解
linux
R-sz3 小时前
14: curl#6 - “Could not resolve host: mirrorlist.centos.org; 未知的错误“
linux·python·centos
code_abc3 小时前
Shell 脚本编程基础:变量
linux
星如雨落3 小时前
Linux shell脚本对常见图片格式批量转换为PDF文件
linux·shell
冰红茶兑滴水3 小时前
云备份项目--工具类编写
linux·c++
董健正3 小时前
centos制作离线安装包
linux·运维·centos
猫猫的小茶馆3 小时前
【数据结构】数据结构整体大纲
linux·数据结构·算法·ubuntu·嵌入式软件