【Linux】认识协议、Mac/IP地址和端口号、网络字节序、socket套接字

⭐️个人主页:@小羊 ⭐️所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

目录


1、初识协议

  • 协议就是一种约定。
  • 如何让不同厂商生产的计算机之间能够互相通信?需要由权威组织或公司制定网络协议。
  • 协议本质也是软件,在设计上为了更好的进行模块化,解耦合,因此被设计为层状结构。

协议本质也是软件,为了更好的模块换,降低耦合度,所以被设计为层状结构。在Linux网络协议栈中,各个层次协同工作,以实现数据的封装、传输、路由和接收。从底层到高层,这些层次包括:

  1. 链路层(数据链路层):负责物理网络上的数据传输,包括帧的封装、错误检测和纠正等。在Linux中,这一层通常与特定的网络接口卡(NIC)驱动程序相关联。

  2. 网络层:提供IP地址管理和路由功能,确保数据包能够正确地从一个网络传输到另一个网络。Linux支持IPv4和IPv6两种IP协议版本。

  3. 传输层:提供端到端的通信服务,确保数据的可靠传输或快速、不可靠的传输。TCP(传输控制协议)提供可靠的数据传输,而UDP(用户数据报协议)则提供无连接的数据传输服务。

  4. 应用层:提供用户和网络服务之间的接口,包括HTTP(用于Web浏览)、SMTP(用于电子邮件发送)、FTP(用于文件传输)等多种应用层协议。

一般而言:

  • 对于一台主机,它的操作系统内核实现了从传输层到物理层的内容
  • 对于一台路由器,它实现了从网络层到物理层
  • 对于一台交换机,它实现了从数据层到物理层
  • 对于一台集线器,它只实现了物理层

2、Mac、IP地址

每台主机在局域网上,要有唯一的标识来保证主机的唯一性:mac 地址

以太网中,任何时刻,只允许一台机器向网络中发送数据。如果有多台同时发送,会发生数据干扰,我们称之为数据碰撞,所有发送数据的主机要进行碰撞检测和碰撞避免,没有交换机的情况下,一个以太网就是一个碰撞域,局域网通信的过程中,主机对收到的报文确认是否是发给自己的,是通过目标mac地址判定的。

其中每层都有协议,当我们进行传输流程的时候,要进行封装和解包:

Tcp/IP通讯过程:

IP 地址 是在 IP 协议中, 用来标识网络中不同主机的地址,对于 IPv4 来说, IP 地址是一个 4 字节,32 位的整数,我们通常也使用 "点分十进制" 的字符串表示 IP 地址, 例如1.94.9.200,用点分割的每一个数字表示一个字节,范围是 0 - 255。

Mac地址 vs IP地址:

唐僧从东土大唐出发,要去西天拜佛求经,途中要经过女儿国和黑风岭,女儿国和黑风岭是相邻两地。

  • 东土大唐 -> 西天:源IP地址 -> 目的IP地址
  • 女儿国 -> 黑风岭:源Mac地址 -> 目的Mac地址

其中经过的各个国家就是路由器,相邻的国家在同一个局域网中,路由器路由的下一个目的地是根据目的IP地址路由的,局域网通信需要Mac地址指路,一般Mac地址只在局域网中有效,IP地址几乎不变。

IP在网络中标识主机的唯一性,数据传输到主机不是目的而是手段,最终到达主机内的目的进程才是目的。但是在主机中,同一时间进程可能有很多,那怎么找到目的进程呢?


3、端口号

端口号(port)是传输层协议的内容,是一个2字节16位的整数,端口号标识唯一进程,一个端口号只能被一个进程占用

IP地址+端口号能够标识网络中的唯一进程。
网络通信,本质上也是进程间通信。

其中 0 - 1023 是知名端口号,HTTP, FTP, SSH等这些广为使用的应用层协议,它们的端口号都是固定的。1024 - 65535 是操作系统动态分配的端口号,比如客户端程序的端口号就是有操作系统动态分配的。

pid也可以标识唯一进程,为什么还要引入端口号呢?

进程pid属于系统概念,如果继续沿用pid标识唯一进程,会增加耦合度。另外,一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定。

网络通信的本质,也是进程间通信,本质是两个互联网进程代表人来进行通信。IP + port 叫做套接字socket。

传输层的典型代表:

TCP协议 UDP协议
传输层协议 传输层协议
有连接 无连接
可靠传输 不可靠传输
面向字节流 面向数据报

4、网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分,网络数据流同样有大端小端之分,如何定义网络数据流的地址?

  • 网络数据流的地址被规定:先发出去的是低地址,后发出去的是高地址。
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

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

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

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t hostlong);
uint16_t ntohs(uint16_t hostshort);
  • h表示hostn表示networkl表示32位长整数,s表示16位短整数。

5、socket

socket常见API:

cpp 复制代码
// 创建 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);

socket:

  1. socket()打开一个网络通讯端口,如果成功,就像open()一样返回一个文件描述符,出错返回-1

  2. 应用程序可以像读写文件一样用 read/write 在网络上收发数据

  3. 对于 IPv4, family 参数指定为 AF_INET

  4. 对于 TCP 协议,type 参数指定为 SOCK_STREAM,表示面向流的传输协议

  5. protocol 参数指定为 0 即可
    bind:

  6. bind()成功返回 0,失败返回-1

  7. bind()的作用是将参数 sockfd 和 myaddr 绑定在一起,使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号

  8. struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen指定结构体的长度,我们可以对 myaddr 参数这样初始化:

cpp 复制代码
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;

listen:

  1. listen()声明 sockfd 处于监听状态,并且最多允许有 backlog 个客户端处于连接
    等待状态,如果接收到更多的连接请求就忽略

  2. listen()成功返回 0,失败返回-1
    accept:

  3. 三次握手完成后,服务器调用 accept()接受连接

  4. 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待,直到有客户端
    连接上来

  5. addr 是一个传出参数,accept()返回时传出客户端的地址和端口号

  6. 如果给 addr 参数传 NULL,表示不关心客户端的地址

  7. addrlen 参数是一个传入传出参数(value-result argument),传入的是调用者提供的,缓冲区 addr 的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度
    connect:

  8. 客户端需要调用 connect()连接服务器

  9. connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,connect 的参数是对方的地址

  10. connect()成功返回 0,出错返回-1

注意:

  1. 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配
  2. 客户端不是不允许调用 bind(),只是没有必要显示的调用 bind()固定一个端口号,否则如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接
  3. 服务器也不是必须调用 bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦

sockaddr结构:

sock API是一层抽象的网络编程接口,适用于各种底层网络协议,各种网络协议的地址格式并不相同。

socket API 可以都用struct sockaddr*类型表示,在使用的时候需要强制转换成sockaddr_in,增加了程序的通用性。


本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

相关推荐
-今昭-44 分钟前
MooseFS分布式文件系统
linux·服务器·网络
byte轻骑兵1 小时前
【BlueZ 】蓝牙 HCI 协议基础:与 BlueZ 源码的层面对应关系
linux·hci·电脑蓝牙·嵌入式蓝牙·buez
砚凝霜1 小时前
软考网络工程师|案例分析:Eth‑Trunk 链路聚合、iStack 堆叠、CSS 集群核心考点总结
前端·css·网络
万联WANFLOW1 小时前
Meta开源了能本地跑的agent
运维·服务器·网络·人工智能·业界资讯
Brilliantwxx1 小时前
【Linux】 进程(1) 从冯诺依曼体系到进程核心概念
linux·运维·服务器
lengjingzju1 小时前
编译与调试完全指南—第12章 日志分析
linux
城管不管2 小时前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
蜀道山老天师2 小时前
Zabbix监控Apache与Nginx应用实践完整指南
linux·运维·nginx·apache·zabbix
Java小白笔记2 小时前
Windows系统免软件命令激活
java·网络·人工智能·windows·ai·ai编程
暖核2 小时前
Linux实战入门:从零编写第一个Bash Shell脚本
linux·运维·bash