Zach的博客

套接字选项

修改套接字选项

有几种方法来修改和获取套接字的选项:

  • getsockopt & setsockopt
  • fcntl
  • ioctl

getsockopt & setsockopt

这两个函数仅仅用于套接字。函数原型如下:

1
2
3
4
5
#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);

sockfd必须指向一个已经打开的套接字,level指向系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(如TCP、IPv4、IPv6或SCTP)。

optval指向某个变量,变量的大小由最后一个参数optlen指定,setsockopt从optval中取得待设置的新值,而getsockopt把获取到的值存入对应的地址中。

套接字选项粗分为两大基本类型:

  • 启用或禁止某个特性的标志选项(flag = 0禁止特性,flag = 1开启特性)
  • 取得并返回我们可以设置或检查的特定值选项,即值选项。

套接字选项可以参见UNP的P151

通用套接字选项(部分)

这里仅仅列出部分的通用套接字选项,其实也就是挑了一些自己看得懂的选项记录下。

SO_BROADCAST

开启或禁止进程发送广播消息的能力。只有数据报支持广播,并且还是在支持广播消息的网络上(以太网、令牌网络等)。

SO_DEBUG

仅由TCP支持,当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接收的所有分组保留详细的跟踪信息,这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查。

SO_ERROR

当一个套接字上发生错误时,内核将套接字的名为so_error的变量设置为标准的Unix Exxx值中的一个,它被成为该套接字的待处理错误(pending error),内核以以下方式通知进程这个错误:

  1. 如果进程阻塞在套接字的select调用上,那么无论是检查可读条件还是可读可写条件,select均返回并设置其中一个或所有的条件。
  2. 如果进程使用信号驱动I/O模型,那么进程或进程组就会接收到内核产生的SIGIO信息。

进程在被内核通知之后,可以通过访问SO_ERROR套接字选项获得so_error的值,由getsockopt返回的整数即待处理错误,随后so_error由内核复位成0。

这个套接字选项可以获取,但是不能设置。

SO_KEEPALIVE

给一个套接字设置保持存活(keep-alive)选项后,如果2小时内在该套接字的任意一个方向上没有数据交换,那么TCP就会自动给对端发送一个保持存活的探测分节(keep-alive probe),这是一个对端必须响应的TCP分节,它
会导致以下三种情况之一:

  1. 对端以期望的ACK响应,一切正常
  2. 对端以RST响应,对端已经崩溃并重启,套接字的待处理错误被置为ECONNRESET,套接字本身被关闭。
  3. 对端对保持存活的探测分节没有响应,TCP间隔一段时间再次发送探测分节,多次之后若还是没有响应,则放弃套接字的待处理错误被置为ETIMEOUT,如果收到一个ICMP错误作为某个探测分节的响应,就返回相应的错误,套接字本身被关闭。

SO_LINGER

指定close函数对面向连接的协议(TCP和SCTP)如何操作。默认的行为是立即返回,如果这时候发送缓冲区有数据残留,系统会尝试把数据发送给对方。选项要求用户进程和内核间传递如下结构:

1
2
3
4
struct linger {
int l_onoff;
int l_linger;
}
  1. 如果l_onoff = 0,l_linger被忽略,选项关闭
  2. l_onoff非0,
    • l_linger = 0,那么TCP丢弃发送缓冲区内的任何数据,并发送一个RST给对端,不会再有四次挥手。
    • l_linger不为0,进程被阻塞,知道所有发送缓冲区的数据都发送完毕并被对方确认,或者是超过了l_linger指定的时间,如果超时,close返回EWOULDBLOCK错误。

假设客户在发送玩数据后调用了close函数,close可能在服务器读套接字接收缓冲区中的剩余数据之前就返回,在应用程序读数据之前服务器可能就会崩溃,而客户进程永远不会知道。
如果设置了SO_LINGER套接字,那么在调用close函数后,应用进程阻塞一段时间,等待数据全部发送并被对方确认(如图所示)。但是这里还有一个问题,延滞的时间可能不够,close仍然会返回EWOULDBLOCK错误,而且close的成功返回只是告诉我们先前发送的数据已经由对方确认,并不能告诉我们应用进程是否读取了数据。

so_linger.png

让客户进程知道服务器已经读取数据的一个方法是改用shutdown(设置SHUT_WR),改用之后的流程如下图:

shutdown_linger.png

另外一个方法是应用程序自己做确认。客户在向服务器发送完数据后,调用read来读取一个字节

1
2
3
4
char ack;

Write(sockfd, data, nbytes);
n = read(sockfd, &ack, 1);

服务器读取来自客户端的数据后发回一个字节的ACK。

1
2
3
nbytes = Read(sockfd, buff, sizeof(buff));

Write(sockfd, "", 1);

SO_RCVBUF & SO_SNDBUF

每一个套接字有一个发送和接收缓冲区,可以利用SO_RCVBUF和SO_SNDBUF选项来修改默认的缓冲区大小。需要注意的是,由于TCP的窗口规模是在建立连接时用SYN分节与对端呼唤a的都的。对于客户端,意味着
SO_RCVBUF必须在connect调用之前设置;对于服务端,该选项必须在调用Listen之前设置。

SO_RCVLOWAT & SO_SNDLOWAT

每一个套接字还有一个接收和发送低水位标志,由select使用,用SO_RCVLOWAT和SO_SNDLOWAT可以修改低水位标志的值。

接收低水位标志是让select返回可读时套接字接收缓冲区所需要的最少的数据量。
发送低水位标志是让select返回可写是套接字发送缓冲区可存入的最少数据量。

两个低水位的默认值都为1

SO_RCVTIMEO & SO_SNDTIMEO

这两个选项允许我们给套接字的接收和发送设置一个超时值。