【TCP 关键知识点】

来源:互联网 发布:sql,字符串截断错误 编辑:程序博客网 时间:2024/05/19 15:24


(1) 设置socket为非阻塞端口

int setNonBolck(int nfd)

{

//set non-block socket

int bFlags = fcntl(nfd, F_GETFL, 0);

if(bFlags < 0)

{

std::cout<<"get socket flags failed."<<std::endl;

return -1;

}

bFlags |= O_NONBLOCK ;

if(fcntl(nfd, F_SETFL, bFlags) < 0)

{

std::cout<<"set socket flags failed."<<std::endl;

return -1;

}

return 0;

}

(2)设置socket keepalive

//get keepalive

int nGetOpt = -1;

socklen_t optLen = (socklen_t)sizeof(nGetOpt);

getsockopt(nConnfd, SOL_SOCKET, SO_KEEPALIVE , &nGetOpt, &optLen);

std::cout<<"the value of keepalive: "<<nGetOpt<<std::endl;

//一旦设置了keeplive, 该socket就会定时发起心跳消息到对方

int nSetOpt = 1;

setsockopt(nConnfd, SOL_SOCKET, SO_KEEPALIVE, &nSetOpt, sizeof(nSetOpt));

//get keepalive

getsockopt(nConnfd, SOL_SOCKET, SO_KEEPALIVE, &nGetOpt, &optLen);

std::cout<<"the value of keepalive: "<<nGetOpt<<std::endl;

(3)设置 reuseaddr

int nGetOpt = -1;

socklen_t optLen = (socklen_t)sizeof(nGetOpt);

getsockopt(nListenfd, SOL_SOCKET, SO_REUSEADDR , &nGetOpt, &optLen);

std::cout<<"the value of reuseaddr: "<<nGetOpt<<std::endl;

int nSetOpt = 1;

setsockopt(nListenfd, SOL_SOCKET, SO_REUSEADDR, &nSetOpt, sizeof(nSetOpt));

getsockopt(nListenfd, SOL_SOCKET, SO_REUSEADDR, &nGetOpt, &optLen);

std::cout<<"the value of reuseaddr: "<<nGetOpt<<std::endl;

(4)Socket发送缓冲区/接收缓冲区

单个连接最大吞吐量取决于发送窗口大小和RTT时延。SuSE10默认最大发送/接收缓冲区为128k。我们可以通过修改/proc/sys/net/core目录下wmem_max和rmem_max两个参数。

(5)完成连接队列长度(backlog)

监听队列的长度通过应用层的listern的backlog参数指定,但是最大不超过内核设置,Suse10默认是128,我们也可以通过修改:

net.core.somaxconn = 1024

测试了一下即使 baklog=1, 启动多个connect 进行连接并且server端不accpet, 最终可以得出 所有conect还是能成功, 只是前2个处理会快

后面的连接就开始比较慢了(9秒), 说明linux系统内部对于超过backlog的连接有一套方案不过处理会比较慢。

10:58:59.213449 <1048 3082247072> socket: 4

10:58:59.213517 <1048 3082247072> start to connect

10:58:59.213638 <1048 3082247072> complete to connect: 0

10:58:59.213708 <1048 3082247072> connect success!

--- 191微秒

10:58:59.213782 <1048 3082247072> socket: 5

10:58:59.213846 <1048 3082247072> start to connect

10:58:59.213926 <1048 3082247072> complete to connect: 0

10:58:59.213991 <1048 3082247072> connect success!

--- 145微秒

10:58:59.214059 <1048 3082247072> socket: 6

10:58:59.214123 <1048 3082247072> start to connect

10:59:08.212201 <1048 3082247072> complete to connect: 0

10:59:08.212240 <1048 3082247072> connect success!

--- 大约9秒钟

自从linux内核2.2开始, TCP socket 中的backlog参数的行为已改变。

现在它指定了 已完成连接socket队列(三路握手完成)的长度 ,这些连接还没有被 accept 从队列中取出;

If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that

value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the  value 128.

如果backlog大于/proc/sys/net/core/somaxconn设置的值, 就取/proc/sys/net/core/somaxconn

/proc/sys/net/core/somaxconn 缺省 128

在linux 2.4.25 内核前SOMAXCONN=128 是硬编码在内核中

未完成连接socket队列的最大长度可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog 设置,当syncookies激活的时候,逻辑上就不存在最大长度,并且这个设置被忽略。

(6)未完成连接队列长度(Syn)

Syn 未完成连接 队列长度,可以适当增大提供连接并发数 ,如果该队列已满, 后面的连接请求就会被丢弃, 并且不发送RST。

net.ipv4.tcp_max_syn_backlog = 1024

/proc/sys/net/ipv4/tcp_abort_on_overflow

缺省为false。一个布尔类型的标志,控制着当有很多的连接请求时内核的行为。启用的话, 如果未完成连接已满,内核将主动地发送RST包,而不是直接丢弃 。

待处理连接最大数( 两个队列之和 ,包括未建立连接队列、建立连接队列)

net.core.somaxconn = 262144 (0x40000)

网络设备(网卡)接收数据包的速率比内核处理这些包的速率快时,允许送到 主内存DMA环形缓冲队列 的数据包的最大数目。

主内存DMA环形缓冲队列解决网卡处理数据的速率远高于接收数据的最大速率导致网卡存在堵塞情况。

net.core.netdev_max_backlog = 262144  (0x40000)

大并发服务器建议上面3个系统参数都设置为: 262144(0x40000)

(7)backlog参数

同时打开:

------------------------------------



accept            只是从建立连接队列中取一个连接使用,连接成功与否和调用accpt没有任何关系

connect api       内部有重试机制也有系统参数设置  net.ipv4.tcp_syn_retries = 5

listen(backlog)  backlog不同系统实现是不同的。 4.2BSD手册定义为 待处理连接最大数(两个队列之和);Berkeley的实现则是增加了模糊因子1.5

                  如果backlog小于1,则backlog被置为1;若backlog大于SOMAXCONN(定义在winsock.h中,值为5),则backlog被置为SOMAXCONN。

  《Unix网络编程》中讲到的是半连接队列和连接队列之和的上限,也就是说这个说法对Linux不适用。

  自从linux内核2.2开始, TCP socket 中的backlog参数的行为已改变。 现在它指定了 已完成连接socket队列(三路握手完成)的长度  

(8)关闭连接

同时关闭

发完fin报文后,没有收到 ack, 而是对端fin, 说明两端同时关闭,

这个时候本端soket状态为 CLOSING


TIME_WAIT (主动关闭)

MSL(Maximum Segment Lifetime)

当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIMEWAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的 Socket(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。 这个连接只能在2MSL结束后才能再被使用

MSL(Maximum Segment Lifetime)

每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。

我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。

对于Linux系统,MSL缺省值为30秒。

对于Solaris/Winodws ,MSL缺省值为2分钟。

我们知道在实际应用中,对IP数据报TTL的限制是基于跳数,而不是定时器。对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并接收最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

TIME_WAIT状态维持时间是两个MSL时间长度,也就是在1-4分钟。

Linux 没有直接对MSL设置的系统参数,对TIME_WAIT的控制需要就是依据下面的参数

net.ipv4.tcp_tw_reuse = 1        表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1      表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout = 30    修改系統默认的time_wait时间  ( net.ipv4.tcp_fin_timeout 同时影响 time_wait和 fin_wait2的超时时间 )

/proc/sys/net/ipv4/tcp_max_tw_buckets   --- 这个可选

默认值是180000。系统同时保持timewait套接字的最大数量。如果超过这个数字,time-wait套接字将立刻被清除并打印警告信息。这个限制仅仅是为了防止简单的 DoS攻击,你绝对不能过分依靠它或者人为地减小这个值,如果网络实际需要大于缺省值,更应该增加这个值(如果增加了内存之后)。

CLOSE_WAIT (Server 端 收到对端fin,本端 没有执行close)

如果CLOSE_WAIT过多,将会耗尽你的socket资源,最终系统没有socket资源使用。

解决方法:为每个socket 设置 keeplive 机制, socket开通keepalive , 就会定时向对端发检测报文。

keeplive 缺省7200秒(2小时), 可以修改系统参数改变

net.ipv4.tcp_keepalive_intvl = 75  ---- keeplive报文发送失败后,重发间隔时间 (秒)

net.ipv4.tcp_keepalive_probes = 9   ---- keeplive报文发送失败后,重发次数, 如果次数达到认为对端已经close, 就可以close本端socket

net.ipv4.tcp_keepalive_time = 7200  ---- keeplive发送周期 (秒)

CLOSE_WAIT状态将 一直保持 ,直到本端调用close将socket关闭为止

FIN_WAIT_2 (Client 端)

发送fin,收到ack,但是没有收到对端fin报文, 对端没有执行close

许多伯克利实现采用如下方式来防止这种在FINWAIT2状态的无限等待。如果执行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接收数据,就设置一

个定时器。如果这个连接空闲 10分钟75秒(不同系统可能时间不太一样) ,TCP将 进入CLOSED 状态。在实现代码的注释中确认这个实现代码违背协议的规范。

解决方法同上 keepalive

可以设置 net.ipv4.tcp_fin_timeout = 60 修改socket在FIN_WAIT_2状态下的超时时间

(9)防范SYN攻击

增大半连接数量

net.ipv4.tcp_max_syn_backlog = 1024

减少syn&ack重试次数

net.ipv4.tcp_synack_retries = 5

SYN-ACK重传次数 :服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同。

使用socket cookie 技术

net.ipv4.tcp_syncookies = 1

(10)sysctl 设置系统参数

使用 sysctl 对linux系统进行修改操作

sysctl -a  显示当前所有的系统参数

(11)修改本端port范围

表示用于向外连接的端口范围。缺省情況下很小:32768到61000 (总共28232个, 2万8千左右 ),改为1024到65000 (总共 63976个,6万4千左右 )

net.ipv4.ip_local_port_range="1024 65000"

【注意】:linux下端口port的申请是递增的,这点不同于fd句柄申请,fd被系统回收后下次再申请fd,获取到的是之前释放的fd句柄。

(12)其他

/proc/sys/net/ipv4/tcp_abort_on_overflow

缺省为false。一个布尔类型的标志,控制着当有很多的连接请求时内核的行为。启用的话, 如果服务超载,内核将主动地发送RST包,而不是直接丢弃 。

/proc/sys/net/ipv4/tcp_fin_timeout

如 果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60秒。2.2 内核的通常值是180秒,你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能吃掉1.5K内存,但是它们的生存期长些。

/proc/sys/net/ipv4/tcp_timestamps   ( 用于高并发链路 )

时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号。该时间戳选项能够让内核接受这种"异常"的数据包。

(13)

http://www.ibm.com/developerworks/cn/linux/l-hisock.html



0 0
原创粉丝点击