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

📅 2026/7/18 10:29:08
【Linux网络】认识协议(TCP/UDP)、Mac/IP地址和端口号、网络字节序、socket套接字
⭐️个人主页小羊⭐️所属专栏Linux网络很荣幸您能阅读我的文章诚请评论指点欢迎欢迎 ~目录1、初识协议2、UDP、TCPTcpTcp三次握手为什么要三次握手为什么要四次挥手测试时出现 bind error 是什么原因为什么 TIME_WAIT 的时间是 2MSL?滑动窗口丢包重传问题拥塞控制Udp3、Mac、IP地址4、端口号5、网络字节序6、socket1、初识协议协议就是一种约定。如何让不同厂商生产的计算机之间能够互相通信需要由权威组织或公司制定网络协议。协议本质也是软件在设计上为了更好的进行模块化解耦合因此被设计为层状结构。在Linux网络协议栈中各个层次协同工作以实现数据的封装、传输、路由和接收。从底层到高层这些层次包括链路层数据链路层负责物理网络上的数据传输包括帧的封装、错误检测和纠正等。在Linux中这一层通常与特定的网络接口卡NIC驱动程序相关联。网络层提供IP地址管理和路由功能确保数据包能够正确地从一个网络传输到另一个网络。Linux支持IPv4和IPv6两种IP协议版本。传输层提供端到端的通信服务确保数据的可靠传输或快速、不可靠的传输。TCP传输控制协议提供可靠需建立连接的数据传输而UDP用户数据报协议则提供不可靠无连接的数据传输服务。应用层提供用户和网络服务之间的接口包括HTTP用于Web浏览、SMTP用于电子邮件发送、FTP用于文件传输等多种应用层协议。一般而言对于一台主机它的操作系统内核实现了从传输层到物理层的内容对于一台路由器它实现了从网络层到物理层对于一台交换机它实现了从数据层到物理层对于一台集线器它只实现了物理层2、UDP、TCPTcp传输层的典型代表TCP协议UDP协议传输层协议传输层协议有连接无连接可靠传输不可靠传输面向字节流面向数据报TCP协议格式确认应答至少应该是一个完整的TCP报头确认序号 序号 1表示该序号之前的内容被全部收到了为什么要有序号和确认序号两个序号并且是独立的字段TCP报文大多数情况下既是应答又是数据即捎带应答机制这个时候序号和确认序号这两个字段要被同时使用。TCP 将每个字节的数据都进行了编号即为序列号。每一个 ACK 都带有对应的确认序列号意思是告诉发送者我已经收到了哪些数据下一次你从哪里开始发。4位首部长度这个字段的单位是4字节取值范围是0到15乘以4后得到报头的实际字节长度范围是20到60字节。当首部长度为5时表示的是标准的20字节报头。6 位标志位用于区分报文类型标志位说明URG紧急指针是否有效需要优先处理的报文ACK表明自己是应答报文PSH提示接收端应用程序立刻从 TCP 缓冲区把数据读走RST对方要求重新建立连接我们把携带 RST 标识的称为复位报文段SYN请求建立连接我们把携带 SYN 标识的称为同步报文段FIN通知对方本端要关闭了我们称携带 FIN 标识的为结束报文段16位窗口大小流量控制由接收缓冲区剩余空间大小决定由滑动窗口实现超时重传在TCP连接中当发送方发送一个数据段后会启动一个超时计时器如果在计时器超时之前发送方没有收到接收方的确认ACK报文那么发送方就会认为该数据段已经丢失并重新发送该数据段直到收到确认报文或达到重传次数限制为止。Tcp三次握手在正常情况下TCP 要经过三次握手建立连接四次挥手断开连接。第一次握手SYN报文客户端给服务端发送SYN报文表示请求建立连接第二次握手SYNACK报文服务端收到客户端发来的SYN报文后进行确认应答发送SYNACK报文给客户端表示接收客户端建立连接的请求第三次握手ACK报文客户端收到服务端的确认应答报文后也要给服务端发送一个确认应答报文这次报文就可以携带客户端到服务端的数据了之后客户端进入 ESTABLISHED 状态表示客户端准备好和服务端进行数据传输服务端在收到应答后也进入 ESTABLISHED 状态此时Tcp建立连接完毕。第三次握手是可以携带数据的但是前两次握手不行。为什么要三次握手三次握手才能保证双方具有接收和发送数据的能力。防止历史连接当因为网络阻塞等原因客户端发送了两个SYN报文服务端收到第一个SYN报文后回发给客户端SYNACK确认应答客户端收到后可以根据确认应答号来判断这是不是最新SYN请求对应的应答如果不是则向服务端发送RST报文请求重新建立连接直到服务端收到最新的SYN报文客户端和服务端才能正确建立连接。如果只是两次握手服务端在收到SYN报文后就立即进入 ESTABLISHED 状态服务端并不知道这是历史连接请求就会直接发送数据导致资源浪费而客户端会判定这次连接时历史连接从而发送RST报文来断开连接。同步双方的初始化序列号序列号是保证可靠传输的一个关键因素接收端可以根据序列号进行去重、按照序列号顺序接收、确认哪些数据包已被接收到。过程客户端发送第一个报文携带客户端初始序列号服务器发送第二个报文携带服务器初始序列号的ACKSYN告诉客户端收到的他的SYN报文客户端发送第三个报文携带服务器的ACK应答。这样一来一回才能确保双方的初始序列号同步可靠。为什么要四次挥手测试时出现 bind error 是什么原因TCP 协议规定主动关闭连接的一方要处于TIME_ WAIT状态等待两个MSL一般为2分钟的时间后才能回到 CLOSED 状态我们使用 Ctrl-C 终止了 server所以 server 是主动关闭连接的一方在TIME_WAIT期间仍然不能再次监听同样的 server 端口如何解决使用setsockopt()设置 socket 描述符的选项SO_REUSEADDR为 1表示允许创建端口号相同但 IP 地址不同的多个 socket 描述符intopt1;setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));CLOSE_WAIT状态则是被动关闭连接的一方在接收到FIN报文并发送ACK后的状态如果服务器不关闭sockfd则只会完成两次挥手服务器就会长时间处于CLOSE_WAIT状态。为什么 TIME_WAIT 的时间是 2MSL?MSL 是 TCP 报文的最大生存时间因此 TIME_WAIT 持续存在 2MSL 的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失否则服务器重启后可能会收到来自上一个进程的迟到的数据在理论上保证最后一个报文可靠到达假设最后一个 ACK 丢失那么服务器会重发一个 FIN这时虽然客户端的进程不在了但是 TCP 连接还在仍然可以重发 LAST_ACK。滑动窗口窗口大小指的是无需等待确认应答而可以继续发送数据的最大值操作系统内核为了维护这个滑动窗口需要开辟发送缓冲区来记录当前还有哪些数据没有应答只有确认应答过的数据才能从缓冲区删掉窗口只能向右滑动且根据对方接收缓冲区的大小调整大小丢包重传问题如果发送端主机连续三次收到了同样一个 “1001” 这样的应答则说明我们只收到了 “1001” 之前的报文在这种情况下发送方不等待超时定时器的触发而是立即重传该丢失的数据包即将对应的数据 1001 - 2000 重新发送。这种机制被称为 高速重发控制”快重传拥塞控制虽然 TCP 滑动窗口能够高效可靠的发送大量的数据但是如果刚开始就发送大量的数据仍然可能引发问题。因为网络上有很多的计算机可能当前的网络状态就已经比较拥堵在不清楚当前网络状态下贸然发送大量的数据很有可能雪上加霜。TCP引入慢启动机制即先发少量的数据探路摸清当前的网络拥堵状态再决定按照多大的速度传输数据当拥塞窗口达到慢启动阈值后TCP会进入拥塞避免阶段。此时拥塞窗口会按线性速率增长以避免突然的大幅增加导致网络拥塞Tcp通过校验和、序列号(按序到达、去重)、确认应答、超时重发、连接管理、流量控制、拥塞控制等保证可靠性通过滑动窗口、快速重传、延迟应答、捎带应答等提高性能。Udp无连接知道对端的 IP 和端口号就直接进行传输不需要建立连接;不可靠没有确认机制没有重传机制如果因为网络故障该段无法发到对方UDP 协议层也不会给应用层返回任何错误信息;面向数据报不能够灵活的控制读写数据的次数和数量16 位 UDP 长度表示整个数据报(UDP 首部UDP 数据)的最大长度如果要传输的数据超过 64K就需要在应用层手动的分包多次发送并在接收端手动拼装如果校验和出错就会直接丢弃UDP协议的报头是固定的8字节所以协议的接收方直接截取前8个字节的报头剩下的就是有效数据。UDP的缓冲区发送缓冲区UDP 没有真正意义上的发送缓冲区调用 sendto 会直接交给内核由内核将数据传给网络层协议进行后续的传输动作接收缓冲区UDP的接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致如果缓冲区满了再到达的 UDP 数据就会被丢弃TCP/UDP 对比无论是Tcp有连接Udp无连接还是Tcp可靠Udp不可靠这都是它们的特性不是优缺点它们适用于不同的需求场景TCP 用于可靠传输的情况应用于文件传输重要状态更新等场景UDP 用于对高速传输和实时性要求较高的通信领域例如早期的 QQ、视频传输、广播等3、Mac、IP地址每台主机在局域网上要有唯一的标识来保证主机的唯一性mac 地址。以太网中任何时刻只允许一台机器向网络中发送数据。如果有多台同时发送会发生数据干扰我们称之为数据碰撞所有发送数据的主机要进行碰撞检测和碰撞避免没有交换机的情况下一个以太网就是一个碰撞域局域网通信的过程中主机对收到的报文确认是否是发给自己的是通过目标mac地址判定的。其中每层都有协议当我们进行传输流程的时候要进行封装和解包| 封装与解包的作用分层解耦简化复杂性各层仅需关注本层功能屏蔽下层细节降低开发和维护成本。类似快递分拣每层按规则 “打包”封装或 “拆包”解封装无需关心其他层如何处理。标准化协议格式每层协议头部包含固定格式的字段确保不同设备间遵循统一规则通信。实现跨层交互与错误处理封装时添加的头部包含控制信息用于解封装时校验数据完整性或重组分片数据。例数据链路层通过 CRC 校验码检测帧传输错误网络层通过 IP 分片重组被拆分的大数据包。支持异构网络互联不同网络如以太网、WiFi、广域网在数据链路层可能使用不同协议但通过网络层的 IP 协议统一封装实现跨网络通信。核心IP 数据报在不同网络中被封装为对应链路层帧如以太网帧、PPP 帧但网络层以上的处理逻辑保持一致。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在网络中标识主机的唯一性数据传输到主机不是目的而是手段最终到达主机内的目的进程才是目的。但是在主机中同一时间进程可能有很多那怎么找到目的进程呢4、端口号端口号port是传输层协议的内容是一个2字节16位的整数端口号标识唯一进程一个端口号只能被一个进程占用。IP地址端口号能够标识网络中的唯一进程。网络通信本质上也是进程间通信。其中 0 - 1023 是知名端口号HTTP, FTP, SSH等这些广为使用的应用层协议它们的端口号都是固定的。1024 - 65535 是操作系统动态分配的端口号比如客户端程序的端口号就是有操作系统动态分配的。pid也可以标识唯一进程为什么还要引入端口号呢进程pid属于系统概念如果继续沿用pid标识唯一进程会增加耦合度。另外一个进程可以绑定多个端口号但一个端口号不能被多个进程绑定。网络通信的本质也是进程间通信本质是两个互联网进程代表人来进行通信。IP port 叫做套接字socket。一个进程可以 bind 多个端口号但一个端口号不能被多个进程 bind。5、网络字节序内存中的多字节数据相对于内存地址有大端和小端之分网络数据流同样有大端小端之分如何定义网络数据流的地址网络数据流的地址被规定先发出去的是低地址后发出去的是高地址。TCP/IP协议规定网络数据流应采用大端字节序即低地址高字节。为使网络程序具有可移植性使用样的C代码在大端和小端机器上编译后都能正常运行可以调用下面库函数做网络字节序和主机字节序的转换。#includearpa/inet.huint32_thtonl(uint32_thostlong);uint16_thtons(uint16_thostshort);uint32_tntohl(uint32_thostlong);uint16_tntohs(uint16_thostshort);h表示hostn表示networkl表示32位长整数s表示16位短整数。6、socketsocket常见API// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)intsocket(intdomain,inttype,intprotocol);// 绑定端口号 (TCP/UDP, 服务器)intbind(intsocket,conststructsockaddr*address,socklen_t address_len);// 开始监听 socket (TCP, 服务器)intlisten(intsocket,intbacklog);// 接收请求 (TCP, 服务器)intaccept(intsocket,structsockaddr*address,socklen_t*address_len);// 建立连接 (TCP, 客户端)intconnect(intsockfd,conststructsockaddr*addr,socklen_t addrlen);socketsocket()打开一个网络通讯端口如果成功就像open()一样返回一个文件描述符出错返回-1应用程序可以像读写文件一样用 read/write 在网络上收发数据对于 IPv4,family参数指定为AF_INET对于 TCP 协议type参数指定为SOCK_STREAM表示面向流的传输协议protocol参数指定为 0 即可bindbind()成功返回 0失败返回-1bind()的作用是将参数 sockfd 和 myaddr 绑定在一起使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号struct sockaddr *是一个通用指针类型myaddr 参数实际上可以接受多种协议的 sockaddr 结构体而它们的长度各不相同所以需要第三个参数 addrlen指定结构体的长度我们可以对 myaddr 参数这样初始化structsockaddr_inlocal;memset(local,0,sizeof(local));local.sin_familyAF_INET;local.sin_porthtons(_port);local.sin_addr.s_addrINADDR_ANY;listenlisten()声明 sockfd 处于监听状态并且最多允许有 backlog 个客户端处于连接等待状态如果接收到更多的连接请求就忽略listen()成功返回 0,失败返回-1accept三次握手完成后服务器调用 accept()接受连接如果服务器调用 accept()时还没有客户端的连接请求就阻塞等待直到有客户端连接上来addr 是一个传出参数accept()返回时传出客户端的地址和端口号如果给 addr 参数传 NULL表示不关心客户端的地址addrlen 参数是一个传入传出参数(value-result argument)传入的是调用者提供的缓冲区 addr 的长度以避免缓冲区溢出问题传出的是客户端地址结构体的实际长度connect客户端需要调用 connect()连接服务器connect 和 bind 的参数形式一致区别在于 bind 的参数是自己的地址connect 的参数是对方的地址connect()成功返回 0出错返回-1注意由于客户端不需要固定的端口号因此不必调用 bind()客户端的端口号由内核自动分配客户端不是不允许调用 bind()只是没有必要显示的调用 bind()固定一个端口号否则如果在同一台机器上启动多个客户端就会出现端口号被占用导致不能正确建立连接服务器也不是必须调用 bind()但如果服务器不调用 bind()内核会自动给服务器分配监听端口每次启动服务器时端口号都不一样客户端要连接服务器就会遇到麻烦sockaddr结构sock API是一层抽象的网络编程接口适用于各种底层网络协议各种网络协议的地址格式并不相同。socket API 可以都用struct sockaddr*类型表示在使用的时候需要强制转换成sockaddr_in增加了程序的通用性。本篇文章的分享就到这里了如果您觉得在本文有所收获还请留下您的三连支持哦~