http://blog.csdn.net/hailuoing/article/details/8250648
通用套接口选项
level(级别)
SOL_SOCKET
SO_BROADCAST允许发送广播数据 适用于UDP socket.其意义是允许UDP socket「广播」(broadcast)讯息到网路上。启用或禁止进程发送广播消息的能力。只有数据报套接口支持广播, 并且还必须在支持广播消息的网络上(如以太网、令牌环网等)。如果目的地址是广播地址但此选项未设,则返回EACCES错误。default = off 设置: BOOL bBroadcast = TRUE; setsockopt( s, SOL_SOCKET, SO_BROADCAST, &bBroadcast, sizeof( BOOL ) );
SO_DEBUG允许调试 仅仅TCP支持。当打开此选项时,内核对TCP在此套接口所发送和接收的所有分组跟踪详细信息。这些信息保存在内核的环形缓冲区内, 可由程序trpt进行检查default = off 设置: BOOL bDebug = TRUE; setsockopt( s, SOL_SOCKET, SO_DEBUG, & bDebug, sizeof( BOOL ) );
SO_DONTROUTE不查找路由 标识是否允许通过网关发送数据包,如果被设置为TRUE,则只能发送局域网内部的数据包。default = off 设置: BOOL bDontroute = TRUE; setsockopt( s, SOL_SOCKET, SO_ DONTROUTE, & bDontroute, sizeof( BOOL ) );
SO_ERROR获得套接字错误 当套接口上发生错误时,源自Berkeley的内核中的协议模块将此套接口的名为so_error的变量设为标准的UNIX Exxx值中的一个, 它称为此套接口的待处理错误(pending error)。内核可立即以以下两种方式通知进程: 1.如果进程阻塞于次套接口的select调用,则无论是检查可读条件还是可写条件,select都返回并设置其中一个或所有两个条件。 2.如果进程使用信号驱动I/O模型,则给进程或进程组生成信号SIGIO。 进程然后可以通过获取SO_ERROR套接口选项来得到so_error的值。由getsockopt返回的整数值就是此套接口的待处理错误。so_error随后由内核复位为0。 当进程调用read且没有数据返回时,如果so_error为非0值,则read返回-1且errno设为so_error的值,接着so_error的值被复位为0。 如果此套接口上有数据在排队,则read返回那些数据而不是返回错误条件。 如果进程调用write时so_error为非0值,则write返回-1且errno设为so_error的值,随后so_error也被复位。default = 0 设置: int iError = 0; setsockopt( s, SOL_SOCKET, SO_ ERROR, &iError, sizeof(int) );
SO_KEEPALIVE保持连接 检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换, TCP就自动给对方发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况: 对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。 对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。 对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟15秒后若仍无响应就放弃。 套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃, 但是不可达,这种情况下待处理错误被置为EHOSTUNREACH。 tcp_keepidle保持TCP/IP连接的时间,单位为0.5秒,缺省值为14,400,即两个小时,通过TCP_KEEPIDLE设置;tcp_keepinittcp保持存活探测timeout值, 单位为0.5秒,缺省值为150,通过TCP_KEEPINITTCP设置;tcp_keepintvl后续探测间隔,单位为0.5秒,缺省值为150,通过TCP_KEEPINTVL设置; tcp_keepcnt后续探测次数,单位为次,缺省值为8,通过TCP_KEEPCNT设置 default = off 设置: BOOL bKeepalive = TRUE; setsockopt( s, SOL_SOCKET, SO_ KEEPALIVE, &bKeepalive, sizeof( BOOL ) ); int keepIdle = 600; int keepInterval = 100; int keepCount = 8; setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int)); setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, &keepInterval, sizeof(int)); setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
SO_DONTLINGER若为真,则SO_LINGER选项被禁止,影响close行为 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程。排队的数据将丢失。default = off 设置: BOOL bDontlinger = TRUE; setsockopt( s, SOL_SOCKET, SO_ DONTLINGER, & bDontlinger, sizeof( BOOL ) ); SO_LINGER延迟关闭连接struct linger ,影响close行为 SO_LINGER选项用来改变此缺省设置。使用如下结构: struct linger { int l_onoff; /* 0 = off, nozero = on */ int l_linger; /* linger time */ }; 选项 间隔 关闭方式 等待关闭与否 SO_DONTLINGER 不关心 优雅 否 SO_LINGER 零 强制 否 SO_LINGER 非零 优雅 是 有下列三种情况: 1. l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回; 2. l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方, 而不是通常的四分组终止序列,这避免了TIME_WAIT状态; 3. l_onoff为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态, 直到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的, 如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。 close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。 default l_onoff = 0 , l_linger = 0 设置: linger m_sLinger; m_sLinger.l_onoff = 1; //在调用closesocket()时还有数据未发送完,允许等待 //若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭 m_sLinger.l_linger = 5; //设置等待时间为5秒 setsockopt( s, SOL_SOCKET, SO_LINGER, &m_sLinger, sizeof( linger ) );
SO_OOBINLINE带外数据放入正常数据流,在普通数据流中接收带外数据
带外(00B)数据是特定于用户的数据,仅对面向连接的(流)套接字有意义。流数据通常是按发送次序接收的。OOB数据的接收与它在流中的位置无关(与发送它的次序无关)。这是有可能的,原因是数据是按以下方式标记的,在将数据从程序A发送至程序B时,会通知程序B数据到达。OOB数据仅在AF_INET(SOCK_STREAM)和AF_INET6(SOCK_STREAM)上受支持。通过在send()、sendto()和sendmsg()函数上指定MSG_OOB标志来发送OOB数据。
传送OOB数据与传送常规数据一样。它是在所有缓冲数据之后发送的。换句话说,OOB数据的优先级别没有可能缓冲的任何数据的优先级别高;数据是按其发送次序传送的。
在接收端,事情有一点复杂:
套接字API通过使用OOB标记程序了解在系统上接收到的OOB数据。OOB标记程序指向发送的OOB数据中的最后一个字节。(注意:指示OOB标记程序指向哪个字节的值是在系统基础上设置的。此值在TCP连接的本地和远程端必须一致。使用此值的套接字应用程序在使用它时必须在客户机和服务器应用程序之间保持一致。)
如果未设置套接字选项SO_OOBINLINE,且发送程序发送的OOB数据的大小超过1字节,则除最后一个字节之外的所有字节都被视作普通数据。(普通数据表示接收程序可接收数据而不指定MSG_OOB标志。)发送的OOB数据的最后一个字节未存储在普通数据流中。只能发出recv()、recvmsg()或recvfrom()函数(设置有MSG_OOB标志)来检索此字节。如果未设置MSG_OOB标志而发出接收,将检索普通数据,OOB字节将被删除。而且,如果发送多次出现的OOB数据,则先前出现的OOB数据将会丢失,仅记住最后一次OOB数据出现的OOB数据位置。
如果设置了套接字选项SO_OOBINLINE,则发送的所有OOB数据都存储在普通数据流中。可通过发出下列三个接收函数之一而不指定MSG_OOB标志(如果指定它的话,将返回错误[EINVAL])来检索数据。如果发送多次出现的OOB数据,OOB数据不会丢失。
如果未设置SO_OOBINLINE且已接收到OOB数据,则不会废弃OOB数据,用户会将SO_OOBINLINE设置为开。初始OOB字节被视作普通数据。
如果未设置SO_OOBINLINE且已发送OOB数据,同时接收程序发出了输入函数以接收OOB数据,则OOB标记程序仍然有效。接收程序仍然可以检查读指针是否在OOB标记程序上,即使接收到OOB字节。default = off
设置:
BOOL bOobinline = TRUE;
setsockopt( s, SOL_SOCKET, SO_ OOBINLINE, &bOobinline, sizeof(BOOL) );
SO_RCVBUF接收缓冲区大小
在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区。default = 87380
//接收缓冲区
int nRecvBuf = 32 * 1024; //设置为32K
setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( const char* )&nRecvBuf, sizeof( int ) );
SO_SNDBUF发送缓冲区大小
default = 16384
int nSendBuf = 32*1024; //设置为32K
setsockopt( s, SOL_SOCKET, SO_SNDBUF, ( const char* )&nSendBuf, sizeof( int ) );
每个套接口都有一个发送缓冲区和一个接收缓冲区。接收缓冲区被TCP和UDP用来将接收到的数据一直保存到由应用进程来读。TCP:TCP通告另一端的窗口大小。TCP套接口接收缓冲区不可能溢出,因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。UDP:当接收到的数据报装不进套接口接收缓冲区时,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
SO_RCVLOWAT接收缓冲区下限
SO_SNDLOWAT发送缓冲区下限
每个套接口都有一个接收低潮限度和一个发送低潮限度。它们是函数select使用的,接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据总量。对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写”而在套接口发送缓冲区中必须有的可用空间。对于TCP套接口,此值常缺省为2048。对于UDP使用低潮限度,由于其发送缓冲区中可用空间的字节数是从不变化的,只要UDP套接口发送缓冲区大小大于套接口的低潮限度,这样的UDP套接口就总是可写的。UDP没有发送缓冲区,只有发送缓冲区的大小。
SO_RCVTIMEO接收超时struct timeval
SO_SNDTIMEO发送超时struct timeval
使用这两个选项可以给套接口设置一个接收和发送超时。通过设置参数的值为0秒和0微秒来禁止超时。缺省时两个超时都是禁止的。
接收超时影响5个输入函数:read、readv、recv、recvfrom和recvmsg;发送超时影响5个输出函数:write、writev、send、sendto和sendmsg。
SO_REUSERADDR允许重用本地地址和端口
1. SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
2. SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
3. SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
4. SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。
SO_REUSEPORT选项有如下语义:
1.此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才性。
2.如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。
使用这两个套接口选项的建议:
1.在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
2.当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。
SO_EXCLUSIVEADDRUSE
独占模式使用端口,就是不充许和其它程序使用SO_REUSEADDR共享的使用某一端口。在确定多重绑定使用谁的时候,根据一条原则是谁的指定最明确则将包递交给谁,而且没有权限之分,也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患,如果不想让自己程序被监听,那么使用这个选项
SO_TYPE获得套接字类型
该选项返回套接口的类型,返回的整数值是一个诸如SOCK_STREAM或SOCK_DGRAM这样的值
SO_USELOOPBACK
该选项仅用于路由域(AF_ROUTE)的套接口,它对这些套接口的缺省设置为打开(这是唯一一个缺省为打开而不是关闭的SO_xxx套接口选项)。当此套接口打开时,套接口接收在其上发送的任何数据的一个拷贝。
SO_BSDCOMPAT与BSD系统兼容
Level(级别)
IPPROTO_IP
IP_HDRINCL在数据包中包含IP首部int
这个选项常用于黑客技术中,隐藏自己的IP地址
如果没有设置IP_HDRINCL选项时,包内可写的内容为数据部分,内核将自动创建IP首部。如果设置了IP_HDRINCL选项,则包内要填充的内容为IP数据包和首部。
IP_OPTINOS IP首部选项int
设置此选项允许我们在IPv4头部中设置IP选项。这要求掌握IP头部中IP选项的格式信息。
IP_RECVDSTADDR
该选项导致所接收到的UDP数据报的目的IP地址由函数recvmsg作为辅助数据返回。
IP_RECVIF
该选项导致所接收到的UDP数据报的接口索引由函数recvmsg作为辅助数据返回。
IP_TOS
该选项使我们可以给TCP或UDP套接口在IP头部中设置服务类型字段。如果我们给此选项调用getsockopt,则放到外出IP数据报头部的TOS字段中的当前值将返回(缺省为0)。还没有办法从接收到的IP数据报中取此值。可以将TOS设置为如下的值:
IPTOS_LOWDELAY:最小化延迟
IPTOS_THROUGHPUT:最大化吞吐量
IPTOS_RELIABILITY:最大化可靠性
IPTOS_LOWCOST:最小化成本
IP_TTL
用次选项,可以设置和获取系统用于某个给定套接口的缺省TTL值(存活时间字段)。与TOS一样,没有办法从接收到的数据报中得到此值。
ICMP6_FILTER
可获取和设置一个icmp6_filter结构,他指明256个可能的ICMPv6消息类型中哪一个传递给在原始套接口上的进程。
以下IPV4选项用于组播
IPv4选项 数据类型 描述
IP_ADD_MEMBERSHIP struct ip_mreq 加入到组播组中
IP_DROP_MEMBERSHIP struct ip_mreq 从组播组中退出
IP_MULTICAST_IF struct ip_mreq 指定提交组播报文的接口
IP_MULTICAST_TTL u_char 指定提交组播报文的TTL
IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效
在头文件中定义了ip_mreq结构:
struct ip_mreq {
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
若进程要加入到一个组播组中,用soket的setsockopt()函数发送该选项。该选项类型是ip_mreq结构,它的第一个字段imr_multiaddr指定了组播组的地址,第二个字段imr_interface指定了接口的IPv4地址。
IP_DROP_MEMBERSHIP
该选项用来从某个组播组中退出。数据结构ip_mreq的使用方法与上面相同。
IP_MULTICAST_IF
该选项可以修改网络接口,在结构ip_mreq中定义新的接口。
IP_MULTICAST_TTL
设置组播报文的数据包的TTL(生存时间)。默认值是1,表示数据包只能在本地的子网中传送。
IP_MULTICAST_LOOP
组播组中的成员自己也会收到它向本组发送的报文。这个选项用于选择是否激活这种状态。
Level(级别)
IPPROTO_IPV6
IPV6_ADDRFORM
允许套接口从IPv4转换到IPv6,反之亦可。
IPV6_CHECKSUM
指定用户数据中校验和所处位置的字节偏移。如果此值为非负,则内核将(1)给所有外出分组计算并存储校验和;(2)输入时检查所收到的分组的校验和,丢弃带有无效校验和的分组。此选项影响出ICMPv6原始套接口外的所有IPv6套接口。如果指定的值为-1(缺省值),内核在此原始套接口上将不给外出的分组计算并存储校验和,也不检查所收到的分组的校验和。
IPV6_DSTOPTS
设置此选项指明:任何接收到的IPv6目标选项都将由recvmsg作为辅助数据返回。此选项缺省为关闭。
IPV6_HOPLIMIT
设置此选项指明:接收到的跳限字段将由recvmsg作为辅助数据返回。
IPV6_HOPOPTS
设置此选项指明:任何接收到的步跳选项都将由recvmsg作为辅助数据返回。
IPV6_NEXTHOP
这不是一个套接口选项,而是一个可指定个sendmsg的辅助数据对象的类型。此对象以一个套接口地址结构指定某个数据报的下一跳地址。
IPV6_PKTINFO
设置此选项指明:下面关于接收到的IPv6数据报的两条信息将由recvmsg作为辅助数据返回:目的IPv6地址和到达接口索引。
IPV6_PKTOPTIONS
大多数IPv6套接口选项假设UDP套接口使用recvmsg和sendmsg所用的辅助数据在内核与应用进程间传递信息。TCP套接口使用IPV6_PKTOPTIONS来获取和存储这些值。
IPV6_RTHDR
设置此选项指明:接收到的IPv6路由头部将由recvmsg作为辅助数据返回。
IPV6_UNICAST_HOPS
类似于IPv4的IP_TTL,它的设置指定发送到套接口上的外出数据报的缺省跳限,而它的获取则返回内核将用于套接口的跳限值。为了从接收到的IPv6数据报中得到真实的跳限字段,要求使用IPV6_HOPLIMIT套接口选项。
IPPRO_TCP
TCP_MAXSEG TCP最大数据段的大小int
获取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给另一端的最大数据量,它常常就是由另一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比对方通告的MSS小些的值。如果此值在套接口连接之前取得,则返回值为未从另·—端收到Mss选项的情况下所用的缺省值。小于此返回值的信可能真正用在连接上,因为譬如说使用时间戳选项的话,它在每个分节上占用12字节的TCP选项容量。我们的TcP将发送的每个分节的最大数据量也可在连接存活期内改变,但前提是TCP要支持路径MTU发现功能。如果到对方的路径改变了,此值可上下调整。
TCP_NODELAY不使用Nagle算法int
指定TCP开始发送保持存活探测分节前以秒为单位的连接空闲时间。缺省值至少必须为7200秒,即2小时。此选项仅在SO_KEPALIVEE套接口选项打开时才有效。
TCP_NODELAY和TCP_CORK,
这两个选项都对网络连接的行为具有重要的作用。许多UNIX系统都实现了TCP_NODELAY选项,但是,TCP_CORK则是Linux系统所独有的而且相对较新;它首先在内核版本2.4上得以实现。此外,其他UNIX系统版本也有功能类似的选项,值得注意的是,在某种由BSD派生的系统上的TCP_NOPUSH选项其实就是TCP_CORK的一部分具体实现。
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在这里的含义是采用Nagle算法把较小的包组装为更大的帧。John Nagle是Nagle算法的发明人,后者就是用他的名字来命名的,他在1984年首次用这种方法来尝试解决福特汽车公司的网络拥塞问题(欲了解详情请参看IETF RFC 896)。他解决的问题就是所谓的silly window syndrome,中文称“愚蠢窗口症候群”,具体含义是,因为普遍终端应用程序每产生一次击键操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据载荷以及40个字节长的包头,于是产生4000%的过载,很轻易地就能令网络发生拥塞,。Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为缺省配置了,但在我们看来,有些场合下把这一选项关掉也是合乎需要的。
现在让我们假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。例如,当我们正在发送一个较短的请求并且等候较大的响应时,相关过载与传输的数据总量相比就会比较低,而且,如果请求立即发出那么响应时间也会快一些。以上操作可以通过设置套接字的TCP_NODELAY选项来完成,这样就禁用了Nagle算法。
另外一种情况则需要我们等到数据量达到最大时才通过网络一次发送全部数据,这种数据传输方式有益于大量数据的通信性能,典型的应用就是文件服务器。应用Nagle算法在这种情况下就会产生问题。但是,如果你正在发送大量数据,你可以设置TCP_CORK选项禁用Nagle化,其方式正好同TCP_NODELAY相反(TCP_CORK和TCP_NODELAY是互相排斥的)。下面就让我们仔细分析下其工作原理。
假设应用程序使用sendfile()函数来转移大量数据。应用协议通常要求发送某些信息来预先解释数据,这些信息其实就是报头内容。典型情况下报头很小,而且套接字上设置了TCP_NODELAY。有报头的包将被立即传输,在某些情况下(取决于内部的包计数器),因为这个包成功地被对方收到后需要请求对方确认。这样,大量数据的传输就会被推迟而且产生了不必要的网络流量交换。
但是,如果我们在套接字上设置了TCP_CORK(可以比喻为在管道上插入“塞子”)选项,具有报头的包就会填补大量的数据,所有的数据都根据大小自动地通过包传输出去。当数据传输完成时,最好取消TCP_CORK选项设置给连接“拔去塞子”以便任一部分的帧都能发送出去。这同“塞住”网络连接同等重要。
总而言之,如果你肯定能一起发送多个数据集合(例如HTTP响应的头和正文),那么我们建议你设置TCP_CORK选项,这样在这些数据之间不存在延迟。能极大地有益于WWW、FTP以及文件服务器的性能,同时也简化了你的工作。示例代码如下:
intfd, on = 1;
…
/*此处是创建套接字等操作,出于篇幅的考虑省略*/
…
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */
write (fd, …);
fprintf (fd, …);
sendfile (fd, …);
write (fd, …);
sendfile (fd, …);
…
on = 0;
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /*拔去塞子*/
不幸的是,许多常用的程序并没有考虑到以上问题。例如,Eric Allman编写的sendmail就没有对其套接字设置任何选项。
Apache HTTPD是因特网上最流行的Web服务器,它的所有套接字就都设置了TCP_NODELAY选项,而且其性能也深受大多数用户的满意。这是为什么呢?答案就在于实现的差别之上。由BSD衍生的TCP/IP协议栈(值得注意的是FreeBSD)在这种状况下的操作就不同。当在TCP_NODELAY模式下提交大量小数据块传输时,大量信息将按照一次write()函数调用发送一块数据的方式发送出去。然而,因为负责请求交付确认的记数器是面向字节而非面向包(在Linux上)的,所以引入延迟的概率就降低了很多。结果仅仅和全部数据的大小有关系。而Linux在第一包到达之后就要求确认,FreeBSD则在进行如此操作之前会等待好几百个包。
在Linux系统上,TCP_NODELAY的效果同习惯于BSD TCP/IP协议栈的开发者所期望的效果有很大不同,而且在Linux上的Apache性能表现也会更差些。其他在Linux上频繁采用TCP_NODELAY的应用程序也有同样的问题。
TCP_DEFER_ACCEPT
我们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其他一些操作系统上也有同样的选项但使用不同的名字)。为了理解TCP_DEFER_ACCEPT选项的具体思想,我们有必要大致阐述一下典型的HTTP客户/服务器交互过程。请回想下TCP是如何与传输数据的目标建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也可以携带数据负载。服务信息的典型例子就是一套所谓的标志,它把包标记代表TCP/IP协议栈内的特殊含义,例如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是完全可能的,但有时,内部逻辑迫使TCP/IP协议栈发出只有包头的IP包。这些包经常会引发讨厌的网络延迟而且还增加了系统的负载,结果导致网络性能在整体上降低。
现在服务器创建了一个套接字同时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志而且不带数据负载的TCP包(一个SYN包)。服务器则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确认收到了第2个包从而结束连接过程。在收到客户发来的这个SYN/ACK包之后,服务器会唤醒一个接收进程等待数据到达。当3次握手完成后,客户程序即开始把“有用的”的数据发送给服务器。通常,一个HTTP请求的量是很小的而且完全可以装到一个包里。但是,在以上的情况下,至少有4个包将用来进行双向传输,这样就增加了可观的延迟时间。此外,你还得注意到,在“有用的”数据被发送之前,接收方已经开始在等待信息了。
为了减轻这些问题所带来的影响,Linux(以及其他的一些操作系统)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设置在侦听套接字的服务器方,该选项命令内核不等待最后的ACK包而且在第1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,服务器就会等待客户程序发送含数据的IP包。现在,只需要在网络上传送3个包了,而且还显著降低了连接建立的延迟,对HTTP通信而言尤其如此。
这一选项在好些操作系统上都有相应的对等物。例如,在FreeBSD上,同样的行为可以用以下代码实现:
/*为明晰起见,此处略去无关代码*/
struct accept_filter_arg af = { "dataready", "" };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af));
这个特征在FreeBSD上叫做“接受过滤器”,而且具有多种用法。不过,在几乎所有的情况下其效果与TCP_DEFER_ACCEPT是一样的:服务器不等待最后的ACK包而仅仅等待携带数据负载的包。要了解该选项及其对高性能Web服务器的重要意义的更多信息请参考Apache文档上的有关内容。
就HTTP客户/服务器交互而言,有可能需要改变客户程序的行为。客户程序为什么要发送这种“无用的”ACK包呢?这是因为,TCP协议栈无法知道ACK包的状态。如果采用FTP而非HTTP,那么客户程序直到接收了FTP服务器提示的数据包之后才发送数据。在这种情况下,延迟的ACK将导致客户/服务器交互出现延迟。为了确定ACK是否必要,客户程序必须知道应用程序协议及其当前状态。这样,修改客户行为就成为必要了。
对Linux客户程序来说,我们还可以采用另一个选项,它也被叫做TCP_DEFER_ACCEPT。我们知道,套接字分成两种类型,侦听套接字和连接套接字,所以它们也各自具有相应的TCP选项集合。因此,经常同时采用的这两类选项却具有同样的名字也是完全可能的。在连接套接字上设置该选项以后,客户在收到一个SYN/ACK包之后就不再发送ACK包,而是等待用户程序的下一个发送数据请求;因此,服务器发送的包也就相应减少了。
TCP_QUICKACK
阻止因发送无用包而引发延迟的另一个方法是使用TCP_QUICKACK选项。这一选项与TCP_DEFER_ACCEPT不同,它不但能用作管理连接建立过程而且在正常数据传输过程期间也可以使用。另外,它能在客户/服务器连接的任何一方设置。如果知道数据不久即将发送,那么推迟ACK包的发送就会派上用场,而且最好在那个携带数据的数据包上设置ACK标志以便把网络负载减到最小。当发送方肯定数据将被立即发送(多个包)时,TCP_QUICKACK选项可以设置为0。对处于“连接”状态下的套接字该选项的缺省值是1,首次使用以后内核将把该选项立即复位为1(这是个一次性的选项)。
在某些情形下,发出ACK包则非常有用。ACK包将确认数据块的接收,而且,当下一块被处理时不至于引入延迟。这种数据传输模式对交互过程是相当典型的,因为此类情况下用户的输入时刻无法预测。在Linux系统上这就是缺省的套接字行为。
在上述情况下,客户程序在向服务器发送HTTP请求,而预先就知道请求包很短所以在连接建立之后就应该立即发送,这可谓HTTP的典型工作方式。既然没有必要发送一个纯粹的ACK包,所以设置TCP_QUICKACK为0以提高性能是完全可能的。在服务器方,这两种选项都只能在侦听套接字上设置一次。所有的套接字,也就是被接受呼叫间接创建的套接字则会继承原有套接字的所有选项。
通过TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK选项的组合,参与每一HTTP交互的数据包数量将被降低到最小的可接受水平(根据TCP协议的要求和安全方面的考虑)。结果不仅是获得更快的数据传输和请求处理速度而且还使客户/服务器双向延迟实现了最小化。
返回说明
成功0,失败-1并设置errno值