socket选项

socket选项
 
 

1、设置/获取socket选项

有两个函数,可以用来对socket进行设置或获取当前设置:

#include <sys/socket.h>


int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
 
int setsockopt(int sockfd, int level, int optname, const void *optval socklen_t optlen);

level指定了系统应该如何“解释”设置项,或这可以说是设置项的“级别”:socket或者是特定的协议。在ipv4的网络中,常用到的level是SOL_SOCKET、IPPROTO_IP和IPPROTO_TCP。

2、常用的socket选项

2.1 SOL_SOCKET级

·SO_ERROR

当一个socket发生错误的时候,将使用一个名为so_error的变量记录对应的错误代码,这又叫做pending error,so_error为0时表示没有错误发生。一般来说,有2种方式通知进程有socket错误发生:

1、进程阻塞在select中,有错误发生时,select将返回,并将发生错误的socket标记为可读写;

2、如果进程使用信号驱动的I/O,将会有一个SIGIO产生并发往对应进程;

此时,进程可以通过SO_ERROR取得具体的错误代码。getsockopt返回后,*optval指向的区域将存储错误代码,而so_error被设置为0。

当so_error不为0时,如果进程对socket进行read操作,若此时接收缓存中没有数据可读,则read返回-1,且errno设置为so_error,so_error置为0,否则将返回缓存中的数据而不是返回错误;如果进行write操作,将返回-1,errno置为so_error,so_error清0。

注意,这是一个只可以获取,不可以设置的选项。

·SO_KEEPALIVE

当这个设置被打开时,如果一个处于连接状态的TCP socket在一定时间(默认值一般为2小时)内没有数据传输,那么此socket将自动的发送一个“心跳包”检测连接是否正常。发送心跳包之后,有3中可能的结果:

1、主机收到远程主机的ACK包,此时代表连接还处于正常状态,系统过一定时间后会再发心跳包。这整个过程中使用此socket的进程没有任何反映;

2、主机收到远程主机的RST包,此时表示远程主机出了异常(如崩溃后重启),连接被重置了。这时本机的so_error被设置为ECONNRESET,且socket被关闭。

3、主机没有收到远程主机发送的任何数据包。这时有2种可能,一是发生丢包事件,二是远程主机不可达。本机会每隔一段时间(一般是75秒)重发心跳包,如果重发一定次数后(一般8次)依然没有相应,则so_error被设置为ETIMEOUT;如果重发期间收到“主机不可达”的ICMP包,则将so_error设置为EHOSTUNREACH。socket被关闭。

可以看到,只要发生了错误,socket就会被关闭,所以这个选项称之为“make dead”更为恰当。

使用这个选项的一般是服务器,因为它要处理很多客户端连接,如果某些客户端异常导致连接“断开”,则需要发现这些连接并关闭,以节省资源。

另外,心跳时间(一般2小时)影响的范围是整个系统,因此如果修改了内核代码相应的值(需重新编译内核),则会影响到系统中的所有socket。

一些服务器,如FTP,使用的是应用程序级的心跳,而非SO_KEEPALIVE。这种方式能够灵活的控制心跳时间和重发时间。

附:检测TCP连接各种状态的方法。

(...未完...)

·SO_LINGER

在对面向连接的socket调用close时,这个选项将影响close的处理方式。SO_LINGER的*optval指向的是linger结构体:

struct linger {
  int l_onoff; /* 0=off, nonzero=on */
  int l_linger; /* linger time, POSIX specifies units as seconds */
};

按linger结构体的值的不同,有3种情况:

1、l_onoff为0,无论l_linger是否为0,表示此选项被关闭。close直接返回,如果此时发送缓存中还存在数据没有发送出去,系统在底层将试图将这些数据发送出去。

2、l_onoff不为0,l_linger为0。此时,调用close将不会执行关闭TCP连接的“4部曲”,而是直接向远程主机发送一个RST包。这样做可以避免socket进入TIME_WAIT状态。

3、l_onoff不为0,l_linger不为0。如果对socket进行close操作时,发送缓存中还有数据尚未发送,则进程会进入睡眠直到以下2个条件其中一个发生:(i)缓存中所有数据被发送出去,且收到远程主机的ACK;(ii)超时(时间由l_linger指定)。注意,如果socket是nonblocking模式,则不会发生睡眠。如果使用了SO_LINGER选项,应当检查close的返回值,因为如果close因为超时而返回,则返回值为EWOULDBLOCK,并且发送缓存中没有发送出去的数据将被丢弃。

由于网络延迟的存在,有时使用SO_LINGER并不能让进程确定远程主机是否已经收到“最后”的数据。例如,l_linger设置成一个较小的值,网络延迟较大,可能远程主机的ACK在close超时超时后到达,close会返回EWOULDBLOCK,但实际上远程主机已经收到数据。

 

·SO_RCVBUF和SO_SNDBUF

每个socket都有自己的接收/发送缓存。当数据到达,而进程未来得及处理的时候,将被缓存起来。对于TCP来说,接收缓存影响滑动窗口的大小,系统会根据缓存的情况进行流量控制。

利用SO_RCVBUF和SO_SNDBUF这两个选项,我们可以修改缓存的默认大小。在老式的实现中,TCP的接收/发送缓存的默认值是4096,在新的系统中使用了更大的值,一般介于8192到61440;UDP的发送缓存一般是9000左右,接收缓存一般是40000左右。

对TCP的缓存进行设置的时候,需要注意顺序问题。TCP的窗口大小初始值附加在连接建立时发送的SYN包中,因此,对于客户端,设置应当发生在调用connect前,而对于服务器,应当在listen之前(由accept返回的socket继承listening socket的设置)。

TCP socket缓存的大小应至少有4个MSS大。这里所说的TCP socket缓存,指的是发送方的发送缓存和接收方的接收缓存。

为了避免缓存空间的浪费,socket的缓存大小应该为MSS的偶数倍。在一些系统中,底层会自动进行圆整,比如若Ethernet的MSS为1460,默认缓存大小为8192,系统会自动圆整为8760(1460*6)。

缓存的大小直接影响socket的效率,大了浪费空间,小了则链路的带宽得不到有效的利用。一般来说,缓存大小至少设置为链路的容量。链路的容量就是带宽时延积,即同一时间内链路上能够容纳的比特数。如果小于这个值,则链路则不是“满”的。

·SO_RCVLOWAT和SO_SNDLOWAT

这两个值是socket的接收/发送阀值,在讨论select时已经讨论过。这两个值的设置在一定程度上也会影响效率。

·SO_RCVTIMEO和SO_SNDTIMEO

这两项分别设置socket的接收和发送超时时间,它们都接收一个timeval结构作为参数,当timeval结构为0时,表示选项无效。接收超时影响read、readv、recv、recvfrom和recvmsg;发送超时影响write、writev、send、sendto和sendmsg。

·SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR选项有4中不同的用途:

1、让服务器绑定某个已经存在连接的端口,并进行监听。存在连接指有远程主机连接到此端口。

2、允许启动一个新的服务器,并绑定到某个端口,该端口已经被某个服务器与wildcard address绑定。比如,某台主机有3个地址192.168.1.1、192.168.1.2和192.168.1.3,服务器A使用wildcard address与2222端口绑定,则若使用了SO_REUSEADDR,服务器B还可以使用192.168.1.1与2222端口进行绑定。当有客户端进行连接的时候,会进行“最详细”匹配原则,即客户端指定192.168.1.1:2222,则会连接到B,否则连接到A。

3、4不常用,暂不介绍。

SO_REUSEPORT在某些系统不支持,建议用SO_REUSEADDR代替。

3、fcntl函数

使用fcntl函数,可以对一个文件描述符进行很多设置,它的原型如下:

#include <fcntl.h>
 
int fcntl(intfd, int cmd, ... /* int arg */ );
 
Returns: depends on cmd if OK, -1 on error

可供选择的cmd和arg很多,一般来说,网络编程常用的是cmd是F_GETFL,对应的arg有2个:

·O_NOBLOCK 设置为非阻塞I/O

·O_ASYNC 设置为信号驱动I/O

只得一提的是,信号驱动I/O产生的SIGIO和SIGURG信号不同于其它信号,它只有在socket有owner(即信号应该发往的进程)时才会产生。因此,如果要使用信号驱动I/O,还应使用F_SETOWN设置socket的所有者,arg表示pid(大于0)或gid(小于0)。另外,由socket函数创建的socket没有所有者,由accept产生的socket继承listening socket的所有者。

在使用fcntl进行设置的时候,正确的步骤应该是(以非阻塞I/O为例):

if ( (flags = fcntl (fd, F_GETFL, 0)) < 0)
    err_sys("F_GETFL error");
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
    err_sys("F_SETFL error");

即先获取当前flag,然后在当前设置的基础上进行改动。如果直接使用O_NOBLOCK设置,则会将原来其他非O_NOBLOCK标志清空。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值