linux 杂乱汇总


SO_LINGER作用

设置函数close()关闭TCP连接时的行为。缺省close()的行为是,如果有数据残留在socket发送缓冲区中则系统将继续发送这些数据给对方,等待被确认,然后返回。

利用此选项,可以将此缺省行为设置为以下两种
  a.立即关闭该连接,通过发送RST分组(而不是用正常的FIN|ACK|FIN|ACK四个分组)来关闭该连接。至于发送缓冲区中如果有未发送完的数据,则丢弃。
  主动关闭一方的TCP状态则跳过TIMEWAIT,直接进入CLOSED。网上很多人想利用这一点来解决服务器上出现大量的TIMEWAIT状态的socket的问题,
  但是,这并不是一个好主意,这种关闭方式的用途并不在这儿,实际用途在于服务器在应用层的需求。
  b.将连接的关闭设置一个超时。如果socket发送缓冲区中仍残留数据,进程进入睡眠,内核进入定时状态去尽量去发送这些数据。
    在超时之前,如果所有数据都发送完且被对方确认,内核用正常的FIN|ACK|FIN|ACK四个分组来关闭该连接,close()成功返回。
    如果超时之时,数据仍然未能成功发送及被确认,用上述a方式来关闭此连接。close()返回EWOULDBLOCK。


SO_LINGER选项使用如下结构:

struct linger {

     int l_onoff;

     int l_linger;

};

l_onoff为0,则该选项关闭,l_linger的值被忽略,close()用上述缺省方式关闭连接。

l_onoff非0,l_linger为0,close()用上述a方式关闭连接。

l_onoff非0,l_linger非0,close()用上述b方式关闭连接。


值得一说的是,不管你用什么方式使用SO_LINGER,都需要大量耐心的测试来确保你的服务器程序确实是按照你的意愿在跑,
因为这个选项对服务器处理小量请求的行为影响非常细微,简单的功能测试很难验证它的行为,上线后量大的情况下可能会出问题,
让你的服务器马上下线,大并发量的模拟实际场景压测才能保证其正确性:)


SO_LINGER还有一个特点是可以用来控制TCP异常关闭需求。

终止一个连接的正常方式是发送FIN。在发送缓冲区中所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。

但我们有时也有可能发送一个RST报文段而不是FIN来中途关闭一个连接。这称为异常关闭。

进程关闭socket的默认方式是正常关闭,如果需要异常关闭,利用SO_LINGER选项来控制。

异常关闭一个连接对应用程序来说有两个优点:

(1)丢弃任何待发的已经无意义的数据,并立即发送RST报文段;
(2)RST的接收方利用关闭方式来区分另一端执行的是异常关闭还是正常关闭。

值得注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接。程序行为如下:

阻塞模型下,内核无法主动通知应用层出错,只有应用层主动调用read()或者write()这样的IO系统调用时,内核才会利用出错来通知应用层对端RST。

非阻塞模型下,select或者epoll会返回sockfd可读,应用层对其进行读取时,read()会报错RST。

=========================================================================
wireshark,它的前身是赫赫有名的Ethereal。wireshark以太网帧的封包格式为:
Frame = Ethernet Header + IP Header + TCP Header + TCP Segment Data
(1)Ethernet Header = 14 Byte = Dst Physical Address(6 Byte)+ Src Physical Address(6 Byte)+ Type(2 Byte),以太网帧头以下称之为数据帧。
(2)IP Header = 20 Byte(without options field),数据在IP层称为Datagram,分片称为Fragment。
(3)TCP Header = 20 Byte(without options field),数据在TCP层称为Stream,分段称为Segment(UDP中称为Message)。
(4)54个字节后为TCP数据负载部分(Data Portion),即应用层用户数据。

Ethernet Header以下的IP数据报最大传输单位为MTU(Maximum Transmission Unit,Effect of short board),
对于大多数使用以太网的局域网来说,MTU=1500。
TCP Maximum Segment Size (MSS)
TCP数据包每次能够传输的最大数据分段为MSS,为了达到最佳的传输效能,在建立TCP连接时双方协商MSS值,双方提供的MSS值的最小值为这次连接的最大MSS值。
MSS往往基于MTU计算出来,通常MSS=MTU-sizeof(IP Header)-sizeof(TCP Header)=1500-20-20=1460。

这样,数据经过本地TCP层分段后,交给本地IP层,在本地IP层就不需要分片了。但是在下一跳路由(Next Hop)的邻居路由器上可能发生IP分片!
因为路由器的网卡的MTU可能小于需要转发的IP数据报的大小。这时候,在路由器上可能发生两种情况:
(1).如果源发送端设置了这个IP数据包可以分片(May Fragment,DF=0),路由器将IP数据报分片后转发。
(2).如果源发送端设置了这个IP数据报不可以分片(Don’t Fragment,DF=1),路由器将IP数据报丢弃,并发送ICMP分片错误消息给源发送端。

setsockopt(sd, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val));

=========================================================================

//查看网络状态值
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

enum {
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING,    /* Now a valid state */
 
    TCP_MAX_STATES  /* Leave at the end! */
};

在TCP网络编程模型中,无论是客户端还是服务端,在网络编程的过程中都需要判断连接的对方网络状态是否正常。
在linux系统中,有很多种方式可以判断连接的对方网络是否已经断开。

通过错误码和信号判断
通过select系统函数判断
通过TCP_INFO套接字选项判断
通过SO_KEEPALIVE套接字选项判断
通过SO_RCVTIMEO/SO_SNDTIMEO判断

(一)通过错误码和信号判断
(1)写数据信号和错误码判断
在写TCP连接数据的时候,如果对方连接已经正常断开,那么写数据端将会收到一个SIGPIPE信号,可以通过这个信号知道对方连接已经断开。
该信号信号会终止当前进程,如果不在对方连接断开不退出进程,那么就应该注册信号函数。
同时,如果对方连接已经正常断开,那么write写数据端将会返回写错误。返回的写长度为-1,此时的错误码为:32,对应错误值为EPIPE;
因此可以写数据时write的返回值和错误码来判断对方连接是否已经断开了。

(2)读数据判断返回值
如果当前是默认的阻塞模式读取,那么此时read读取返回的长度为0,错误码也是为0,其实表示读取成功。
这里需要注意read 和recv接口的默认返回值是不一样的,使用recv接口也会返回EPIPE错误码。

(二)通过select系统函数判断
select实际是IO复用的一个接口,它可以同时检测多个连接是否有数据可读写操作,并且可以设置检测的超时时间。
在点对点的连接中如果select超时,它返回值为0;

当出现异常的时候,返回-1,如果对方断开可能收到104的错误码,也就是ECONNRESET,表示连接被重置
当select返回1,表示正常,如果read此时返回的值为0,表示对方连接已经断开。
(三)通过TCP_INFO套接字选项判断
通过getsockopt函数可以获取TCP连接的连接状态,当状态为ESTABLISHED的时候表示该连接正常。TCP的其它状态还有:

CLOSED:表示初始状态。对服务端和C客户端双方都一样。
LISTEN:表示监听状态。服务端调用了listen函数,可以开始accept连接了。
SYN_SENT:表示客户端已经发送了SYN报文。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN。
SYN_RCVD:表示服务端收到客户端发送SYN报文。服务端收到这个报文后,进入SYN_RCVD状态,然后发送ACK+SYN给客户端。
ESTABLISHED:表示连接已经建立成功了。服务端发送完ACK+SYN后进入该状态,客户端收到ACK后也进入该状态。
FIN_WAIT_1:表示主动关闭连接。无论哪方调用close函数发送FIN报文都会进入这个这个状态。
FIN_WAIT_2:表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的ACK后,会进入该状态。
TIME_WAIT:表示收到对方的FIN报文并发送了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FIN_WAIT_1状态下,收到对方同时带FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING:表示双方同时关闭连接。如果双方几乎同时调用close函数,那么会出现双方同时发送FIN报文的情况,此时就会出现CLOSING状态,表示双方都在关闭连接。
CLOSE_WAIT:表示被动关闭方等待关闭。当收到对方调用close函数发送的FIN报文时,回应对方ACK报文,此时进入CLOSE_WAIT状态。
LAST_ACK:表示被动关闭方发送FIN报文后,等待对方的ACK报文状态,当收到ACK后进入CLOSED状态。

(四)通过SO_KEEPALIVE套接字选项判断
选项SO_KEEPALIVE用于设置TCP连接的保持,当设置此项后,连接会测试连接的状态。这个选项用于可能长时间没有数据交流的连接,通常在服务器端进行设置。

当设置SO_KEEPALIVE选项后,如果在两个小时内没有数据通信时,TCP会自动发送一个活动探测数据报文,对方必须对此进行响应,通常有如下3种情况。

TCP的连接正常,发送一个ACK响应,这个过程应用层是不知道的。再过两个小时,又会再发送一个。
对方发送RST响应,对方在2个小时内进行了重启或者崩溃。之前的连接己经失效,套接字收到一个ECONNRESET错误,之前的套接字关闭。
如果对方没有任何响应,则本机会发送另外8个活动探测报文,时间的间隔为75s,当第一个活动报文发送11分15秒后仍然没有收到对方的任何响应,则放弃探测,
套接字错误类型设置为ETIMEOUT,并关闭套接字连接。如果收到一个ICMP控制报文响应,此时套接字也关闭,这种情况通常收到的是一个主机不可达的ICMP报文,
此时套接字错误类型设置为EHOSTUNREACH,并关闭套接字连接。
   
 SO_KEEPALIVE的使用场景主要是在可能发送长时间无数据响应的TCP连接,例如Telnet会话,经常会出现打开一个telnet客户端后,长时间不用的情况,
 这需要服务器或 者客户端有一个探测机制知道对方是否仍然活动。根据探测结果服务器会释放己经失效的客户端,保证服务器资源的有效性,
 例如有的telnet客户端没有按照正常步骤进行关闭。

(五)通过SO_RCVTIMEO/SO_SNDTIMEO判断
这个是通过套接字的SO_RCVTIMEO、SO_SNDTIMEO来设置收发数据超时。对于前面的几种判断方式,都是基于对方正常网络断开后,主机才能够正常的判断到网络状态。
如果连接的某一方突然断电,主机并不能知道对方设备突然断电,通过TCP_INFO查询到的也是网络正常,但实际情况是这是网络连接已经断开了。

这时,可以使用收发数据超时来判断:
    如果设置的时间没有收到数据,read时会返回-1,同时有错误码EAGAIN产生,这时是可以判断出对连接已经断开了。
    这种方式的确定就是,如果设定的一段时间没有收发数据,就会被判断为超时断开连接。

(六)自定义通信心跳判断
在一些比较重要的命令收发链接中,一般是客户端和服务端会建立心跳机制,心跳时间间隔根据不同的业务需求而不同。
当约定的时间段内没有收到心跳数据包,就可以判断对方是否已经断开了连接。

这种方式非常简单,对于嵌入式设备而言,主要的缺点是心跳会耗费流量,同时会增加一点点系统负载,并且不适合并发连接的情况

==========================
解耦目的:简化模块间依赖的复杂性,达到高内聚,松耦合。

解耦方法:封装与抽象、中间层、模块化、以及其它设计模式的思想和原则;

=================================
struct tcp_info {
    __u8    tcpi_state;           //tcp state: TCP_SYN_SENT,TCP_SYN_RECV,TCP_FIN_WAIT1,TCP_CLOSE etc
    __u8    tcpi_ca_state;     //congestion state:
    __u8    tcpi_retransmits;  //重传数,表示当前待重传的包数,这个值在重传完毕后清零
    __u8    tcpi_probes;        ///* 持续定时器或保活定时器发送且未确认的段数*/
    __u8    tcpi_backoff;        //用来计算持续定时器的下一个设计值的指数退避算法指数,在传送超时是会递增。
    __u8    tcpi_options;        //tcp头部选项是否包含:扩展因子、时间戳、MSS等内容
    __u8    tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4; //扩展因子数值
    __u8    tcpi_delivery_rate_app_limited:1;  //限速标志

    __u32    tcpi_rto;        //重传超时时间,这个和RTT有关系,RTT越大,rto越大
    __u32    tcpi_ato;        //用来延时确认的估值,单位为微秒. 
                            //在收到TCP报文时,会根据本次与上次接收的时间间隔来调整改制,在设置延迟确认定时器也会根据
                            //条件修改该值
    __u32    tcpi_snd_mss;    // 本端的MSS
    __u32    tcpi_rcv_mss;    // 对端的MSS

    __u32    tcpi_unacked;    //未确认的数据段数
    __u32    tcpi_sacked;    //2个含义:server端在listen阶段,可以接收连接的数量;收到的SACK报文数量
    __u32    tcpi_lost;        //本端在发送出去被丢失的报文数。重传完成后清零
    __u32    tcpi_retrans;   /* 重传且未确认的数据段数 */
    __u32    tcpi_fackets;

    /* Times. */
    __u32    tcpi_last_data_sent;    //当前时间-最近一个包的发送时间,单位是毫秒
    __u32    tcpi_last_ack_sent;     /* 未使用*/
    __u32    tcpi_last_data_recv;    //当前时间-最近接收数据包的时间,单位是毫秒
    __u32    tcpi_last_ack_recv;     //当前时间-最近接收ack的时间,单位是毫秒

    /* Metrics. */
    __u32    tcpi_pmtu;            /* 最后一次更新的路径MTU */
    __u32    tcpi_rcv_ssthresh;   //当前接收窗口的大小
    __u32    tcpi_rtt;            //smoothed round trip time,微妙    
    __u32    tcpi_rttvar;        //描述RTT的平均偏差,该值越大,说明RTT抖动越大
    __u32    tcpi_snd_ssthresh;  //拥塞控制慢开始阈值
    __u32    tcpi_snd_cwnd;        //拥塞控制窗口大小
    __u32    tcpi_advmss;        /* 本端能接受的MSS上限,在建立连接时用来通告对端 */
    __u32    tcpi_reordering;    /* 没有丢包时,可以重新排序的数据段数 */

    __u32    tcpi_rcv_rtt;        // 作为接收端,测出的RTT值,单位为微秒. 这个值不是对方计算并传送过来的rtt,而是作为接收端,在没发送数据的情况下
                                // 通过接收发送端发送的数据的情况计算得到的rtt值。在数据发送方,如果不接受数据,这个值一般情况下为0。
    __u32    tcpi_rcv_space;        /* 当前接收缓存的大小 */

    __u32    tcpi_total_retrans;  //统计总重传的包数,持续增长。

    __u64    tcpi_pacing_rate;        //发送速率
    __u64    tcpi_max_pacing_rate;    //最大发送速率,默认是unlimited,可以通过SO_MAX_PACING_RATE来设置
    __u64    tcpi_bytes_acked;    /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
    __u64    tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
    __u32    tcpi_segs_out;         /* RFC4898 tcpEStatsPerfSegsOut */
    __u32    tcpi_segs_in;         /* RFC4898 tcpEStatsPerfSegsIn */

    __u32    tcpi_notsent_bytes;
    __u32    tcpi_min_rtt;
    __u32    tcpi_data_segs_in;    /* RFC4898 tcpEStatsDataSegsIn */
    __u32    tcpi_data_segs_out;    /* RFC4898 tcpEStatsDataSegsOut */

    __u64   tcpi_delivery_rate;

    __u64    tcpi_busy_time;      /* Time (usec) busy sending data */
    __u64    tcpi_rwnd_limited;   /* Time (usec) limited by receive window */
    __u64    tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */
};

你可能感兴趣的:(linux,运维,服务器)