IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> UNIX网络套接字相关总结 -> 正文阅读

[系统运维]UNIX网络套接字相关总结

文章目录

网络协议

RFC 相关文档

  • RFC官网
  • RFC 791:INTERNET PROTOCOL
  • RFC 793:Transmission Control Protocol

网络 ip 层

ip 头部

ip消息头可分为 20 个字节的固定头部和最多40字节可扩展头:

    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                    Example Internet Datagram Header
类型长度描述
Version4bit值为4时代表IPV4;值为6时代表IPV6
IHL4bitip消息头可分为20个字节的固定头部40字节可扩展头
Type of Service8bit服务类型,只有在有QoS差分服务要求时这个字段才起作用
Total Length16bit代表总长度,整个IP数据报的长度,包括首部和数据之和,单位为字节,最长65535,总长度必须不超过最大传输单元MTU
Identification16bit标识,主机每发一个报文值会加1,分片重组时会用到该字段
Flags3bit分片重装时使用:第一位,为0,第二位,DF(Don’t Fragment),能否分片位,0表示可以分片,1表示不能分片;第三位MF(More Fragment),表示是否该报文为最后一片,0表示最后一片,1代表后面还有
Fragment Offset13bit片偏移:分片重组时会用到该字段。表示较长的分组在分片后,某片在原分组中的相对位置
Time to Live8bit生存时间可经过的最多路由数,即数据包在网络中可通过的路由器数的最大值
Protocol8bit标识下一层协议
Header Checksum16bit首部校验和,只检验数据包的首部,不检验数据部分
Source Address32bi源IP地址
Destination Address32bit目的IP地址。
Options长度可变选项字段,用来支持排错,测量以及安全等措施。
Padding长度可变填充字段,全为0

传输层

tcp

相关概念
MSL是Maximum Segment Lifetime,“报文最大生存时间”

它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

因为TCP报文(segment)是IP数据报(datagram)的数据部分,而IP头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。

RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等
2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间。

等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。
在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。
当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。

MSS:Maximum Segment Size

对于IPv4,为了避免IP分片,主机一般默认MSS为536字节 (576IP最大字节数-20字节TCP协议头-20字节IP协议头=536字节)。同理,IPv6的主机默认MSS为1220字节(1280IP最大字节数-20字节TCP协议头-40字节IP协议头=1220字节)。

当发送方主机想要调整MSS时,应注意以下几点:

  • MSS不包含TCP及IP的协议头长度。
  • MSS选项只能在初始化连接请求(SYN=1)使用。
  • 发送方与接收方的MSS不一定相等

最大报文段长度(MSS)与最大传输单元(Maximum Transmission Unit, MTU)均是协议用来定义最大长度的。不同的是,MTU应用于OSI模型的第二层数据链接层,并无具体针对的协议。MTU限制了数据链接层上可以传输的数据包的大小,也因此限制了上层(网络层)的数据包大小。例如,如果已知某局域网的MTU为1500字节,则在网络层的因特网协议(Internet Protocol, IP)里,最大的数据包大小为1500字节(包含IP协议头)。MSS针对的是OSI模型里第四层传输层的TCP协议。因为MSS应用的协议在数据链接层的上层,MSS会受到MTU的限制

RTT:Round Trip Time

发送一个数据包收到对应的ACK,所花费的时间

RTO:Retransmission TimeOut

重传时间间隔

/proc/sys/net/ipv4/

参考:/proc/sys/net/ipv4/下网络参数的理解以及sysctl命令修改内核参数

tcp 头部
TCP Header Format

                                    
    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            TCP Header Format

  1. 端口号:用来标识同一台计算机的不同的应用进程。
    1)源端口:源端口和IP地址的作用是标识报文的返回地址。
    2)目的端口:端口指明接收方计算机上的应用程序接口。

TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接

  1. 序号和确认号:是TCP可靠传输的关键部分
    1)序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。
    2)确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。

  2. 数据偏移/首部长度:4bits
    由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。

  3. 保留:为将来定义新的用途保留,现在一般置0。

  4. 控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。

    1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。

    2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。

    3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。

    4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。

    5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。

    6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。

  5. 窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。

  6. 校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。

  7. 紧急指针:只有当 URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受

  8. 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。

  9. 数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段

可选项:

  • 选项的第一个字段kind说明选项的类型。有的TCP选项没有后面两个字段,仅包含1字节的kind字段
  • 第二个字段length(如果有的话)指定该选项的总长度,该长度包括kind字段和length字段占据的2字节
  • 第三个字段info(如果有的话)是选项的具体信息。常见的TCP选项有7种,
    Currently defined options include (kind indicated in octal):

      Kind     Length    Meaning
      ----     ------    -------
       0         -       End of option list.
       1         -       No-Operation.
       2         4       Maximum Segment Size.
      

    Specific Option Definitions

      End of Option List

        +--------+
        |00000000|
        +--------+
         Kind=0
  • kind=0是选项表结束选项。

  • kind=1是空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。

  • kind=2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。

  • kind=3是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小是用16位表示的,故最大为65535字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N乘2M,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。关于窗口扩大因子选项的细节,可参考标准文档RFC 1323。

  • kind=4是选择性确认(Selective Acknowledgment,SACK)选项。TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP模块只重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。

  • kind=5是SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。

  • kind=8是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。

tcp 状态转换图
                              +---------+ ---------\      active OPEN  
                              |  CLOSED |            \    -----------  
                              +---------+<---------\   \   create TCB  
                                |     ^              \   \  snd SYN    
                   passive OPEN |     |   CLOSE        \   \           
                   ------------ |     | ----------       \   \         
                    create TCB  |     | delete TCB         \   \       
                                V     |                      \   \     
                              +---------+            CLOSE    |    \   
                              |  LISTEN |          ---------- |     |  
                              +---------+          delete TCB |     |  
                   rcv SYN      |     |     SEND              |     |  
                  -----------   |     |    -------            |     V  
 +---------+      snd SYN,ACK  /       \   snd SYN          +---------+
 |         |<-----------------           ------------------>|         |
 |   SYN   |                    rcv SYN                     |   SYN   |
 |   RCVD  |<-----------------------------------------------|   SENT  |
 |         |                    snd ACK                     |         |
 |         |------------------           -------------------|         |
 +---------+   rcv ACK of SYN  \       /  rcv SYN,ACK       +---------+
   |           --------------   |     |   -----------                  
   |                  x         |     |     snd ACK                    
   |                            V     V                                
   |  CLOSE                   +---------+                              
   | -------                  |  ESTAB  |                              
   | snd FIN                  +---------+                              
   |                   CLOSE    |     |    rcv FIN                     
   V                  -------   |     |    -------                     
 +---------+          snd FIN  /       \   snd ACK          +---------+
 |  FIN    |<-----------------           ------------------>|  CLOSE  |
 | WAIT-1  |------------------                              |   WAIT  |
 +---------+          rcv FIN  \                            +---------+
   | rcv ACK of FIN   -------   |                            CLOSE  |  
   | --------------   snd ACK   |                           ------- |  
   V        x                   V                           snd FIN V  
 +---------+                  +---------+                   +---------+
 |FINWAIT-2|                  | CLOSING |                   | LAST-ACK|
 +---------+                  +---------+                   +---------+
   |                rcv ACK of FIN |                 rcv ACK of FIN |  
   |  rcv FIN       -------------- |    Timeout=2MSL -------------- |  
   |  -------              x       V    ------------        x       V  
    \ snd ACK                 +---------+delete TCB         +---------+
     ------------------------>|TIME WAIT|------------------>| CLOSED  |
                              +---------+                   +---------+

                      TCP Connection State Diagram
                               Figure 6.
LISTEN:侦听来自远方的TCP端口的连接请求
 
SYN-SENT:再发送连接请求后等待匹配的连接请求(客户端)
 
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认(服务器)
 
ESTABLISHED:代表一个打开的连接
 
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
 
FIN-WAIT-2:从远程TCP等待连接中断请求
 
CLOSE-WAIT:等待从本地用户发来的连接中断请求
 
CLOSING:等待远程TCP对连接中断的确认
 
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
 
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
 
CLOSED:没有任何连接状态
主动端可能出现的状态:FIN_WAIT1、FIN_WAIT2、CLOSING、TIME_WAIT?
被动端可能出现的状态:CLOSE_WAIT LAST_ACK
  • SYN_RCVD: 这个状态表示接收到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。如果收到一个RST信号,则返回到LISTEN状态

  • SYN_SENT: 这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。

  • FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。

  • FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。

  • TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

  • CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。另外一种情况就是,ACK丢失了。

  • CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。

  • LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了

tcp 定时器
  • 超时重传
  • 坚持定时器:
  • keepalive
  • time_wait
流量控制和滑动窗口

滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度,不过如果接收端的缓冲区一旦面临数据溢出,窗口大小值也会随之被设置一个更小的值通知给发送端,从而控制数据发送量(发送端会根据接收端指示,进行流量控制)。

发送端:

  • 已发送被确认
  • 已发送未确认
  • 允许发送未发送
  • 暂不允许发送

接收端:

  • 已确认消息
  • 允许接收
  • 接收未发送确认消息
  • 不允许接收
拥塞控制和拥塞窗口

发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。

发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数

  • 慢开始( slow-start )
  • 拥塞避免( congestion avoidance )
  • 快重传( fast retransmit )
  • 快恢复( fast recovery )
tcp 延时 ACK

参考:TCP/IP卷一:80—TCP数据流与窗口管理之(延时确认(延迟ACK)、Nagle算法

ACK延迟确认机制
接收方在收到数据后,并不会立即回复ACK,而是延迟一定时间。一般ACK延迟发送的时间低于500ms,但这个时间并非收到数据后需要延迟的时间。系统有一个固定的定时器会来检查是否需要发送ACK包。这样做有两个目的。

  • 这样做的目的是ACK是可以合并的,也就是指如果连续收到两个TCP包,并不一定需要ACK两次,只要回复最终的ACK就可以了,可以降低网络流量。
  • 如果接收方有数据要发送,那么就会在发送数据的TCP数据包里,带上ACK信息。这样做,可以避免大量的ACK以一个单独的TCP包发送,减少了网络流量。

不同操作系统对延迟确认的实现

  • 采用延时ACK的方法会减少ACK传输数目,可以一定程度地减轻网络负载。对于批量数据传输通常为 2:1 的比例。基于不同的主机操作系统,延迟发送ACK的最大时延可以动态配置
  • Linux使用了一种动态调节算法,可以在每个报文段返回一个ACK (称为“快速 确认”模式)与传统延时ACK模式间相互切换
  • Mac OS X中,可以改变系统变量net.inet. tcp.delayed_ack值?来设置延时ACK。可选值如下:禁用延时(设为0),始终延时(设为1),每隔一个包回复一个ACK(设为2),自动检测确认时间(设为3)。默认值为3
  • 最新的 Windows版本中,?注册表项中,每个接口的全局唯一标识(GUID)都不同(IG表示被引用的特定网络接口的GUID)。TcpAckFrequency值(需要被添加)可以设为0-255,默认为2。它?代表延时ACK计时器超时前在传的ACK数目?。将其设为1表明对每个收到的报文段都生成相应的ACK。ACK计时器值可以通过TcpDelAckTicks注册表项控制。该值可设为2 - 6,默认为2。它以百毫秒为单位,表明在发送延时ACK前要等待百毫秒数
//在c语言中可以通过设置socket来实现
int quickack = 1; /* 启用快速确认,如果赋值为0表示使用延迟确认 */
setsockopt(fd, SOL_TCP, TCP_QUICKACK, &quickack, sizeof(quickack));
Time-wait状态(2MSL)
1. 为什么需要TIME_WAIT状态

假设最后的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发最后的ACK,否则将会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连接的两个方向,所以client必须进入TIME_WAIT状态。

此外,考虑一种情况,TCP实现可能面临着先后两个相同的五元组。如果前一个连接处于TIME_WAIT状态,而允许另一个拥有相同五元组连接出现,可能处理TCP报文时,两个连接互相干扰。所以使用SO_REUSEADDR选项就需要考虑这种情况。

linux网络编程

相关概念

同步异步

同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。

阻塞非阻塞

阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。

套接字地址结构

struct in_addr

字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序

//ipv4套接字地址结构,在<netinet/in.h>中声明
typedef uint32_t in_addr_t;  //32位(unsigned int)的ip地址,
struct in_addr
{
    in_addr_t s_addr;
};

struct sockaddr和struct sockaddr_in

struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,
二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中

//sizeof(sockaddr_in)=16,定义在#include <netinet/in.h>
struct sockaddr_in
{
    unsigned short int sin_family;    //Address family 2
    unsigned short int sin_port;      // Port number 2
    struct in_addr sin_addr;          //Internet address 4
    unsigned char sin_zero[8];        //未使用 8
};

//sizeof(sockaddr)=16,定义在#include <sys/socket.h>
struct sockaddr
{
    sa_family_t sa_family;            //sa_family_t为unsigned short int,地址家族, AF_INET 2
    char sa_data[14];                 // 14 bytes of protocol address
};

字节排序函数

首先解释一下字节序的概念,所谓字节序是指多字节数据的存储顺序,比如0x1234要放在0000H和0001H两存储单元,有两种存储方式:大端格式为[0000H]=12,[0001H]=34和小端格式为[0000H]=34,[0001H]=12。

  1. 大端格式:将高位字节数据存储在低地址,低位字节数据存储在高地址

  2. 小端格式:将高位字节数据存储在高地址,低位字节数据存储在低地址

#include <stdio.h>

int main(int argc, char *argv[])
{
    union{
        
        short temp;
        
        char test[sizeof(short)];
        
    }un_tmp;
            
    un_tmp.temp = 0x1234;
    if ((un_tmp.test[0] == 0x12) && (un_tmp.test[1] == 0x34)) 
    {
        printf("大端格式:高位字节数据存储在低地址,低位字节数据存储在高地址");
    }
    
    if ((un_tmp.test[0] == 0x34) && (un_tmp.test[1] == 0x12))
    {
        printf("小端格式:高位字节数据存储在高地址,低位字节数据存储在低地址");
    }
    
    return 0;
    
}

网际协议采取的是大端字节序,我们在编程的时候才需要考虑网络字节许和主机字节序之间的转换。下面是四个转换函数

#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);  //均返回网络字节序

uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);  //均返回主机字节序

地址转换函数:

BSD网络软件中包含了inet_addr、inet_aton和inet_ntoa,用来在二进制地址格式和点分十进制字符串格式之间相互转换,但是这三个函数仅仅支持IPv4。(废弃,不建议使用)

1. in_addr_t inet_addr(const char *cp)函数转换标准的ASCII以点分十进制的地址值返回为网络字节序二进制值

如果参数 char *cp 无效则返回-1(INADDR_NONE),但这个函数有个缺点:在处理地址为255.255.255.255时也返回-1,虽然它是一个有效地址,但inet_addr()无法处理这个地址。

#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);  //in_addr_t-->uint32_t

输入是点分的IP地址格式(如A.B.C.D)的字符串,从该字符串中提取出每一部分,转换为ULONG,假设得到4个ULONG型的A,B,C,D,
ulAddress(ULONG型)是转换后的结果,
ulAddress = D<<24 + C<<16 + B<<8 + A(网络字节序),即inet_addr(const char *)的返回结果
另外,我们也可以得到把该IP转换为主机序的结果,转换方法一样
A<<24 + B<<16 + C<<8 + D

2. int inet_aton(const char *__cp, in_addr *__inp)转换标准的ASCII以点分十进制的地址值返回网络字节序二进制值

如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略。

#include <arpa/inet.h>
/**
 * @brief inet_aton
 * @param __cp     输入参数包含ASCII表示的IP地址
 * @param __inp    输出参数将要用新的IP地址更新的结构
 * @return 
 */
extern int inet_aton (const char *__cp, struct in_addr *__inp);

3. char *inet_ntoa (struct in_addr __in)函数转换网络字节序二进制值返回标准的ASCII以点分十进制的地址值

该函数返回值指向保存点分十进制的字符串地址的指针,该字符串的空间为静态分配 的,所以在第二次调用这个函数时,意味着上一次调用并保存的结果将会被覆盖(重写)

#include <arpa/inet.h>

extern char *inet_ntoa (struct in_addr __in);

4. 代码示例:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char *argv[])
{
    char ip1[] = "192.168.0.74";
    char ip2[] = "211.100.21.179";
    struct in_addr addr1, addr2;
    long l1, l2;
    l1 = inet_addr(ip1);   //IP字符串——》网络字节
    l2 = inet_addr(ip2);
    printf("IP1: %s\nIP2: %s\n", ip1, ip2);
    printf("Addr1: %ld\nAddr2: %ld\n", l1, l2);
    
    memcpy(&addr1, &l1, 4); //复制4个字节大小
    memcpy(&addr2, &l2, 4);
    printf("%s <--> %s\n", inet_ntoa(addr1), inet_ntoa(addr2)); //注意:printf函数自右向左求值、覆盖
    printf("%s\n", inet_ntoa(addr1)); //网络字节 ——》IP字符串
    printf("%s\n", inet_ntoa(addr2));
    return 0;
}

IP1: 192.168.0.74
IP2: 211.100.21.179
Addr1: 1241557184
Addr2: 3004523731
192.168.0.74 <--> 192.168.0.74
192.168.0.74
211.100.21.179 

功能相似的两个函数同时支持IPv4和IPv6,p代表presentation表达,n代表numeric数值

1. int inet_pton(int domain, const char *str, void *addr)将标准的ASCII以点分十进制的地址值转化为网络传输的二进制数值格式

返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr)
{
//这两个函数的family参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。
//如果,以不被支持的地址族作为family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
    if (family == AF_INET) {
	    struct in_addr  in_val;
		if (inet_aton(strptr, &in_val)) {
		    memcpy(addrptr, &in_val, sizeof(in_val));
		    return (1);
		}
	}
	errno = EAFNOSUPPOPT;
	return (-1);
}

2. const char *inet_ntop(int domain, const void *addr, char *str, socklen_t size)将网络传输的二进制数值转化标准的ASCII以点分十进制的地址值格式

inet_ntop函数的strptr参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,返回值:若成功则为指向结构的指针,若出错则为NULL

#include <arpa/inet.h>

const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
	const u_char *p = (const u_char*)addrptr;
	if (family == AF_INET) {
		char temp[INET_ADDRSTRLEN];
		snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
		if (strlen(temp) >= len) {
		    errno = ENOSPC;
		    rturn (NULL);
		}
		strcpy(strptr, temp);
		return (strptr);
	}
	errno = EAFNOSUPPOPT;
	return (NULL);
}

3. 代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
    char ip[] = "192.168.0.74"; 
    struct in_addr addr;
    
    int ret = inet_pton(AF_INET, ip, (void *)&addr);   //IP字符串 ——》网络字节流
    if(0 == ret){
        printf("inet_pton error, return 0\n");
        return -1;
    }else{
        printf("inet_pton ip: %ld\n", addr.s_addr);
        printf("inet_pton ip: 0x%x\n", addr.s_addr);
    }

    const char *pstr = inet_ntop(AF_INET, (void *)&addr, ip, 128);  //网络字节流 ——》IP字符串
    if(NULL == pstr){
        printf("inet_ntop error, return NULL\n");
        return -1;
    }else{
        printf("inet_ntop ip: %s\n", ip);
    }
    
    return 0;
}

inet_pton ip: 1241557184
inet_pton ip: 0x4a00a8c0
inet_ntop ip: 192.168.0.74

TCP通信相关函数

1. int socket(family, type, protocol):创建套接字

/**
 * #include <sys/types.h>
 * #include <sys/socket.h>
 * @brief Socket  创建一个套接字用于通信
 * @param family
 *      AF_INET             IPv4地址协议
        AF_INET6            IPv6地址协议
        AF_LOCAL            UNIX域协议
        AF_ROUTE            路由套接字
        AF_KEY              密钥套接字
 * @param type  指定socket类型,
        SOCK_STREAM         流式套接字
        SOCKDGRAM           数据报套接字
        SOCK_SEQPACKET      有序分组套接字
        SOCKRAW             原始套接字,提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
 * @param protocol 协议类型 If PROTOCOL 为0,内核将会自动进行选择,可以默认填0
        IPPROTO_TCP         TCP传输协议
        IPPROTO_UDP         UDP传输协议
        IPPROTO_SCTP        SCTP传输协议

 * @return 成功返回非负整数套接字描述符;失败返回-1
 */
int socket(int family,int type,int protocol);

2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):绑定套接字

#include <sys/types.h>
#include <sys/socket.h>
/**
 * @brief bind
 * @param fd  绑定套接子
 * @param addr  要绑定的地址
 * @param addrlen  地址长度
 * @return 成功返回 0  失败返回 -1
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

3. int listen(int fd,int backlog):设置同时通信的最大套接字数量

#include <sys/types.h>
#include <sys/sock.h>

/**
 * @brief listen
 *   (1)一般来说,listen函数应该在调用socket和bind函数之后,调用accept函数之前调用
 *   (2)对于给定的监听套接字接口,内核要维护两个队列
 *          <1>已由客户发送并到达服务器,服务器正在等待完成对应的TCP三次握手过程
 *          <2>已经完成连接的队列
 * @param fd  socket函数返回的套接字
 * @param backlog  规定内核为此套接字排队的最大的连接个数
 * @return 成功返回 0  失败返回 -1
 */
int listen(int fd,int backlog);

4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)阻塞式监听客户端连接

#include <sys/types.h>
#include <sys/scoket.h>

/**
 * @brief accept  从已经完成连接队列返回第一个连接,如果已经完成连接队列为空,则阻
 * @param sockfd  服务器套接字
 * @param addr    将返回对等待的套接字地址
 * @param addrlen 返回对等方的套接字地址长度
 * @return  成功返回非负整数:对应和客户点连接的新套接字 ,失败返回-1
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

5. int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen)用来客户端和tcp服务器建立连接

#include <sys/types.h>
#include <sys/socket.h>

/**
 * @brief connect  用于建立与指定socket的连接
 * @param sockfd   标识一个未连接的socket
 * @param addr    指定要连接套接字的sockaddr结构体的指针
 * @param addrlen sockaddr结构体的字节长度
 * @return  0 on success, -1 for errors
 */  
int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen);

5. ssize_t read(int fd,void *ptr,size_t nbytes)一次读取指定字节长度数据

#include <unistd.h>

/**
 * @brief read
 * @param fd   将要读取数据的文件描述符
 * @param ptr  所读取到的数据的内存缓冲
 * @param nbytes   需要读取的数据量
 * @return  成功执行时,返回所读取的数据量;
 *      如果返回0, 表示已到达文件尾或是无可读取的数据
 *      失败返回-1,errno被设为以下的某个值
        EAGAIN:打开文件时设定了O_NONBLOCK标志,并且当前没有数据可读取
        EBADF:文件描述词无效,或者文件不可读
        EFAULT:参数buf指向的空间不可访问
        EINTR:数据读取前,操作被信号中断
        EINVAL:一个或者多个参数无效
        EIO:读写出错
        EISDIR:参数fd索引的时目录
 */
ssize_t read(int fd,void *ptr,size_t nbytes);

6. ssize_t write(int fd,const void *ptr,size_t nbytes)一次写入指定字节长度数据

/**
 * @brief write
 * @param fd   将要写入数据的文件描述符
 * @param ptr  所写入到的数据的内存缓冲
 * @param nbytes   需要写入的数据量
 * @return 成功执行时,返回所写入的数据量。失败返回-1,错误代码存入errno中
 */
ssize_t write(int fd,const void *ptr,size_t nbytes);

7. ssize_t Readn(int fd,void *vptr,size_t n);循环读取n个字节数据

/**
 * @brief Readn 从描述符fd中读取n个字节,存入vptr指针的位置
        1. 当剩余长度大于0的时候就一直读啊读
        2. 当read的返回值小于0的时候,做异常检测
        3. 当read的返回值等于0的时候,退出循环
        4. 当read的返回值大于0的时候,拿剩余长度减read的返回值,拿到新的剩余长度,读的入口指针加上read的返回值,进入步1
        5. 返回参数n减去剩余长度,即实际读取的总长度
 * @param fd
 * @param vptr
 * @param n
 * @return
 */
/* Read "n" bytes from a descriptor. */
ssize_t	Readn(int fd, void *vptr, size_t n)
{
	size_t	nleft;
	ssize_t	nread;
	char	*ptr;
 
	ptr = vptr;
	nleft = n;
	while (nleft > 0) 
    {
		if ( (nread = read(fd, ptr, nleft)) < 0) 
        {
			if (errno == EINTR)
				nread = 0;		/* and call read() again */
			else
				return(-1);
		} else if (nread == 0)
			break;				/* EOF */
 
		nleft -= nread;
		ptr   += nread;
	}
	return(n - nleft);		/* return >= 0 */
}
/* end readn */

8. ssize_t Writen(int fd,const void*vptr,size_t n)循环写入n个字节数据

/**
 * @brief Writen  向描述符fd中写入n个字节,从vptr位置开始写
        1. 当要写入的剩余长度大于0的时候就一直写啊写
        2. 当write的返回值小于0的时候,做异常检测
        3. 当write的返回值等于0的时候,出错退出程序
        4. 当write的返回值大于0的时候,拿剩余长度减去write的返回值,拿到新的剩余长度,写的入口指针加上write的返回值,进入步骤1
        5. 返回参数n的值,即期望写入的总长度
 * @param fd
 * @param vptr
 * @param n
 * @return 
 */
/* Write "n" bytes to a descriptor. */
ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;
 
	ptr = vptr;
	nleft = n;
	while (nleft > 0) 
    {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) 
        {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;		/* and call write() again */
			else
				return(-1);			/* error */
		}
 
		nleft -= nwritten;
		ptr   += nwritten;
	}
	return(n);
}
/* end writen */

9. ssize_t Readline(int fd, void *vptr, size_t maxlen) 读到’\n’或者读满缓冲区才返回

static ssize_t readch(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];

    if(read_cnt <= 0)
    {
again:
        if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
        {
            if(errno == EINTR)
            {
                goto again;
            }
            else
            {
                return -1;
            }
        }
        else if(read_cnt == 0)
        {
            return 0;
        }
        read_ptr = read_buf;
    }

    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char c, *ptr;

    ptr = vptr;

    for(n = 1; n < maxlen; n++)
    {
        if((rc = readch(fd, &c)) == 1)
        {
            *ptr++ = c;
            if(c == '\n')
            {
                break;
            }
        }
        else if(rc == 0)
        {
            *ptr = 0;
            return n - 1;
        }
        else
        {
            return (n - 1);
        }
    }

    *ptr = 0;
    return n;
}

10. int close(int fd) 关闭套接字

close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说

//一般不会立即关闭而经历TIME_WAIT的过程
#include<unistd.h>  
int close(int sockfd);     //返回成功为0,出错为-1

11. shutdown()函数切断进程共享的套接字的所有连接

#include<sys/socket.h>  

/**
 * @brief shutdown shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,
 *                  那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,
 *                  同时可利用shutdown的第二个参数选择断连的方式
 * @param sockfd 文件描述符
 * @param howto
        1.SHUT_RD:值为0,关闭连接的读这一半。
        2.SHUT_WR:值为1,关闭连接的写这一半。
        3.SHUT_RDWR:值为2,连接的读和写都关闭。
 * @return 成功为0,出错为-1.
 */
int shutdown(int sockfd,int howto);

12. recv()和send()函数

int recv(int sockfd,void *buf,int len,int flags);
int send(int sockfd,void *buf,int len,int flags); 
flags含义
0相当于read和write函数
MSG_DONTROUTE不查找表
MSG_OOB接受或者发送带外数据
MSG_PEEK查看数据,并不从系统缓冲区移走数据
MSG_WAITALL等待所有数据
  • MSG_DONTROUTE:是send函数使用的标志。这个标志告诉IP,目的主机在本地网络上面,没有必要查找表。这个标志一般用网络诊断和路由程序里面。

  • MSG_OOB:表示可以接收和发送带外的数据。关于带外数据我们以后会解释的。

  • MSG_PEEK:是recv函数的使用标志。表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容,这样下次读的时候仍然是一样的内容。一般在有多个进程读写数据时可以使用这个标志。

  • MSG_WAITALL:是recv函数的使用标志。表示等到所有的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足或者是发生了错误。

1)当读到了指定的字节时,函数正常返回。返回值等于len
2)当读到了文件的结尾时,函数正常返回。返回值小于len
3)当操作发生错误时返回-1,且设置错误为相应的错误号(errno)

UDP通信相关函数

1. recvfrom()函数

/**
     * @brief recvfrom
     * @param sockfd    套接字
     * @param buf       UDP数据报缓存区(包含所接收的数据
     * @param nbytes    缓冲区长度
     * @param flags     调用操作方式(一般设置为0)
     * @param from      指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换)
     * @param fromlen   指针,指向from结构体长度值
     * @return  成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中
     */
    int recvfrom(int sockfd, const void *buf, size_t nbytes,int flags,
    struct sockaddr *from, int *fromlen);

2. sendto()函数

sendto函数专用与UDP连接

/**
     * @brief sendto
     * @param sockfd    套接字
     * @param buf       带发送数据存储缓冲区
     * @param nbytes    要发送数据的字节数
     * @param flags     可选标志
     * @param destaddr  (目标地址)数据接收方
     * @param destlen   目标地址结构长度
     * @return 
     */
    ssize_t sendto(int sockfd,const void * buf,size_t nbytes,int flags,
        const struct sockaddr_in * destaddr,socklen_t destlen );

UDP组播通信相关函数

组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。

  1. 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
  2. 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
  3. 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
  4. 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
getsockopt()/setsockopt()的选项含义
IP_MULTICAST_TTL设置多播组数据的TTL值
IP_ADD_MEMBERSHIP在指定接口上加入组播组
IP_DROP_MEMBERSHIP退出组播组
IP_MULTICAST_IF获取默认接口或设置接口
IP_MULTICAST_LOOP禁止组播数据回送
    // IPv4 multicast request. 
    struct ip_mreq
      {
        // 多播组的IP地址 IP multicast address of group. 
        struct in_addr imr_multiaddr;
        //加入的客户端主机IP地址 Local IP address of interface. 
        struct in_addr imr_interface;
      };

    //加入组播组
    ip_mreq multiCast;
    multiCast.imr_interface.s_addr=htonl(INADDR_ANY);     //本地某一网络设备接口的IP地址。
    multiCast.imr_multiaddr.s_addr=inet_addr("234.2.2.2"); //组播组的IP地址。
    setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&multiCast,sizeof(multiCast));

常见的错误码

  1. 网络通信中 TCP 产生 RST 的三个条件分析

参考文献

  1. LInux Tcp 延迟确认问题
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:56:35  更:2022-09-13 11:59:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 18:44:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计