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

Linux网络编程:套接字

目录

一 前言 

二 源ip地址和目的ip地址

三 认识端口号 

四 理解 "端口号" 和 "进程ID" 

五 理解源端口号和目的端口号

六 认识TCP(Transmission Control Protocol)协议

七 UDP((User Datagram Protocol)协议的基本特点

八  网络字节序

九 socket编程接口

1. struct sockaddr  

2. 接口使用


一 前言 

上篇文章主要介绍的是网络通信方面的一些知识,也算是为以后的学习先做一下铺垫。从本章开始我们就要正式进入网络编程的学习了。在开始网络编程之前,我们还需要进行一些准备工作,需要先了解一些概念。


二 源ip地址和目的ip地址

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


三 认识端口号 

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

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

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

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

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

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

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

端口号(port)是传输层协议的内容.

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用. 

四 理解 "端口号" 和 "进程ID" 

我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?

进程PID是操作系统内部用于识别和管理进程的标识符,而端口号是在网络通信中用于区分不同服务或进程的标识符。两者有各自不同的作用和用途,并没有直接的关联性.这样做的原因就像协议分层一样降低耦合性


五 理解源端口号和目的端口号

 传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 数据是谁发的, 要发给谁。


六 认识TCP(Transmission Control Protocol)协议

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

  1. 传输层协议
  2. 有连接。(比如我们打电话的时候,拨打对方的电话号码,对方必须接电话我们才能进行通信)
  3. 可靠传输(中性词)。在传输数据时,TCP会使用各种防止数据丢失或者损坏,但是并不表示TCP传输一定是可靠的。
  4. 面向字节流。指它将应用层传递下来的数据视为一个无结构的字节序列,即字节流。这意味着应用程序通过TCP发送数据时,这些数据可以被视为是连续的比特或字节序列,而不是一个个独立的消息或数据包。

七 UDP((User Datagram Protocol)协议的基本特点

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

八  网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

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

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

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

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

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

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

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

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

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

🍎为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换。

系统中提供了 主机字节序(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")。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

九 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 

1. 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.

8字节填充只是为了结构体对齐等需求,有效内容还是上面三个

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

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


2. 接口使用

  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数据包的应用程序。它们可以用来构建自定义的协议或者进行网络研究等。即

 3、int protocol

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

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

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


相关文章:

  • Spring AI支持的聊天模型全方位比较与分析
  • 利用n8n、DeepSeek、AI Agent、子工作流生成统计图
  • PyTorch_张量形状操作
  • 常用命令集合
  • 55、【OS】【Nuttx】编码规范解读(三)
  • 比较 TensorFlow 和 PyTorch
  • 30.沿触发控制与电平宽度触发控制的抗干扰能力对比分析
  • 如何在服务器后台运行Python脚本,并配置虚拟环境与GPU支持
  • 科普简洁版:同态加密——密码学的未来瑰宝
  • CPU 的指令集存放在什么地方?
  • 护网奇谈: 红队工程师手记
  • k230摄像头初始化配置函数解析
  • 含铜废水的资源化利用
  • 第三方组件库:element-uiiviewVant
  • 方案精读:业财融合转型路径和华为实践【附全文阅读】
  • 【Godot】使用 Shader 实现可调节的精确切角效果
  • indexedDB
  • 两台电动缸同步算法
  • Linux常用命令30——groupadd创建新的用户组
  • hot100:链表倒数k个节点- 力扣(LeetCode)
  • 印度扩大对巴措施:封锁巴基斯坦名人账号、热门影像平台社媒
  • 国铁集团:全国铁路旅客发送量连续3天同比增幅超10%
  • 张建华评《俄国和法国》|埃莲娜·唐科斯的俄法关系史研究
  • 缔造“水饺皇后”的香港,也是被移民塑造的香港
  • 49:49白热化,美参议院对新关税政策产生巨大分歧
  • 新疆维吾尔自治区原质量技术监督局局长刘新胜接受审查调查