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

掘根 2024-09-08 10:07:02 阅读 61

1.源IP地址和目的IP

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

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

        但是我们先来理解一下

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

 我们如果仅仅使用 <code>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地址”、“协议号”、“源端口号”、“目标端口号”。只要其中某一项不同,则被认为是其他通信。

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

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

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

这个就是套接字socket啊

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

主流的传输层协议有两个:TCP 和 UDP

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

TCP 协议:传输控制协议

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

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

UDP 协议:用户数据协议

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

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

关于 可靠性

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

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

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

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

 4.网络字节序

4.1.高低位和高低地址

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

#include <stdio.h>

int main()

{

int a = 0x11223344;

return 0;

}

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

这个就和大小端有关系了

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

数字的高低位

先来了解数字的高低位

<code>0x12345678

高位 低位

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

高低地址

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

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

第一页 : 0x0000001

第二页 : 0x0000002

最后一页: 0x0000092

下面有两种使用情况

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

0x0000001 -> 0x0000002-> … -> 0x0000092

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

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

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

0x0000092 -> … ->0x0000002 -> 0x0000001

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

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

高地址低地址容易与高位低位产生混淆.

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

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

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

4.2.大小端字序

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<code>#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位短整数

h代表主机字节序,n代表网络字节序。l表示“长”(4字节)整数,s表示“短”(2字节)整数。 

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

 我们通常在编写和阅读的电脑文件的时候,总是习惯于从上到下,从左到右。

此时低地址在左边或者在上边,高地址在右边或者下边。

我们先看看低地址在左边,高地址在右边的。

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

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

0x9cfe 的高位字节 0x9c 保存在低地址端 0x0045,低位字节是 0xfe 保存在高地址端 0x00460x1466 的高位字节 0x14 保存在低地址端 0x0047,低位字节是 0x66 保存在高地址端 0x0048

我们再看看低地址在上面,高地址在下面的



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。