网络编程。
应用层
应用层包括的协议有:
1、文件传输类:HTTP(超文本传输协议)、FTP(文件传输协议)、TFTP(简单文件传输协议)
2、远程登录类:Telnet
3、电子邮件类:SMTP(简单邮件传输协议)
4、网络管理类:SNMP(简单网络管理协议)
5、域名解析类:DNS
**HTTP协议:**这是一种超文本链接协议,适用于分布式超媒体系统,它对应的客户端的连接对象是web服务器,也就是我们常见的“www”,我们在登录一个网页的时候必然需要经过这个协议,它是我们在网页设计以及网页数据的传输中的规范。
**FTP协议:**这是一种文件传输协议,它是基于TCP协议(TCP协议位于传输层,我们之后会提到)实现的一种应用层的协议,使得用户可以在FTF服务器上下载大信息量的文件,FTP协议保障了文件的传输的可靠性。
**TFTP协议:**简单文件传输协议,TFTP只能获得和写入文件,和前者不同之处是这种协议是基于UDP协议的,这是不是一种面向连接的协议。什么是面向连接呢?面向连接就是协议的目的是保障通信双方能够保持连接并进行数据的交互。而TFTP协议是一种无连接协议,发文件方只管文件发出去了而不会管对方是不是受到了文件。QQ传输文件使用的就是TFTP协议,那么既然它不可靠,为什么还要使用它呢?因为这种文件传输方式成本低,在小文件的传输中可以提高速度而不需要复杂的来回确认和验证,我们要想保障文件的传输,只要在应用层对UDP传输的数据做一些处理,依然能达到可靠传输的效果。
**Telnet协议:**远程登录类协议,客户端需要通过这个协议去登录到服务器上。这里我们可以举个简单的例子来说明:主机1提出申请希望登录到某主机2的服务器上,这时TCP会为它分配一个源端口号(假设是1288),然后指定登录类的目标端口号为23(登录类的端口号在计算机上是确定且唯一的,这可以让主机2知道这个消息发给登录类的),当数据到达主机2的时候,如果23号端口的服务器是开启的,客户端便可以连接到服务器,主机2会生成一个远程端口1288号,如果主机1上的第二个Telnet用户向TCP提出登录申请,这时TCP会为它分配另一个(如1289)的端口号去和服务器连接。关于端口号的使用,小于255一般是公共应用,255 ~1023是特定应用商的特定端口号,我们一般使用大于1023的端口号。
**STMP协议:**简单邮件传输协议,这也是基于UDP的一项应用,其实现类似于简单文件传输协议但这项协议是针对邮件的传输的。
**SNMP协议:**简单网络管理协议,它的作用是管理员可以通过网络对那些位于不同物理空间的主机进行配置和管理。
**DNS协议:**这是一系列关于网站域名和IP地址的映射,IP地址是由一系列数字组成,不便于记忆,而映射为域名去访问就会容易很多,在DNS中,这种映射也是唯一的。
传输层
TCP协议(传输控制协议)
TCP头部
-
序号(32bit):传输方向上字节流的字节编号。初始时序号会被设置一个随机的初始值(ISN),之后每次发送数据时,序号值 = ISN + 数据 在整个字节流中的偏移。假设A -> B且ISN = 1024,第一段数据512字节已经到B,则第二段数据发送时序号为1024 + 512。用于解决网络包乱序问题。
-
确认号(32bit):接收方对发送方TCP报文段的响应,其值是收到的序号值 + 1。
-
首部长(4bit):标识首部有多少个4字节 * 首部长,最大为15,即60 字节。
-
标志位(6bit):
-
URG:标志紧急指针是否有效。
-
ACK:标志确认号是否有效(确认报文段)。用于解决丢包问题。
-
PSH:提示接收端立即从缓冲读走数据。
-
RST:表示要求对方重新建立连接(复位报文段)。
-
SYN:表示请求建立一个连接(连接报文段)。
-
FIN:表示关闭连接(断开报文段)。
-
-
窗口(16bit):接收窗口。用于告知对方(发送方)本方的缓冲还能接收多少字节数据。用于解决流控。
-
校验和(16bit):接收端用CRC检验整个报文段有无损坏。
常见TCP的连接状态
-
CLOSED:初始状态。 -
LISTEN:服务器处于监听状态。 -
SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。 -
SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。 -
ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。 -
FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。 -
CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状 态。 -
FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器的ACK包,但并没有立即接收到服务端的FIN 包,进入FIN_WAIT_2状态。 -
LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。 -
TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的 确认,在此之后的2MSL时间称TIME_WAIT状态。
TCP三次握手与四次挥手
最开始的时候客户端和服务器都是处于CLOSED关闭状态。主动打开连接的为客户端,被动打开连接的是服务器。
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了 LISTEN 监听状态。
第一次握手: TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT 同步已发送状态。
第二次握手: TCP服务器收到请求报文后,如果同意连接,则会向客户端发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了 SYN-RCVD 同步收到状态。
第三次握手: TCP客户端收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED已建立连接状态 触发三次握手。
在TCP连接建立的三次握手过程中,flags 的值在每次握手中的定义如下:
第一次握手 (客户端 -> 服务器):SYN 标志置位。
第二次握手 (服务器 -> 客户端):SYN 和 ACK 标志置位。
第三次握手 (客户端 -> 服务器):ACK 标志置位。
在连接建立后,发送数据包时通常会设置以下标志:
PSH`:表示有数据需要传输,应尽快推送给接收方。``
ACK:表示确认已经收到的数据包。
所以,在发送数据时,flags 的值通常为 PSH | ACK。
细节: 前两次握手SYN报文不能携带数据,但要消耗掉一个序号。 第三次握手ACK报文段可以携带数据,但如果不携带数据则不消耗序号。 凡是需要对端确认的,一定消耗TCP报文的序列号。
TCP四次挥手
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。
第一次挥手: 客户端发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态
第二次挥手: 服务器端接收到连接释放报文后,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT 关闭等待状态
第三次挥手: 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手: 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(最长报文寿命),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成
为什么客户端要等待2MSL?
主要原因是为了保证客户端发送那个的第一个ACK报文能到到服务器,因为这个ACK报文可能丢失,并且2MSL是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃,在这个时间内服务器没有再给客户端重传FIN报文,说明服务器已经收到了ACK报文,这样新的连接中不会出现旧连接的请求报文。
在2MSL时间内,同一个socket不能再被使用,否则有可能会和旧连接数据混淆(如果新连接和旧连接的socket相同的话)。
无time-wait等待的危害
网络不好的情况下,主动方无time-wait等待,关闭前一个连接后,主动方与被动方又建立了新连接,这个时候被动方超时重传,或延时过来的fin报文,会影响新的tcp连接。即使没有建立新连接,当接收到被动方重传的fin报文或者延迟的fin报文后,会给被动方返回一个RST包,可能会影响被动方的其他连接。
已建立的连接中客户端发送故障
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
服务器出现大量close_wait的连接的原因以及解决方法
close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量close_wait状态的原因有两种:
- 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业务逻辑有问题,没有执行close()方法 。
- 服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收。
- 超时与重传
- 如果发送方在一定时间内没有接受到发送的数据段的应答,就会重新发送此数据段,直到接受到该数据段的应答后就会释放该数据段的缓存。另外,如果在数据段传输的过程中发生了网络堵塞,发送方迟迟没有收到应答,就会重传数据段,网络恢复后原先的数据段就会继续发送到接收方,这时接收方收到了两个相同的数据段,但它只会获取第一个接收到的数据包而丢弃第二个,这增加了TCP数据传输的可靠性。
-
窗口技术
- 窗口是限制同时接收的最大数据量,如果发送方一次性发出的数据段数量大于它的窗口限制,就会只接收最大限额的数据段而把剩下的数据段返还给发送方,并告诉发送方下次最大的同时发送的数据段数量,返还的数据段会在下一次发送的时候再次发出。窗口技术对数据的传输量提出了要求,可以防止网络的堵塞。
-
面向流
- 当发送方和接收方之间传输大量数据的时候,发送方的TCP会把数据流分段分组发送,接收方再根据分段上的信息对其重新整合还原。
- 多路复用技术
- 由端口号来实现。
-
全双工
- 在一条连接上可以同时传输两条相反的数据流。
-
TCP数据域头格式

UDP协议(用户数据包协议)
-
UDP协议是一种无连接的不可靠的协议,也被称为一种轻权通信。如果说TCP是协议实现的是两个人的电话的交流,UDP实现的给另一个发一封信,我只管把东西发出去,而不用管对方有没有收到。因此这是一种不可靠的协议,在数据的传输中是可能出现丢包的情况的,但它依然应用广泛,因为它传输数据很方便,消耗的资源很少,我们需要用应用层协议来确保数据的可靠性,可能实现比TCP更优的效果。
-
使用UDP的协议包括:TFTP、SNMP、NFS(网络文件系统)、DNS、DHCP(动态主机配置)等。
-
UDP数据域头格式

网络层
网络层包括的协议有:IP、ICMP、ARP、RARP、DHCP等。
IP(网际协议):
- IP协议是我们最常见的一种通信协议,它为每一台主机赋予了身份,使得不同主机之间可以通过IP进行访问,IP地址有32位和48位两种,32位的是IPv4,目前这种IP将要被分配完,之后将逐渐过渡到IPv6,更长的位数意味着可以分配更多不同的IP。IP地址是网络供应商为其网络中的用户分配的一系列地址,IPv4有A、B、C、D、E五类,IP地址的高位一般是网络地址,低位是主机地址,不同的类别决定了其网络地址的长度,比如A类的高八位是网络地址,B类的高16位是网络地址。
- 什么是网络地址,什么是主机地址:我们知道,在不同主机之间除了有传输数据的线路,还有分配数据的路由器,路由器的作用就是通过其自身的网络地址和接口的映射来决定把数据传输给谁。一般来说我们通过链路层发出的数据包括以下这些信息:目标IP地址,源IP地址,目标MAC(介质访问地址,此地址由设备的制造商赋予,全球唯一)地址,自身MAC地址,数据,到达路由器后它会选择一个合适的通道把数据传送到下一个网络地址,数据会从一个路由器跳到另外一个路由器(逐跳的方式),当数据从最后一个路由器发出后,通过ARP就可以把数据送到目标主机,ARP寻址用到的就是主机地址。IP地址是网络地址和主机地址统一,我们区分他们的方法就是通过子网掩码,子网掩码是高位全为1,低位全为0的和IP等长的数据区域,把子网掩码和IP地址相与就可以得到高位的IP网络地址。
- 公有地址和私有地址。为了节省地址的数量,我们把地址分为公有地址和私有地址,公有地址是对外的且其地址唯一,私有地址是对内的,在内部使用的时候用的是私有地址,我们用公有地址来接收数据,转为私有地址再使用,我们要把信息发出去时要先转化为公有地址再发出去。
- VLSM是一种可变长度子网掩码的协议,使得地址空间的分配更加灵活,得到优化。
ICMP协议(控制消息协议):
-
它主要用在对信息的发送的判断处理上。
-
查询报文。比如在cmd中的Ping指令,它可以去尝试连接某个网,如果能够寻找到就会有返回信息;在Ping自身IP时,指令经过一个回路会回到原处,此时不需要网络。
-
错误报文。错误报文可能出现在信息传输的不同环节上,分为几种不可达。网络不可达:主机1把信息发到路由器上之后,发现这个路由器上没有到达指定位置的网络地址的映射,会返回一个网络不可到达的指令;主不可到达:在网络可达的情况下,如果在与主机2连接的路由器发出信息后,服务器down机了,便会返回主机不可达到;禁止分割:当数据的大小超过数据包最大传输单元(MTU)时,我们需要把数据包分割为两个较小的单位才可以通过路由器,但如果路由器禁止分割,数据包就无法通过,便会返回禁止分割;当数据包到达主机2但是主机2上没有TCP/IP协议(这种情况一般不会发生),会返回协议不可达;端口不可达:即使数据包通过了最后一步,连接上了服务器,但相关端口的软件没有开,那么数据仍然无法达到,此时返回端口不可达。
-
ICMP超时:每个数据包在发出后会在数据段标识TTL这个值,初值大于0,每经过一个路由器就会减1,一旦减到0,这个数据包就会被丢弃,返回ICMP超时,丢弃这个数据包,这样可以防止数据包在两个路由器之间不断回传。
-
ICMP重定向:当主机1向主机2发送数据时,默认网关是发给路由器B,路由器B发现下一跳是发给路由器A,而路由器A的接口和之前的数据包接收的接口是同一个,那么它会向主机发送一个ICMP重定向报文,告诉它以后把数据发给路由器A,这就是重定向。
-
在Ping指令中,ICMP还可以实现计算数据的往返时间,用接收到数据包的时间减去发送数据包的时间就可得到往返时间。
ARP协议(地址解析协议):
- 这也是一种映射,它是映射在主机中的,它存储的信息是IP地址到MAC的映射。IP和MAC的区别就是IP是由网络服务供应商分配提供的地址,IP在大的范围内是可能重复的,MAC是由计算机的制造商生产的时候赋予计算机的全球唯一的标识。假设主机1和主机2在同一个网络中,主机1有主机2的IP但不知道它的MAC地址,这时候数据是无法送达主机2的,这时需要用到广播的形式。以太网中,主机之间都有一条公用的总线,主机1发送消息给总线,所有网络内的主机都能收到,他们会匹配目的IP地址,如果匹配上就会应答,向主机发送自己的IP和MAC,同时把主机的IP和MAC映射到自己的ARP中,便于下次发消息,其他主机虽然不会去应答,但也会把主机1的信息映射到自己的ARP中。
RARP协议(反向地址解析协议):
- 和ARP的区别的这类协议的主机自身无磁盘空间,它的信息都存放在别的服务器中,在发送信息前需要回去需要的数据,比如它已知自己的MAC,需要获取自己的IP,发出广播后,其授权服务器接收到信息后就会产生应答,把它的IP地址返回。
DHCP协议(动态主机配置协议):
- 它的作用是使得主机的配置可以在服务器中完成,通过服务器去配置其他主机的IP,子网掩码,默认网关等参数。这使得维护修改IP数据的成本大大降低了。
链路层
-
链路层是计算机网络中TCP/IP协议栈的最底层,负责在物理介质上传输数据。它主要处理数据链路的建立、维护和释放。链路层包括两个子层:逻辑链路控制子层(LLC)和介质访问控制子层(MAC)。下面详细介绍链路层的功能、协议和机制。
链路层功能:
- 封装成帧: 链路层将网络层传递下来的数据封装成帧,每一帧包含了数据和必要的控制信息,如源地址和目的地址,以便在物理介质上传输。
- 链路控制: 负责数据帧的建立、维护和释放,确保数据能够可靠地传输。
- 差错检测与纠正: 链路层使用帧校验序列(FCS)进行差错检测,通过重传机制纠正传输过程中出现的错误。
- 流量控制: 控制发送方的数据传输速率,防止发送方发送数据过快超过接收方的处理能力。
- 介质访问控制: 管理多个设备如何共享同一物理介质,避免冲突和碰撞。例如,以太网使用CSMA/CD(载波监听多路访问/冲突检测)机制,Wi-Fi使用CSMA/CA(载波监听多路访问/冲突避免)机制。
链路层子层:
- 逻辑链路控制子层(LLC):
- 负责多路复用和解复用数据链路层的服务。
- 提供连接管理、帧同步、流量控制和错误控制等功能。
- 介质访问控制子层(MAC):
- 负责控制对物理传输介质的访问。
- 使用MAC地址来标识网络中的设备,确保数据帧能够正确地传输到目的地。
- 实现了多种介质访问控制机制,如CSMA/CD和CSMA/CA。
常见的链路层协议:
- 以太网(Ethernet):
- 以太网是最常见的局域网技术,规定了物理层和数据链路层的标准。
- 使用MAC地址进行通信,每个以太网设备都有一个唯一的MAC地址。
- 以太网数据帧格式包括前导码、帧起始定界符、目的地址、源地址、类型字段、数据和帧校验序列。
- Wi-Fi(无线局域网):
- 基于IEEE 802.11标准,允许设备通过无线电波进行通信。
- 使用MAC地址进行通信,支持多种加密方式以保障数据传输的安全性。
- PPP(点对点协议):
- 用于通过串行链路进行通信,常用于电话线拨号连接、光纤连接等。
- 包括链路控制协议(LCP)和网络控制协议(NCP),LCP用于建立、配置和测试数据链路连接,NCP用于协商和配置网络层协议。
MAC地址(介质访问控制地址):
- MAC地址是链路层使用的硬件地址,全球唯一,由设备制造商在生产时分配。MAC地址通常表示为六组十六进制数字,例如00:1A:2B:3C:4D:5E。
- MAC地址用于识别网络中的设备,并在局域网内传输数据帧。
以太网帧格式:
- 前导码(Preamble): 7字节的同步信号,用于帮助接收端同步数据流。
- 帧起始定界符(SFD): 1字节,标识帧的开始。
- 目的地址(Destination MAC Address): 6字节,接收设备的MAC地址。
- 源地址(Source MAC Address): 6字节,发送设备的MAC地址。
- 类型字段(Type/Length): 2字节,标识上层协议(如IP协议)或数据的长度。
- 数据(Data): 46-1500字节,实际传输的数据。
- 帧校验序列(FCS): 4字节,用于差错检测。
链路层的机制:
- CSMA/CD(载波监听多路访问/冲突检测):
- 常用于有线以太网。
- 设备在发送数据前监听介质,如果检测到冲突,会停止发送并等待随机时间后重试。
- CSMA/CA(载波监听多路访问/冲突避免):
- 常用于无线网络(Wi-Fi)。
- 设备在发送数据前监听介质,如果检测到介质空闲,会发送一个请求发送(RTS)信号,等待接收方发送清除发送(CTS)信号后再发送数据。
- 令牌环(Token Ring):
- 使用令牌传递的方式控制对网络介质的访问。
- 只有持有令牌的设备才能发送数据,避免冲突。
- 时分多址(TDMA):
- 将时间分为多个时隙,每个设备在自己的时隙内发送数据。
- 常用于蜂窝网络和卫星通信。
字节序转换
在Linux和Windows网络编程时需要用到htons和htonl函数,用来将主机字节顺序转换为网络字节顺序。
解释如下,数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。 由于Intel机器是小尾端,存储数字16时实际顺序为1000,存储4096时实际顺序为0010。因此在发送网络包时为了报文中数据为0010,需要经过htons进行字节转换。如果用IBM等大尾端机器,则没有这种字节顺序转换,但为了程序的可移植性,也最好用这个函数。
另外用注意,数字所占位数小于或等于一个字节(8 bits)时,不要用htons转换。这是因为对于主机来说,大小尾端的最小单位为字节(byte)。
1、转换函数
htonl()函数
函数原型是:uint32_t htonl(uint32_t hostlong)
其中,hostlong是主机字节顺序表达的32位数,htonl中的h–host主机地址,to–to,n–net网络,l–unsigned long无符号的长整型(32位的系统是4字节);
函数返回值是一个32位的网络字节顺序;
函数的作用是将一个32位数从主机字节顺序转换成网络字节顺序。
htons()函数
函数原型是:uint16_t htons(uint16_t hostlong)
其中,hostlong是主机字节顺序表达的16位数,htons中的h–host主机地址,to–to,n–net网络,s–signed long无符号的短整型(32位的系统是2字节);
函数返回值是一个16位的网络字节顺序;
函数的作用是将一个16位数从主机字节顺序转换成网络字节顺序,*简单的说就是把一个16位数高低位呼唤*。
ntohs()函数
函数原型是:uint16_t ntohs(uint16_t hostlong)
其中,hostlong是网络字节顺序表达的16位数,ntohs中的,n–net网络,to–toh–host主机地址,s–signed long有符号的短整型(32位的系统是2字节);
函数返回值是一个16位的主机字节顺序;
函数的作用是将一个16位数由网络字节顺序转换为主机字节顺序,*简单的说就是把一个16位数高低位互换*。
ntohl()函数
函数原型是:uint32_t ntohs(uint32_t hostlong)
其中,hostlong是网络字节顺序表达的32位数,ntohs中的,n–net网络,to–toh–host主机地址,s–unsigned long无符号的短整型(32位的系统是4字节);
函数返回值是一个32位的主机字节顺序;
函数的作用是将一个32位数由网络字节顺序转换为主机字节顺序。
2、这些函数存在的意义
为什么存在这个函数呢?或者存在这个函数的意义?
说到这部分需要引入字节存放的两个概念一个是“大端顺序”,一个是“小端顺序”。俗称“小尾顺序”、“大尾顺序”。
简单的说就是对应数据的高字节存放在低地址,低字节存放在高地址上就是大端顺序,对应数据的高字节存放在高地址,低字节存放在低地址上就是小端顺序。
比如 unsigned long hostlong = 0xa2b4c6d8;
大端顺序存放:
偏移地址 存放内容
0x00000000 0xa2
0x00000001 0xb4
0x00000002 0xc6
0x00000003 0xd8
小端顺序存放:
偏移地址 存放内容
0x00000000 0xd8
0x00000001 0xc6
0x00000002 0xb4
0x00000003 0xa2
同理推理16位数以及64位数。
一般地,在我的编译器里面设置的是小端顺序,这个可以根据自己的编译器看下设置。但是网络传输数据采用的是大端顺序。所以这才涉及到主机字节顺序和网络字节顺序,
主机字节顺序可能是大端顺序或者小端顺序(这个要看编译器的设置,还有自己是用的C还是Java还是其他的语言,其各自都是不尽相同,但是网络字节顺序一定是大端顺序。
为什么会有这个大端模式和小端模式呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令[1] )进行大小端的切换。
序列化和反序列化
1、背景
1、在TCP的连接上,它传输数据的基本形式就是二进制流,也就是一段一段的1和0。
2、在一般编程语言或者网络框架提供的API中,传输数据的基本形式是字节,也就是Byte。一个字节就是8个二进制位,8个Bit。
二进制流和字节流本质上是一样的。对于我们编写的程序来说,它需要通过网络传输的数据是结构化的数据,比如,一条命令、一段文本或者一条消息。对应代码中,这些结构化的数据都可以用一个类或者一个结构体来表示。
序列化的用途除了用于在网络上传输数据以外,
将结构化数据保存在文件中(将对象存储于硬盘上),因为文件内保存数据的形式也是二进制序列。
问题:
在内存里存放的任何数据,它最基础的存储单元也是二进制比特,也就是说,我们应用程序操作的对象,它在内存中也是使用二进制存储的,既然都是二进制,为什么不能直接把内存中,对象对应的二进制数据直接通过网络发送出去,或者保存在文件中呢?为什么还需要序列化和反序列化呢?
内存里存的东西,不通用, 不同系统, 不同语言的组织可能都是不一样的, 而且还存在很多引用,指针,并不是直接数据块。内存中的对象数据应该具有语言独特性,例如表达相同业务的User对象(id/name/age字段),Java和PHP在内存中的数据格式应该不一样的,如果直接用内存中的数据,可能会造成语言不通。只要对序列化的数据格式进行了协商,任何2个语言直接都可以进行序列化传输、接收。
一个数据结构,里面存储的数据是经过非常多其他数据通过非常复杂的算法生成的,因为数据量非常大,因此生成该数据结构所用数据的时间可能要非常久,生成该数据结构后又要用作其他的计算,那么你在调试阶段,每次执行个程序,就光生成数据结构就要花上这么长的时间。假设你确定生成数据结构的算法不会变或不常变,那么就能够通过序列化技术生成数据结构数据存储到磁盘上,下次又一次执行程序时仅仅须要从磁盘上读取该对象数据就可以,所花费时间也就读一个文件的时间。
虽然都是二进制的数据,但是序列化的二进制数据是通过一定的协议将数据字段进行拼接。第一个优势是:不同的语言都可以遵循这种协议进行解析,实现了跨语言。第二个优势是:这种数据可以直接持久化到磁盘,从磁盘读取后也可以通过这个协议解析出来。
2、定义
要想使用网络框架的API来传输结构化的数据,必须得先实现结构化的数据与字节流之间的双向转换。这种将结构化数据转换成字节流的过程,称为序列化,反过来转换,就是反序列化。
- 序列化(Serialization):将数据结构(如对象、结构体)转换为字节流,以便能够通过网络传输或写入文件存储。
- 反序列化(Deserialization):将字节流转换回数据结构,以便能够在程序中使用。
简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它依据流重构对象。这两个过程结合起来,能够轻松地存储和数据传输。
比如,能够序列化一个对象,然后使用HTTP 通过 Internet 在client和server之间传输该对象。
3、序列化评价指标
1、可读性
序列化后的数据最好是易于人类阅读的
2、实现复杂度
实现的复杂度是否足够低
3、性能
序列化和反序列化的速度越快越好
4、信息密度
序列化后的信息密度越大越好,也就是说,同样的一个结构化数据,序列化之后占用的存储空间越小越好
4. 何时进行序列化和反序列化
-
网络传输:当你需要通过网络传输数据时,需要将数据结构序列化为字节流传输,然后在接收端反序列化回数据结构。例如,发送TCP/IP包时,可以序列化TCP/IP头和数据,然后在接收端进行反序列化。
-
持久化存储:当你需要将数据结构存储到文件中或数据库中,需要将其序列化,以便以后读取时可以反序列化回来。例如,将程序状态保存到文件中,下次启动时恢复。
-
进程间通信:当多个进程需要共享数据时,可以使用序列化将数据转换为字节流,然后通过管道、消息队列等进行传输。
套接字(Socket)
流套接字(Stream Socket):
使用面向连接的传输协议(通常是 TCP)。
提供可靠的、基于连接的字节流传输服务。
适用于需要保证数据传输可靠性和顺序的应用,如 HTTP、FTP 等。
数据报套接字(Datagram Socket):
使用无连接的传输协议(通常是 UDP)。
提供不保证可靠性、无连接的消息传递服务。
适用于对传输可靠性要求不高、但需要快速传输的应用,如视频流、在线游戏等。
原始套接字(Raw Socket):
允许对网络层数据包进行更低级别的控制和操作。
通常用于开发网络协议、网络分析工具或安全工具等。
需要较高权限(如 root 权限)才能创建和使用。
可靠数据报套接字(Reliable Datagram Socket):
使用可靠数据报传输协议(如 SCTP)。
提供消息传递服务,结合了 TCP 的可靠性和 UDP 的无连接特性。
适用于需要既保证可靠性又需要无连接特性的应用。
Windows环境下使用Socket需创建winsock环境:
WSADATA wsd;//定义WSADATA对象,调用WSASTART后返回数据给这个对象 |
套接字创建示例:
//流式套接字 |
Windows与Linux环境下原始套接字的不同
Linux 下的数据链路层操作
在 Linux 中,可以使用 SOCK_RAW 或 SOCK_DGRAM 套接字来操作数据链路层。具体来说,Linux 提供了 AF_PACKET 协议族,允许直接访问网络接口的数据链路层(如以太网帧)。
Windows 的限制
在 Windows 上,原始套接字不能直接访问数据链路层(如以太网帧)。Windows 提供的原始套接字只能用于 IP 层及以上的协议(如 ICMP、TCP、UDP 等)。这意味着不能直接操作或捕获以太网帧。
解决方案
在 Windows 上,如果需要操作数据链路层,可以使用专用的网络驱动程序或库,例如 WinPcap 或其继任者 Npcap。这些库允许捕获和注入数据链路层帧,并提供了类似于 AF_PACKET 的功能。
数据发送与接收
sendto 和 recvfrom 是用于数据报通信(如 UDP)和原始套接字操作的函数。这两个函数分别用于发送和接收数据,并允许指定目标或来源的地址信息。
sendto 函数原型:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); |
参数
sockfd:套接字文件描述符。buf:指向要发送的数据缓冲区的指针。len:要发送的数据长度,以字节为单位。flags:发送标志,通常设为 0。如果需要,可以设置特殊的发送选项(如MSG_DONTWAIT)。dest_addr:指向sockaddr结构的指针,指定目标地址和端口。addrlen:dest_addr结构的长度。
recvfrom 函数原型:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); |
参数
sockfd:套接字文件描述符。buf:指向用于存放接收数据的缓冲区的指针。len:接收缓冲区的长度,以字节为单位。flags:接收标志,通常设为 0。如果需要,可以设置特殊的接收选项(如MSG_DONTWAIT)。src_addr:指向sockaddr结构的指针,用于存放源地址信息。可以为NULL,表示不需要源地址。addrlen:指向sockaddr结构长度的指针。在调用函数前,设置为sockaddr结构的长度;函数返回后,设置为实际存储的地址长度。
send 和 recv 是在面向连接的套接字(如 TCP 套接字)中常用的函数,用于发送和接收数据。这两个函数通常用于流套接字(SOCK_STREAM)。
send 函数原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
参数
-
sockfd:套接字文件描述符。 -
buf:指向要发送的数据缓冲区的指针。 -
len:要发送的数据长度,以字节为单位。 -
flags:发送标志,通常设为 0。可以使用的标志包括:
MSG_DONTWAIT:非阻塞模式。MSG_OOB:发送带外数据。MSG_NOSIGNAL:阻止发送SIGPIPE信号。
recv 函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
参数
sockfd:套接字文件描述符。buf:指向用于存放接收数据的缓冲区的指针。len:接收缓冲区的长度,以字节为单位。flags:接收标志,通常设为 0。可以使用的标志包括:MSG_DONTWAIT:非阻塞模式。MSG_OOB:接收带外数据。MSG_PEEK:查看数据但不从缓冲区移除。MSG_WAITALL:等待接收到所请求的全部数据。
总结
send和recv:- 用于面向连接的套接字,发送和接收数据。
- 不需要指定目标或来源地址,因为数据在已经建立的连接中传输。
- 常用于 TCP 套接字编程。
sendto和recvfrom:- 用于无连接的套接字,发送和接收数据。
- 需要指定目标或来源地址,适用于 UDP 或原始套接字编程。
- 本文作者: hzr
- 本文链接: https://HZR0709.github.io/2024/06/15/TCP-IP-Stack/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
