• 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    类型长度描述
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 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));
    
    • 1
    • 2
    • 3
    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套接字地址结构,在中声明
    typedef uint32_t in_addr_t;  //32位(unsigned int)的ip地址,
    struct in_addr
    {
        in_addr_t s_addr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    struct sockaddr和struct sockaddr_in

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

    //sizeof(sockaddr_in)=16,定义在#include 
    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 
    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
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    字节排序函数

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

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

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

    #include 
    
    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;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

    #include 
    
    uint16_t htons(uint16_t host16bitvalue);
    uint32_t htonl(uint32_t host32bitvalue);  //均返回网络字节序
    
    uint16_t ntohs(uint16_t net16bitvalue);
    uint32_t ntohl(uint32_t net32bitvalue);  //均返回主机字节序
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    地址转换函数:

    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 
    
    in_addr_t inet_addr(const char *cp);  //in_addr_t-->uint32_t
    
    • 1
    • 2
    • 3

    输入是点分的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 
    /**
     * @brief inet_aton
     * @param __cp     输入参数包含ASCII表示的IP地址
     * @param __inp    输出参数将要用新的IP地址更新的结构
     * @return 
     */
    extern int inet_aton (const char *__cp, struct in_addr *__inp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    3. char *inet_ntoa (struct in_addr __in)函数转换网络字节序二进制值返回标准的ASCII以点分十进制的地址值

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

    #include 
    
    extern char *inet_ntoa (struct in_addr __in);
    
    • 1
    • 2
    • 3
    4. 代码示例:
    #include 
    #include 
    #include 
    #include 
    #include 
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

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

    #include 
    
    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2. const char *inet_ntop(int domain, const void *addr, char *str, socklen_t size)将网络传输的二进制数值转化标准的ASCII以点分十进制的地址值格式

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

    #include 
    
    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    3. 代码示例:
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    inet_pton ip: 1241557184
    inet_pton ip: 0x4a00a8c0
    inet_ntop ip: 192.168.0.74
    
    • 1
    • 2
    • 3

    TCP通信相关函数

    1. int socket(family, type, protocol):创建套接字
    /**
     * #include 
     * #include 
     * @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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):绑定套接字
    #include 
    #include 
    /**
     * @brief bind
     * @param fd  绑定套接子
     * @param addr  要绑定的地址
     * @param addrlen  地址长度
     * @return 成功返回 0  失败返回 -1
     */
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    3. int listen(int fd,int backlog):设置同时通信的最大套接字数量
    #include 
    #include 
    
    /**
     * @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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)阻塞式监听客户端连接
    #include 
    #include 
    
    /**
     * @brief accept  从已经完成连接队列返回第一个连接,如果已经完成连接队列为空,则阻
     * @param sockfd  服务器套接字
     * @param addr    将返回对等待的套接字地址
     * @param addrlen 返回对等方的套接字地址长度
     * @return  成功返回非负整数:对应和客户点连接的新套接字 ,失败返回-1
     */
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    5. int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen)用来客户端和tcp服务器建立连接
    #include 
    #include 
    
    /**
     * @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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    5. ssize_t read(int fd,void *ptr,size_t nbytes)一次读取指定字节长度数据
    #include 
    
    /**
     * @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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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 */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    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 */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    10. int close(int fd) 关闭套接字

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

    //一般不会立即关闭而经历TIME_WAIT的过程
    #include  
    int close(int sockfd);     //返回成功为0,出错为-1
    
    • 1
    • 2
    • 3
    11. shutdown()函数切断进程共享的套接字的所有连接
    #include  
    
    /**
     * @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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    12. recv()和send()函数
    int recv(int sockfd,void *buf,int len,int flags);
    int send(int sockfd,void *buf,int len,int flags); 
    
    • 1
    • 2
    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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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 );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    常见的错误码

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

    参考文献

    1. LInux Tcp 延迟确认问题
  • 相关阅读:
    全部常用邮件端口25、109、110、143、465、995、993、994
    c++的作用域 (局部域,类域,名字命名空间,文件域)
    Vue.js 3.x 响应式系统原理
    Binder
    【MySQL】sql调优实战教学
    【Python】列表list
    怎么把PDF转换成图片?这三种转换方法都可以实现
    ostringstream 多线程下性能问题探究
    2023-亲测有效-git clone失败怎么办?用代理?加git?
    CRC循环冗余码计算
  • 原文地址:https://blog.csdn.net/bwangk/article/details/126824014