【Linux网络】第二篇:套接字(一)与UDP编程

      最后更新:2022-06-10 14:52:08 手机定位技术交流文章

      目录

      • 预备知识
        • 源IP与目的IP
        • 源 MAC地址及目的 MAC地址
        • 端口号
      • 主机和网络序列
      • 数据类型和接口地址的相关功能
        • 通用接口API
        • 校舍结构
      • IP地址转换函数
      • 创建插座-插座函数
      • 命名插座-绑定函数
      • UDP数据读写 - recvfrom,∏o函数
      • UDP服务端及客户端实现
        • UDP服务端实现
        • UDP客户端实现
      • 功能测试

      在这里插入图片描述

      预备知识

      源IP与目的IP

      每个计算机的公共IP是唯一的,如果本地主机和同类主机要通信,然后端口主机的IP地址将是数据传输的目的的IP地址。仅仅知道目标IP地址是不够的,当主机接收数据并需要对主机作出回应时,所以最终主机需要知道本地主机的IP地址,即源IP。

      • 来源IP:它是从哪里来的
      • 目标IP:去哪里

      源 MAC地址及目的 MAC地址

      大多数网络服务是跨域网络,同时跳到多个路由器最终到达目标主机。

      传输开始时的源MAC地址是本地主机的MAC,目标MAC是下一跳路由器的MAC。 源MAC的最后跳跃是最后路径路由器的MAC,目标MAC是终端主机的MAC。

      因此数据的网络地址有两组地址:

      • 源IP地址和目标IP地址在数据传输中基本上不变(有某些特殊情况,例如在数据传输中使用网络技术,其中源IP地址改变,但至少目标IP地址不变)。
      • 源 MAC地址及目的 MAC地址,这两个地址是一直在发生变化的,因为在数据传输的过程中路由器不断在进行解包和重新封装。

      端口号

      一个端口号只标识一个进程在主机上。

      IP识别公共网络上的唯一主机,而端口代码用于识别主机上的唯一进程,而IP+端口代码可以实现整个网络的唯一进程,达到网络传输点到点服务。

      • 端口号与PID

        两者之间有什么区别,两者都是唯一能够在主机上识别进程的对象?

        在一个主机上有许多进程,但并非所有过程都进行网络传输。执行网络请求的过程需要使用一个端口号码来识别唯一性,因此,端口号是面向网络的。而PID是识别当前主机的进程的独特性。面向操作系统服务。两者是表达过程在不同层次上的独特性的机制。正如社会中的公民使用身份证来识别独特性一样,独特性是用代码表示的单位。

      • 源端口及目的地端口

        对于两个主机进行通信,只有主机的IP地址才能帮助我们找到网络上的主机,但我们还需要找到提供主机上的相应服务的进程,可以通过与主机进程关联的端口号码,目的地端口号码找到。

        同样,最终主机需要对发送者作出回应,通过源IP地址找到发件人的主机,找到主机还不够,还必须查明在相反的主机上,哪个进程启动了请求,答复方需要通过与请求开始的进程有关的港口号码找到过程,也就是源端口号,然后你就可以回答。

        • 源端口编号:由发送者主机服务进程约束的端口编号,以确保接收者能够找到相应的服务
        • 目标端口编号:与接收主机的服务进程挂钩的端口编号,以确保发送者能够找到相应的服务

        接口通信的本质:在网络中进行进程间通信。 如上图所示,网络通信是一个在两个主机上进行通信的过程。

      主机和网络序列

      现代CPU的构建者可以至少装载4个字节(考虑32位机),即一个整数。因此这些四个字符在内存中排序的顺序将影响到它被载入阵列的整数的值,这就是字节序问题。序列被分成大环形和小环形。

      • 大端序列:存储在内存中较低地址的高级整数。
      • 小端序列:一个高级别整数存储在存储器的高位置。

      在这里插入图片描述

      • 检查当前主机的字符序列:联盟

      在这里插入图片描述

      当格式化数据使用不同的字节序列直接传输到两个主机之间时,接收机必须错误地解释它。

      解决这个问题是:TCP/IP协议允许使用网络数据流的大型序列。

      因此,发送器总是使用大端序列,因此接收器可以根据它使用的序列(小端序列、大端序列)决定是否转换接收数据。

      因此,大端序列也被称作网络序列,它为所有接收机提供了一个正确的解释接收的格式化数据的保证

      Linux提供下列库函数来转换网络序列和主机序列:

      • h表示主机(主机),n表示网络(网络),l表示长(32位),s表示短(16位);
      • htonl表示将32位整数从主机序列转换为网络序列;
      • 如果主机是一个小端节点序列,这些函数在转换后返回参数;
      • 主机是一个大端节点序列,函数不转换并返回未锁定的参数。

      这些四个函数中,长整数函数通常用于转换IP地址,而短整数函数则用于转换端口数(当然,任何通过网络传输的格式化数据都应该使用这些函数来转换序列)。

      数据类型和接口地址的相关功能

      通用接口API

      • 头文件
      • 创建通信端口并返回索克文件描述符(TCP/UDP,客户端+服务器)
      • 绑定端口号码(TCP/UDP,服务器)
      • 开始监控接口(TCP服务器)
      • 接受请求(TCP,服务器)
      • 建立连接(TCP,客户端)

      校舍结构

      索克地址是网络编程接口中的结构socketaddr,其定义如下:

      • socketaddr结构

      地址家族类型sa_family_t的实类型没有签名短

      1. sa_family成员是地址家族类型的一个变量,与地址家族类型和协议家族类型相符。
      2. sa_data成员用于存储索克地址值,不同的协议地址值具有不同的含义和长度。

      通用协议家族(也称为域)与表所示的相应地址家族相符:

      协议族 地址族 描述 地址值含义和长度
      PF_UNIX AF_UNIX UNIX本地协议组 文件的路径名称,最大108字节长
      PF_INET AF_INET TCP/IPv4协议家族 16位元端口及32位元IPv4地址,6字节
      PF_INET6 AF_INET6 TCP/IPv6协议家族 16bit端口号码和32bit流量标识符,128bitIPv6地址,32bit范围ID,26字节

      PF_*和AF_*定义在bits/socket.h在标题中,后者具有与前者相同的值,所以两者通常是混合的。

      Linux为每个协议家族提供了专门的接口地址结构:

      在跨网络通信中,我们需要传递端口号和IP地址,因此我们提供了特定网络的插座结构。
      IPv4和IPv6地址格式定义在netinet/in.h特别使用IPv4地址sockaddr_in结构表示,包括IPv6地址的16位端口编号和32位IP地址sockaddr_in6它包含16位端口编号和128位IP地址。

      • sockaddr_in结构
      • sockaddr_in6结构

      该接口不仅支持网络进程间通信,而且支持本地进程间通信(域间接口)。

      该区域协议家族的专门接口地址结构:

      • sockaddr_un结构

      在这里插入图片描述

      所有专门的插座地址类型的变量需要转换为一般的插座地址类型 sockaddr (强制转换是可能的)。

      事实上, sockaddr和 sockaddr_in之间的转变很容易理解。因为他们开头一样,内存大小也一样,但是 sockaddr和 sockaddr_in6之间的转换有点混乱,事实上,你可能会被这个结构所占据的记忆弄糊涂了,这些结构基本上是通过指针的形式传递的参数,以 functionbind() 为例。这个函数总共接收了三个参数,第一个监视文件描述符,第二个参数是 sockaddr*类型,第三个参数是输入指针原始结构的内存大小。下面是两个信息,不管原来的结构如何改变,因为他们的头都是一样的,那是16个家庭,所以我们也可以处理这个头。

      这些插座函数的参数应设计为void*类型用于接受各种类型的指针,但索克API实现基于ANSI C标准化,它尚未空*,因此这些函数的参数由struct sockaddr*类型表示:

      IP地址转换函数

      上述IP地址为32位,人们通常习惯使用可读字符串来标识IP地址,例如,十进制点字符串代表IPv4地址。此外,IPv6地址由16英寸的字符串表示。但是在编程中,我们需要把它们转换成整数(二进制数),然后才能使用它们。

      下列三种函数可用于以点点 दशमलव字符串表示的IPv4地址和以边界序列整数表示的IPv4地址的转换:

      in_addr转换字符串的函数

      • inet_addr函数
        向网络序列IPv4地址传输点-by-point十进制字符串的IPv4地址不能返回INADDR_NONE(-1)。

        缺陷:即当IP存在时,该函数将认为它是无效的IP地址

      • inet_aton函数
        完成与inet_addr相同的函数,但将转换结果存储在参数inp指示的结构中。

        成功返回1,失败返回0。

        inet_aton函数与上述函数之间的区别是,他认为它是有效的,他不会责怪看似特殊的IP地址。

      • inet_pton函数

        该函数与前两个相同,并适用于IPv4地址和IPv6地址。

        它将由字符串表示的IP地址src(由点10位字符串表示的IPv4地址或由16位字符串表示的IPv6地址)转换成由网络序列整数表示的IP地址,并将地址存储在Addptr指向的内存中。

        家族参数指定地址家族,可以是AF_INET或者AF_INET6

        成功返回1, 未能返回0并设置errno.

      转换字符串的in_addr函数

      • inet_ntoa函数

        将由网络字符串序列整数表示的IPv4地址转换为由点数字符串表示的IPv4地址。

        注意:该函数使用静态变量存储转换结果,函数的返回值指向静态内存,使得第二次调用的结果超过了以前调用的结果。

      • inet_ntop函数

        第一个三个参数与inet_pton参数相同。最后一个参数cnt指定目标存储器的大小。以下两个宏帮助我们指定大小(IPv4和IPv6):

        inet_ntop函数由调用者自己提供一个缓冲储存结果,该结果是线程安全的。

      创建插座-插座函数

      Linux的设计理念是,所有东西都是文件。 索克不是例外,它是可读、可写、可控制和可关闭的文件描述符。

      • 参数

        • domain指示系统使用哪些基本协议家族。对于TCP/IP协议家族,该参数应设置为AF_INET(IPv4),AF_INET6(IPv6);该参数应为空间插座(在这个区域)设置为AF_UNIX。其他的协议族如下,参考man手册:

          在这里插入图片描述

        • type指定服务类型,服务类型主要有SOCK_STREAM服务(流服务),和SOCK_DGRAM服务(数据报服务)。对于TCP/IP协议家族,其值取 SOCK_STREAM 表示传输层使用TCP协议, 取 SOCK_DGRAM 表示传输层使用UDP协议。

          在这里插入图片描述

        值得注意的是,Linux内核版本2.6.从17中,类型参数可以接受与上述服务类型相符的值和下列两个重要符号:SOCK_NONBLOCKSOCK_CLOEXEC前者表示新创建的接口设置为非锁定,并且在调用叉子子进程时,接口在子进程中关闭。

        • protocol该参数在选定特定协议时,是由最初两个参数组成的协议集中,但这个值通常是唯一的一个(前两个参数已经完全确定了它的值)。
      • 返回值

        成功返回一个索克文件描述符, 未能返回 return-1, 并设置errno.

      索克底部做了些什么?

      每个工艺都有工艺控制单元PCB(task_struct其中有一个指向结构的标记struct files_struct结构包含一个文件描述表fd_array第一个三个子标签指的是标准输入、标准输出和标准错误流。 第一个创建的文件(包括插座)将被分配到3的第一个子值。

      在这里插入图片描述

      每一个struct file结构包含文件的信息(属性、操作函数和文件缓冲器等),属性由struct inode结构体维护,struct file_operations它包含处理文件的函数指针,普通文件的文件缓冲器是磁盘,网络传输文件是网络卡。

      插座与应用程序一起创建。

      该应用程序有一个插座组件,在应用程序启动时,将调用接口应用程序创建接口,协议堆栈创建一个基于应用程序的应用程序的接口:首先分配一个接口所需的内存空间,这个步骤相当于制备控制信息的容器,但只有容器才能真正发挥作用,所以你还需要把控制信息放在容器里;如果你不申请创建索克所需的内存空间,你创建的控制信息没有存储的地方,所以分配内存空间,无法将控制信息丢失.到目前为止, Socket 的创建已经完成。

      在 Socket 创建完成后,将一个 Socket 文件描述 returned to the application, 相当于一个分开不同 Socket 的数字卡。 根据这个描述,应用程序在接收来自托管协议堆栈的数据时需要提供这个描述。

      命名插座-绑定函数

      当创建一个接口时,我们指定了地址家族和数据格式,但没有指定用于地址家族的特定接口地址(sockaddr)。

      绑定到一个索克地址的索克文件(文件描述符)称为索克名称。

      • ?在服务器程序中,我们通常想命名插座,明确分配端口号和IP地址到插座地址,然后把它绑到插座上。因为只有在命名后,客户端才能知道如何连接到服务器(例如服务器打开一个插座空间,将网络中的空间的位置传达给想要访问的客户。
      • ?在客户端程序中,通常不需要命名插座,操作系统秘密分配插座地址并自动绑定它.它的码头将是一个任意的自由码头.当然,你也可以明确地命名插座,但在此之前,其他进程可以使用固定的端口号码,不推荐。
      • 函数声明
      • 函数:bind将由 my_addr指定的索克地址分配给一个未命名的sockfd文件描述符(服务器的索克地址在绑定前已预定义),addrlen参数指示插座地址的长度(因为不同的协议有不同的插座地址长度)。

      • 返回值:成功返回0,返回-1失败,设置Errno。

        • EACCES: 绑定地址是保护地址,只有根才能访问它,如果普通用户接口绑定到一个已知的服务端口(0~1023),这个错误将返回。
        • EADDRINUSE:绑定地址正在使用,例如绑定索克到位于 TIME_WAIT状态的索克地址。

      UDP数据读写 - recvfrom,∏o函数

      系统要求在插座编程接口中读写UDP数据报告如下:

      重新读sockfd上的数据,buf指定的读取缓冲器的位置(程序员需要预先保留空间),len对于读缓冲区的大小,但此读缓冲不能保证接收UDP消息的顺序与发送UDP消息的顺序一致。如果缓冲区满了,UDP数据将被丢弃,当它再次到达.因为UDP通信是一个无连接的概念,所以每次我们阅读数据时,我们需要得到发送器的插座地址,即通过输出型参数src_addr获取接口地址(相反端的IP和端口号码),addrlen该参数指定插座地址的长度。

      sendto往sockfd上插入数据, 缓冲器和镜头参数分别指定写字缓冲器的位置和大小.dest_addr参数指定接收机的接口地址,addrlen该参数指定地址的长度。

      flag参数提供了数据传输的额外控制,他可以使用下面的某个或多个选项登录或:

      在这里插入图片描述

      recvfrom sendto的旗帜意义与 recv and send一样,后者是接收TCP数据的函数。

      • 返回值

      在成功时返回实际读取数据的长度,他可能比我们所期望的长度更小,所以我们可能需要多次调用recv,你可以阅读完整的数据。recvfrom的返回值是0,这意味着两个通信都关闭了连接。从错误发生时返回-1,并设置errno。

      如果成功,则返回实际写入数据的长度,如果失败,则返回 -1,并设置errno。

      UDP服务端及客户端实现

      我们分别使用两个类别密封服务端和客户

      建设UDP的具体沟通过程如下:

      在这里插入图片描述

      1. 为编写服务器端UDP程序,bind是一个重要的步骤,它告诉操作系统服务器从哪个端口企业想要获取数据。
      2. 客户端作为通信的发起者,没有绑定操作,系统将自动帮助我们选择一个空端口发送数据和记录端口号码信息。 当然,这不是说你不能启动绑定,但我们不推荐它,因为当它实际上运行时,我们不一定有空端口号码来绑定。

      下面将分别实现UDP-based服务器和客户端

      所建立的项目文件如下:

      在这里插入图片描述

      Makefile文件如下:

      首先,我们实施了一个简单地传输数据的UDP程序

      UDP服务端实现

      服务器的IP需绑定INADDR_ANY(String, value 0)表示一个未定义的地址,或“所有地址”,或“任何地址”。

      一般而言,如果你想设置一个网络服务器应用程序,然后您需要通知服务器操作系统:请在地址 xx.xx.xx.investigate 上一个终端 xx on yy,给我寄个监视包。这个过程,您通过 bind()系统完成了调用。也就是说,您的程序被绑定到服务器的特定地址,或者, 例如, 将服务器地址上的一个端口作为已经使用的端口.

      服务器有多个网络卡(每个网络卡都有不同的IP地址),以及您的服务(在UDP端口上,或在tcp端口上听),由于某种原因:您服务器的操作系统可能随时可以增加或减少IP地址,也可以避免在调用 bind()时在服务器上识别任何网络端口(网络卡)的麻烦,告诉操作系统,“我需要在yy端口上听,这个端口将所有发送到服务器上,任何网络卡/IP地址接收数据,都是我处理的。”这时候,服务器程序收听此地址。

      • udp_server.hpp
      • udp_server.cc

      UDP客户端实现

      访问服务器的客户端的IP地址是: – 是返回地址,指用于测试使用的本地机器。

      该地址被分配到路由返回接口(本地路由返回)。loopback是一个特殊的网络接口(被理解为虚拟网络卡),它用于机上各种应用之间的网络交互。只要操作系统的网络组件正常,反弹可以奏效。

      • udp_client.hpp
      • udp_client.cc

      功能测试

      首先在执行客户端时执行服务器端,然后从客户端转移数据:

      在这里插入图片描述

      • 指示netstat查看网络状态

        • -a在所有连接中显示插座
        • -p显示使用插座的程序识别代码和程序名称
        • -l只列出正在监视的服务状态
        • -t显示TCP传输协议的连接状态
        • -u显示UDP传输协议的连接状态
          -i显示网页界面信息表格
        • -r显示路由表信息
        • -n直接使用IP地址,而不是通过域名服务器
      • netstat -nlup查看UDP插座程序

      在这里插入图片描述

      — end —

      青山不改 绿水长流

      本文由 在线网速测试 整理编辑,转载请注明出处,原文链接:https://www.wangsu123.cn/news/26995.html

          热门文章

          文章分类