当前位置: 首页 > article >正文

Linux网络编程——网络编程初识、UDP套接字简单了解

目录

前言

1、源ip地址和目的ip地址

2、端口号 和 socket套接字

3、源端口号和目的端口号

4、认识TCP协议的基本特点

5、UDP协议的基本特点

6、网络字节序

7、socket编程接口 

struct sockaddr 

使用

简单的UDP接口


前言

上篇文章主要介绍的是网络通信方面的一些知识,也算是为以后的学习先做一下铺垫。从本章开始我们就要正式进入网络编程的学习了。

但是在开始网络编程之前,我们还需要进行一些准备工作,需要先了解一些概念。

1、源ip地址和目的ip地址

从上篇文章我们知道不同的局域网的主机之间的通信是靠ip地址进行的,所以 源ip地址即指发送的主机的ip地址目的ip地址指接收主机的ip地址。就像车辆的始发站和终点站一样,即使中途经过了好多地方,但是一般情况下,终点站是不会变的。

2、端口号 和 socket套接字

网络通信可以看作是两台主机在通信,不过在通信过程中我们不仅仅需要考虑两台主机之间的数据交互。

网络通信、数据交互说到底都是为用户提供的交互,无论是朋友之间的线上聊天还是网络游戏。在上篇中我们讲到网络协议栈,我们知道网络层和传输层是属于操作系统的,更上层的应用层才是属于用户使用的。

虽然操作系统是由用户操作的,但是在本质上,在操作系统层面,用户的身份是由程序体现的,要实现通信,程序一定是在运行中,也就是 进程。那么在操作系统看来,两台主机进行通信就是通过运行应用层程序进行通信,也就是说是进程在通信和交换数据

所以网络通信的本质实际上是进程间的通信,只不过是不同主机之间的进程。而端口号,其实就是用来 表示唯一进程的标识符,是传输层协议的內容

所以对当前主机来说,ip地址保证了主机的唯一性,端口号(PORT)保证了主机内进程的唯一性。

当端口号和IP地址按照如下方式使用就可以标识到 网络中的唯一进程

IP地址:PORT    ,该组合也被称为 socket套接字

注:端口号是一个2字节16位的整数,一个端口号只能被一个进程占用,即一个端口号只能标识一个进程。

那么我们思考一个问题,就是在系统中每个进程都有自己的PID作为自己的唯一标识符,为什么还需要端口号呢?

       首先,利用进程的PID来进行标识网络中的唯一进程理论和操作上当然是可以实现的,但是这样做不好,因为进程的PID就是来方便进程管理的,进程并不都是需要进行网络通信的,如果使用了PID来标识网络中的唯一进程,就相当于将操作系统和网络强耦合起来了,这并不好。

3、源端口号和目的端口号

源IP地址和目的IP地址是用来确定主机的,但是网络通信的本质实际上是进程间的通信,所以在网络间通信的时候,除了需要确定对应的主机IP还需要知道主机上的进程,因此就需要知道 源端口号和目的端口号来确定源主机的发送进程和目的主机的进程。

源IP:源端口号 目的IP:目的端口号 就组成了一个socket对。

4、认识TCP协议的基本特点

 先对TCP的传输控制协议做一个简单的认识

  1. 传输层协议
  2. 有链接。如我们使用的SSH连接服务器主机,SSH使用的就是TCP协议,必须要与服务器主机连接上之后才能正常地与服务器主机进行通信。
  3. 可靠传输。在传输数据时,TCP会使用各种防止数据丢失或者损坏,但是并不表示TCP传输一定是可靠的。
  4. 面向字节流。指它将应用层传递下来的数据视为一个无结构的字节序列,即字节流。这意味着应用程序通过TCP发送数据时,这些数据可以被视为是连续的比特或字节序列,而不是一个个独立的消息或数据包。

5、UDP协议的基本特点

  1. 传输层协议
  2. 无连接。指通信前,双方不需要建立连接,如直播和短视频等。
  3. 不可靠传输。UDP协议不会保证数据发送的完整性,如果在传输过程中数据出现丢失或者损坏,UDP也不会管,更不会重新发送。例如直播的时候 出现的卡顿模糊等。
  4. 面向数据包。指在传输数据的时候,是将数据视为独立的、不可分割的数据包或数据报进行传输。

6、网络字节序

内存中的多字节数据相对于内存地址都有着大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端和小端之分。

先来回忆一下什么是大端和小端,我们以内存中的存储数据为例,长字节数据在内存中存储时,不同的平台可能会有着不同的存储方式。

  1. 大端字节序:数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。
  2. 小端字节序:数据的低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。这里需要注意的是该存储方式并不是将数据倒序存储,而是以字节为单位,从低位数据到高位数据存储到内存的低地址处到高地址处。

所以数据的存储方式不同就导致我们在传输数据时读取数据的方式也不同。

使用大端字节序的平台,读取数据的时候需要从内存的低地址处开始读到高地址处。

而小端字节序的平台,读取数据的时候需要从内存的高地址处开始读到低地址处。

需要注意的是 ,CPU在读取数据的时候默认是从有效低地址开始的,所以小端字节序的平台默认读取到的数据顺序是错误的。

所以我们在网络通信中网络字节流的地址就需要有规定 

  • 发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出。
  • 接受主机把从网络中接收到的数据也是按照从低到高的顺序保存在接收缓冲区中。

因此我们规定网络字节流的地址应该是 先发出的是低地址,后发出的是高地址。并且TCP/IP协议规定,网络数据流应该采用大端字节序,即低地址处是高位字节不论当前的主机是大端机还是小端机都要按照TCP/IP规定的这个网络字节序来发送/接收数据。如果当前主机是小端机,那么就需要先将数据转成大端,否则就忽略。因此,大端字节序也称为 网络字节序

经过对上面的对网络字节流的规定,我们现在知道网络中的所有数据都是大端字节序,那么在接收端读取这些数据的时候就按照大端字节序的方式读取,或者对于小端机器先将接受到的数据的存储顺序调整成小端字节序再读取。

系统中提供了 主机字节序(Host Byte Order) 和 网络字节序(Network Byte Order) 的转换接口如下:

  • htonl():将32位整数从主机字节序转换为网络字节序("host to network long")。
  • htons():将16位整数从主机字节序转换为网络字节序("host to network short")。
  • ntohl():将32位整数从网络字节序转换为主机字节序("network to host long")。
  • ntohs():将16位整数从网络字节序转换为主机字节序("network to host short")。

转换之后会通过返回值返回,如果本机就是大端字节序存储,则这些接口就不会发生转换。

7、socket编程接口 

先见一见一些常见的API(应用程序编程接口,Application Programming Interface)接口

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

// 发送报文 (UDP)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

// 接收报文 (UDP)
ssize_t recvfrom(int socket, void* restrict buffer, size_t length, int flags, struct sockaddr* restrict address, socklen_t* restrict address_len);

我们可以看到大部分的接口的参数都包含一个参数类型: struct sockaddr 


struct sockaddr 

sockaddr 是一个结构体。套接字通信在设计的时候不仅实现了网络间通信还实现了本机内进程的通信。所以套接字我们通常分为两类:网络套接字域间套接字,分别用于网络通信和本地(域间)通信。

域间套接字通信可以实现本地进程的通信,与我们之前介绍的管道通信、共享内存通信的功能大致相同。域间通信也可以称为双向管道。其使用起来要比网络套接字简单,因为不需要跨主机进行通信,所以没有IP,也没有端口号的概念。在使用时,只需要提供一个文件的路径,与命名管道听起来是一样的,但是操作是有着区别的。

思考:套接字的设计分成了两类,那么使用套接字实现进程通信也分别实现了网络通信和本机通信两种吗?

       实际上不是的,要是设计成两种就太过于麻烦了。既然都是套接字,那么只需要将两种的通信接口统一起来就好了。那么该如何做呢?两种通信所需要的资源都是不一样的

  • 网络间的通信需要的资源是 IP地址、端口号等资源,所以设计了 struct sockaddr_in 等结构来描述网络通信所需要的资源。
  • 本机间进程的通信需要的资源是 路径名 等资源,所以设计了 struct sockaddr_un 结构体来描述域间通信所需要的资源。

struct sockaddr_in前16位是一个宏AF_INET

struct sockaddr_un前16位也是一个宏AF_UNIX

既然我们统一了接口,但是不同的通信所需要的资源是不同的,所以同一个接口怎么接收不同类型的资源呢?

             接口既然要接收不同类型的数据,那么就不能将接口的参数设置成 上面的具体的描述的资源的结构体。而且在上面接口参数中,我们也没有看到 struct sockaddr_in 和 struct sockaddr_un 类型的,只有一个 struct sockaddr。下面我们研究一下这个结构体是什么。

我们将他们三个放在一起对比着看,可以发现他们有着共同点,这三个结构体的首16位都是 地址类型。

 地址类型,不同的宏可以区分协议 以及 区分网络通信还是域间通信。

IPv4和IPv6是互联网协议(IP)的两个主要版本,它们定义了如何在网络上定位设备并进行数据包的路由。IPv4使用32位地址,IPv6使用128位地址.

AF_INET 和AF_INET6 都是用来指定IP地址类型的两个常量,AF_INET 用于 IPv4  AF_INET6 用于 IPv6

如果是域间通信则地址类型为AF_UNIX.

再看看 sockaddr_in 结构体中都是什么

所以 struct sockaddr实际上是设计出来的一个抽象的中间的结构体,就是为了让接口能接收不同类型的数据资源

使用

  1. 在使用该接口的时候,需要先将 struct sockaddr_in* 或者 struct sockaddr_un* 等类型的结构体先强转成 struct sockaddr*然后再传给接口使用。这是因为上面所说的他们的首16位都表示地址类型。
  2. 接口接收到数据之后,会根据 前16 的地址类型来区分协议及通信方式,也会根据地址类型判断出数据的原结构类型,然后将 sockaddr 结构体强转回原结构体类型以获取完成的信息。

简单的UDP接口

学习了上面的知识,我们在演示简单的UDP通信接口前再来介绍一个接口:int socket()

该接口的作用是 创建一个 通信的端点

成功则返回一个新的socket文件描述符(一个整数),这个描述符用于标识网络连接实际上在操作系统中,套接字操作都是通过文件描述符来实现的

失败返回 -1并设置全局变量 errno以指示错误类型。

下来看看三个参数:

1、int domain

该参数用于指定一个通信域,它选择将用于通信的协议族。这些家族定义在 <sys/socket.h> 头文件中。目前理解的格式包括如下:

  • AF_UNIX(或 AF_LOCAL):本地通信协议族,用于同一台机器上的进程间通信(IPC)。使用文件系统的路径名作为地址。
  • AF_INET:IPv4 网络协议族,用于通过互联网协议版本4进行通信。这是最常用的类型之一,适用于广域网和局域网中的通信。
  • AF_INET6:IPv6 网络协议族,用于通过互联网协议版本6进行通信。随着IPv4地址空间的耗尽,越来越多的应用开始支持IPv6。
  • AF_IPX:Novell IPX 协议族,用于NetWare环境下的网络通信。不过,由于IP逐渐成为主流,这种协议族现在较少见。
  • AF_PACKET:直接访问低层网络协议。这个选项通常用于需要直接处理数据链路层协议的应用程序,比如网络嗅探器。 

2、int type

在创建socket时,type 参数指定 socket 的类型,它决定了通信的语义。当前定义的主要类型包括

  • SOCK_STREAM:提供一个有序、可靠、基于连接的字节流服务。数据被视为无边界的数据流,通常使用TCP协议。这意味着你可以发送和接收任意大小的数据流,并且这些数据将按照发送顺序到达另一端。
  • SOCK_DGRAM:支持无连接的数据报服务,每个数据报都是独立的,具有固定的最大长度,并且不保证送达顺序或可靠性。通常使用UDP协议。这种类型的socket适合于那些对实时性要求较高但对可靠性要求不那么严格的场景。
  • SOCK_RAW:允许直接访问底层网络协议。这类sockets主要用于高级用户、开发者或需要直接处理IP数据包的应用程序。它们可以用来构建自定义的协议或者进行网络研究等。即

    原始套接字. 使用此套接字, 通信可以直接绕过传输层的协议, 直接访问IP协议.不过, 绕过传输层协议, 就表示需要自己实现一些传输协议的内容. 一般用于网络诊断等方面

  • SOCK_SEQPACKET:提供了一个面向数据报的接口,但是数据报是有序的并且可靠的,类似于SOCK_STREAM,但是数据边界被保留(不像SOCK_STREAM那样是一个无结构的字节流)。并不是所有协议族都支持这种类型。
  • SOCK_RDM:一种可靠的分组交付服务,保证消息按顺序到达且不会重复或丢失,但并不保证消息的边界。这个类型不如其他几种常见,支持也较为有限。 

3、int protocol

该参数用来选择协议类型,此参数的选择与第二个参数密切相关。

如 type 选择了 SOCK_STREAM 此参数就需要传入 IPPROTO_TCP ,即选择了TCP协议。

但是实际上我们不用手动使用宏去选择协议,网络通信时,选定type并且只需要使用一种协议时, 该参数可以直接传入 0 ,表示使用默认协议,其实就是操作系统根据前面的参数自动选择最合适的协议。


http://www.kler.cn/a/580197.html

相关文章:

  • 三次握手与四次挥手
  • vscode arm拓展 keil acm5 到acm6迁移
  • 【透视国家的三维棱镜:技术、制度与文化的解构与重构】
  • 知识库Dify和cherry无法解析影印pdf word解决方案
  • 数据安全基石:备份文件的重要性与自动化实践
  • 【iOS逆向与安全】sms短信转发插件与上传服务器开发
  • Rust 模式匹配中的可反驳性与不可反驳性
  • Docker数据管理,端口映射与容器互联
  • 【网络安全工程】任务11:路由器配置与静态路由配置
  • 用Python实现PDF转Doc格式小程序
  • 一篇文章巩固技术-----设计模式
  • 安固软件上网行为管理软件:提升企业效率与安全的双重保障
  • 【leetcode hot 100 2】两数相加
  • volatile 在 JVM 层面的实现机制
  • 时序分析
  • Hadoop安装文件解压报错:无法创建符号链接。。。
  • golang从入门到做牛马:第三篇-Go程序的“骨骼架构”
  • Jetson Xavier NX安装CUDA加速的OpenCV
  • Day04 模拟原生开发app过程 Androidstudio+逍遥模拟器
  • 安当TDE透明加密技术:为Manus大模型构建用户会话数据保护的“安全金库”