【UNIX网络编程】| 【05】套接字选项(详)

文章目录

1、概述

相关函数:
	- getsockopt、setsockopt;
	- fcntl;
	- ioctl;

2、getsockopt和setsockopt

#include <sys/sock.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);
/**
@param sockfd: 必须为一个打开的套接字描述符;
@param level: 协议族;
@param optval:指向某个变量的指针,
	setsockopt从*optval中选取待设置的新值;
	getsockopt把已获取的选项当前值存到该指针中;
@param optlen:optval的大小,在getsockopt是一个值-结果参数,在setsockopt为值参数;
*/
2.1 选项基本类型
- 启用或禁止某个特性的二元选项;
- 取得并返回我们可用设置或检查的特定值的选项;

在这里插入图片描述

在这里插入图片描述

3、检查选项是否受支持并获取默认值

#include "../Jxiepc/unp.h"
#include <netinet/tcp.h>

/**
 * val为getsockopt的每个可能的返回值;
 * */
union val {
    int i_val;
    long l_val;
    struct linger linger_val;
    struct timeval timeval_val;
};

static char strres[128];

/**
 * 函数原型
 * */
static char *sock_str_flag(val *ptr, int len) {
    if(len != sizeof(int))
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    else
        snprintf(strres, sizeof(strres),
                 "%s", (ptr->i_val == 0) ? "off" : "on");

    return strres;
}

static char *sock_str_int(val *ptr, int len) {
    if(len != sizeof(int))
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    else
        snprintf(strres, sizeof(strres),
                 "%d", ptr->i_val);
    return strres;
}
static char *sock_str_linger(val *ptr, int len) {
    struct linger *lptr = &ptr->linger_val;
    if(len != sizeof(struct linger))
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct linger)", len);
    else
        snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d",
                lptr->l_onoff, lptr->l_linger);
    return strres;
}

static char *sock_str_timeval(val *ptr, int len) {
    struct timeval *tvptr = &ptr->timeval_val;
    if (len != sizeof(struct timeval))
        snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct timeval)", len);
    else
        snprintf(strres, sizeof(strres), "%d sec, %d usec"
                , tvptr->tv_sec, tvptr->tv_usec);
    return strres;
}
/**
 * 该结构题包含每个套接字选项调用了getsockopt并输出其当前值所要的信息
 * */
struct sock_opts{
    const char *opt_str;
    int opt_level;
    int opt_name;
    char *(*opt_val_str)(val, int);
};

sock_opts sock_opts_list[] = {
                { "SO_BROADCAST",		SOL_SOCKET,	SO_BROADCAST,	sock_str_flag },
                { "SO_DEBUG",			SOL_SOCKET,	SO_DEBUG,		sock_str_flag },
                { "SO_DONTROUTE",		SOL_SOCKET,	SO_DONTROUTE,	sock_str_flag },
                { "SO_ERROR",			SOL_SOCKET,	SO_ERROR,		sock_str_int },
                { "SO_KEEPALIVE",		SOL_SOCKET,	SO_KEEPALIVE,	sock_str_flag },
                { "SO_LINGER",			SOL_SOCKET,	SO_LINGER,		sock_str_linger },
                { "SO_OOBINLINE",		SOL_SOCKET,	SO_OOBINLINE,	sock_str_flag },
                { "SO_RCVBUF",			SOL_SOCKET,	SO_RCVBUF,		sock_str_int },
                { "SO_SNDBUF",			SOL_SOCKET,	SO_SNDBUF,		sock_str_int },
                { "SO_RCVLOWAT",		SOL_SOCKET,	SO_RCVLOWAT,	sock_str_int },
                { "SO_SNDLOWAT",		SOL_SOCKET,	SO_SNDLOWAT,	sock_str_int },
                { "SO_RCVTIMEO",		SOL_SOCKET,	SO_RCVTIMEO,	sock_str_timeval },
                { "SO_SNDTIMEO",		SOL_SOCKET,	SO_SNDTIMEO,	sock_str_timeval },
                { "SO_REUSEADDR",		SOL_SOCKET,	SO_REUSEADDR,	sock_str_flag },
#ifdef	SO_REUSEPORT
                { "SO_REUSEPORT",		SOL_SOCKET,	SO_REUSEPORT,	sock_str_flag },
#else
                { "SO_REUSEPORT",		0,			0,				NULL },
#endif
                { "SO_TYPE",			SOL_SOCKET,	SO_TYPE,		sock_str_int },
                { "SO_USELOOPBACK",		SOL_SOCKET,	SO_USELOOPBACK,	sock_str_flag },
                { "IP_TOS",				IPPROTO_IP,	IP_TOS,			sock_str_int },
                { "IP_TTL",				IPPROTO_IP,	IP_TTL,			sock_str_int },
#ifdef	IPV6_DONTFRAG
                { "IPV6_DONTFRAG",		IPPROTO_IPV6,IPV6_DONTFRAG,	sock_str_flag },
#else
                { "IPV6_DONTFRAG",		0,			0,				NULL },
#endif
#ifdef	IPV6_UNICAST_HOPS
                { "IPV6_UNICAST_HOPS",	IPPROTO_IPV6,IPV6_UNICAST_HOPS,sock_str_int },
#else
                { "IPV6_UNICAST_HOPS",	0,			0,				NULL },
#endif
#ifdef	IPV6_V6ONLY
                { "IPV6_V6ONLY",		IPPROTO_IPV6,IPV6_V6ONLY,	sock_str_flag },
#else
                { "IPV6_V6ONLY",		0,			0,				NULL },
#endif
                { "TCP_MAXSEG",			IPPROTO_TCP,TCP_MAXSEG,		sock_str_int },
                { "TCP_NODELAY",		IPPROTO_TCP,TCP_NODELAY,	sock_str_flag },
#ifdef	SCTP_AUTOCLOSE
                { "SCTP_AUTOCLOSE",		IPPROTO_SCTP,SCTP_AUTOCLOSE,sock_str_int },
#else
                { "SCTP_AUTOCLOSE",		0,			0,				NULL },
#endif
#ifdef	SCTP_MAXBURST
                { "SCTP_MAXBURST",		IPPROTO_SCTP,SCTP_MAXBURST,	sock_str_int },
#else
                { "SCTP_MAXBURST",		0,			0,				NULL },
#endif
#ifdef	SCTP_MAXSEG
                { "SCTP_MAXSEG",		IPPROTO_SCTP,SCTP_MAXSEG,	sock_str_int },
#else
                { "SCTP_MAXSEG",		0,			0,				NULL },
#endif
#ifdef	SCTP_NODELAY
                { "SCTP_NODELAY",		IPPROTO_SCTP,SCTP_NODELAY,	sock_str_flag },
#else
                { "SCTP_NODELAY",		0,			0,				NULL },
#endif
                { NULL,					0,			0,				NULL }
};

int main() {
    int fd;
    socklen_t len;
    struct sock_opts *ptr;

    /**
     * 遍历所有的元素,若为空,则实现没有定义该选项
     * */
    for(ptr = sock_opts_list; ptr->opt_str != nullptr; ptr++) {
        cout << ptr->opt_str << ": ";
        if(ptr->opt_val_str == nullptr)
            cout << "underfined" << endl;
        else {
            /**
             * 创建套接字
             * */
            switch (ptr->opt_level) {
                case SOL_SOCKET:
                case IPPROTO_TP:
                case IPPROTO_TCP:
                    fd = socket(AF_INET, SOCK_STREAM, 0);
                    break;
#ifdef IPV6
                case IPPROTO_IPV6:
                    fd = socket(AF_INET6, SOCK_STREAM, 0);
                    break;
#endif
#ifdef IPPROTO_SCTP
                case IPPROTO_SCTP:
                    fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
                    break;
#endif
                default:
                    err_quit("Can not create fd for level %d", ptr->opt_level);
            }

            /**
             * 调用getsockopt
             * */
            len = sizeof(val);
            if(getsockopt(fd, ptr->opt_level, ptr->opt_name, &val, &len) == -1) {
                err_ret("getsockopt error");
            }else {
                cout << "default = " << (*ptr->opt_val_str)(&val, len);
            }
            close(fd);
        }
    }
    return 0;
}

4、套接字状态

TCP中重要的套接字选项:SO_DEBUG、SO_DONTROUTSE、SO_KEEPALIVE、SO_LINGER、SO_O0BINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEC和TCP_NODELAY;
若想在三路握手完成时确保这些套接字选项中某一个是已连接套接字设置的,则必须先给监听套接字设置选项;

5、通用套接字选项

5.1 SO_BROADCAST
开启或禁止进程发送广播消息的能力;
	- 只有数据报套接字支持广播(支持广播消息的网络上);不可能在点对点链路上、基于连接的传输协议之上进行广播;
由于应用进程在发送广播数据报之前必须设置本套接字选项,它能够有效地防止一个进程在其应用程序根本没有设计成可广播时就发送广播数据报;
	若一个UDP应用程序可能以命令行参数取得目的IP地址,不过它并不期望用户键入一个广播地址;
	- 处理方法并非让应用进程来确定一个给定地址是否为广播地址,而是在内核中进行测试;
	如果该目的地址是一个广播地址且本套接字选项没有设置,那么返回EACCES错误;
5.2 SO_DEBUG
本选项仅由TCP支持;
	- 当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接收的所有分组保留详细跟踪信息;
	- 这些信息保存在内核的某个环形缓冲区中,并可使用trpt程序进行检查;
5.3 SO_DONTROUTE
本选项规定外出的分组将绕过底层协议的正常路由机制;
	- 若,再IPv4情况下外出分组将被定向到适当的本地接口,即由其目的地址的网络和子网部分确定的本地接口,若该本地接口无法由目的地址确定,则返回ENETUNREACH错误;
5.4 SO_ERROR
当一个套接字上发生错误时的待处理错误,内核能够以下面两种方式之一立即通知进程这个错误,该套接字不能被设置;
	- 若进程阻塞在对该套接字的select调用上,无论是检查可读还是可写条件,select均返回并设置其中一个或所有两个条件;
	- 若进程使用信号驱动式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随后被复位为0;
5.5 SO_KEEPALIVE
设置该选项后,若2小时内在该套接字的任一方向上都没有数据交换,TCP就自动给对端发送一个保持存活探测分节,这是一个对端必须响应的TCP分节,它会导致以下三种情况之一:
	- 对端以期望的ACK响应,应用进程得不到通知(一切正常)。在又经过仍无动静的2小时后,TCP将发出另一个探测分节;
	- 对端以RST响应,它告知本端TCP:对端已崩溃且已重新启动。该套接字的待处理错误被置为ECONNRESET,套接字本身则被关闭;
	- 对端对保持存活探测分节没有任何响应。源自Berkeley的TCP将另外发送8个探测分节,两两相隔75秒,试图得到一个响应。TCP在发出第一个探测分节后11分15秒内若没有得到任何响应则放弃;
5.6 SO_LINGER
本选项指定close函数对面向连接的协议如何操作(默认下是close立即返回,但若由数据残留则试图将其发送);

在这里插入图片描述

#include <sys/socket.h>

struct linger {
	int l_onoff;	// 指套接字是否应在指定时间内保持打开状态
	int l_linger;	// 以秒为单位的逗留时间,函数调用后保持打开多长时间以 启用排队数据发送
};
本选项要求使用该结构再用户进程域内核间传递。将由3中情形:
- 若l_oroff为0,那么关闭本选项,l_linger的值被忽略,即close立即返回;
- 若l_onoff为非0值且1_linger为0,那么当close某个连接时TCP将中止该连接;
- 若l_onoff为非0值且l_linger也为非0值,则当套接字关闭时内核将拖延一段时间;

套接字上close的返回时机
在这里插入图片描述

假设在客户数据到达时,服务器暂时处于忙状态,那么数据由TCP加入到服务器的套接字接收缓冲区中;
	类似地,下一个分节即客户的FIN也加入该套接字接收缓冲区中,默认情况下客户的close立即返回;
如图所示,客户的close可能在服务器读套接字接收缓区中的剩余数据之前就返回,对于服务器主机来说,在服务器应用进程读这些剩余数据之前就崩溃是完全可能的,而且客户应用进程永远不会知道;

客户可设置正的延滞时间
在这里插入图片描述

该情况下客户的close要到它的数据和服务器主机的TCP确认后才返回;

给SO_LINGER设置偏低的延滞时间值可能出现的现象
在这里插入图片描述

设置SO_LINGER后,close的成功返回指是告诉我们先前发送的数据和FIN已由对端TCP确认,而不能告诉我们对端应该程序是否已读取数据,故需要设置;
- 让客户知道服务器已读取其数据的可调用shutdown而不是close,并等待对端close连接的服务器端;

在这里插入图片描述
关闭客户端时,根据所调用的函数以及是否设置了套接字选项,可在以下3个不同时机返回

-  close立即返回,根本不等待(默认状况,图7-7);
-  close一直拖延到接收了对于客户端FIN的ACK才返回(图7-8);
- 后跟一个read调用的shutdown一直等到接收了对端的FIN才返回(图7-10);

获知对端应用程序已读取我们的数据的另一个方法是使用应用级确认

char ack;
n = read(sockfd, data, nbytes);
nbytes = read(sockfd, buff, sizeof(buff));
write(sockfd, "", 1);
当客户的read返回时,我们可保证服务器进程已读完了我们所发送的所有数据;

在这里插入图片描述
shutdown和close汇总
在这里插入图片描述

5.7 SO_OOBINLINE
当本选项开启时,带外数据将被留在正常的输入队列中(即在线留存);
	- 这种情况下接收函数的MSG_OOB标志不能用来读带外数据;
5.8 SO_RCVBUF和SO_SNDBUF
每个套接字都有接收、发送缓冲区;
接收缓冲区被TCP、UDP和SCTP用来保存接收到的数据,直到由应用进程来读取;
	- 对于TCP来说,套接字接收缓冲区中可用空间的大小限定了TCP通告对端的窗口大小;TCP套接字接收缓冲区不可能溢出,因为不允许对端发出超过本端所
	通告窗口大小的数据,这就是TCP的流量控制;
	- 如果对端无视窗口大小而发出了超过该窗口大小的数据,本端TCP将丢弃它们,然而对于UDP来说,当接收到的数据报装不进套接字接收缓冲区时,该数据
	报就被丢弃;
这两个套接字选项允许我们改变这两个缓冲区的默认大小;
	- 早期的源自Berkeley的实现将TCP发送和接收缓冲区的大小均默认为4096字节;
	- 较新的系统使用较大的值,可以是8192~61 440字节间的任何值;
	- 如果主机支持NFS,那么UDP发送缓冲区的大小经常默认为9000字节左右的一个值,而UDP接收缓冲区的大小则经常默认为40 000字节左右的一个值;
当设置TCP套接字接收缓冲区的大小时,函数调用的顺序很重要;由于TCP的窗口规模选项是在建立连接时用SYN分节与对端互换得到的;
	- 对于客户,这意味着SO_RCVBUF选项必须在调用connect之前设置;
	- 对于服务器,这意味着该选项必须在调用listen之前给监听套接字设置,给已连接套接字设置该选项对于可能存在的窗口规模选项没有任何影响,因为accept
	直到TCP的三路握手完成才会创建并返回已连接套接字。这就是必须给监听套接字设置本选项的原因;
TCP套接字缓冲区的大小至少应该是相应连接的MSS值的四倍;
	- 对于单向数据传输,“套接字缓冲区大小”时,即发送端主机上的套接字发送缓冲区大小和接收端主机上的套接字接收缓冲区大小;
	- 对于双向数据传输,我们在发送端指的是收发两个套接字缓冲区的大小,在接收端也是指收发两个套接字缓冲区的大小。典型的缓冲区大小默认值是8192字
	节或更大,典型的MSS值为512或1460;

如何避免缓冲区空间浪费

TCP套接字缓冲区大小还必须是相应连接的MSS值的偶数倍。有些实现替应用进程处理这个细节问题,在连接建立后向上舍入套接字缓冲区大小;
	- 这是在建立连接之前设置这两个套接字选项的另外一个原因;

设置缓冲区大小时需要考虑性能问题

理解的重点在于全双工管道的概念、它的容量以及它们如何关系到连接两端的套接字缓冲区大小;
- 管道的容量称为带宽-延迟积,它通过将带宽(bit/s)和RTT(秒)相乘,再将结果由位转换为字节计算得到,其中RTT可以很容易地使用ping程序测得;
- 带宽是相应于两个端点之间最慢链路的值,某种程度上是已知的;
举例来说,RTT为60ms的一条T1链路(1536 000 bit's)的带宽-延迟积为11 520字节。如果套接字缓冲区大小小于该值,管道将不会处于满状态,性能也将低于期
望值。当带宽变大或RTT变大时,套接字缓冲区也需要增长。当带宽-延迟积超过TCP的最大正常窗口大小(65535字节)时,两端就得设置我们在2.6节提到过的
TCP长胖管道选项。
5.9 SO_RCVLOWAT和SO_DLOWAT
每个套接字还有一个接收低水位标记和一个发送低水位标记;由select函数使用,这两选项允许修改低水位标记;
	- 接收低水位标记是让select返回“可读”时套接字接收缓冲区中所需的数据量,对于TCP、UDP和SCTP套接字,其默认值为1;
	- 发送低水位标记是让select返回“可写”时套接字发送缓冲区中所需的可用空间,对于TCP套接字,其默认值通常为2048;
UDP也使用发送低水位标记,然而由于UDP套接字的发送缓冲区中可用空间的字节数从不改变;只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位
标记,该UDP套接字就总是可写。UDP只有发送缓冲区大小这个属性;
5.10 SO_RCVTIMEO和SO_SNDTIMEO
这两个选项允许我们给套接字的接收和发送设置一个超时值;
【注意】访问它们的getsockopt和setsockopt函数的参数是指向timeval结构的指针,与select所用参数相同。这可让我们用秒数和微秒数来规定超时。我们通过
设置其值为0s和Ous禁止。默认情况下这两个超时都是禁止的;

接收超时影响5个输入函数: read、readv、recv、recvfrom和recvmsg;
发送超时影响5个输出函数: write、writev、send、sendto和sendmsg;
5.11 SO_REUSEADDR和SO_REUSEPORT
SO_REUSEADDR套接字选项能起到以下4个不同的功用。
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将该端口用作它们的本地端口的连接仍存在:
	- 启动一个监听服务器;
	- 连接请求到达,派生一个子进程来处理这个客户;
	- 监听服务器终止,但子进程继续为现有连接上的客户提供服务;
	- 重启监听服务器;
	默认情况下,当监听服务器在通过调用socket、bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端
	口,从而bind调用会失败;若该服务器在socket和bind两个调用之间设置了SO_REUSE2ADDR,那么bind将成功。所有TCP服务器都应该指定本套接字选
	项,以允许服务器在这种情形下被重新启动;

SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可;

SO_REUSEADDR允许单个进程捆绑同一端口到多个套接字上,只要每次捆绑指定不同的本地IP地址即可;
	- 在不支持该套接字选项的系统上,TCP服务器通常不使用这种方法,因为TCP服务器在建立连接后总是能够通过调用getsockname来确定客户请求的目的IP
地址。然而对于希望在一个多目的主机的若干个(而非全部)本地地址上服务连接的TCP服务器进程来说,仍需采用这种方法;

SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口已绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接字上。一般来说本特性仅支持UDP套接字;
	- 本特性用于多播时,允许在同一个主机上同时运行同一个应用程序的多个副本。当一个UDP数据报需由这些重复捆绑套接字中的一个接收时,所用规则为:
	如果该数据报的目的地址是一个广播地址或多播地址,那就给每个匹配的套接字递送一个该数据报的副本;但是如果该数据报的目的地址是一个单播地址,那
	么它只递送给单个套接字。在单播数据报情况下,如果有多个套接字匹配该数据报,那么该选择由哪个套接字接收它取决于实现;
5.12 SO_TYPE
本选项返回套接字的类型,通常由启动时继承了套接字的进程使用;
5.13 SO_USELOOPBACK
本选项仅用于路由域的套接字;它的默认设置为打开。当本选项开启时,相应套接字将接收在其上发送的任何数据报的一个副本;

6、IPv4套接字选项

6.1 IP_HDRINCL
如果本选项是给一个原始IP套接字设置的,那么我们必须为所有在该原始套接字上发送的数据报构造自己的IP首部;
一般情况下,在原始套接字上发送的数据报其IP首部是由内核构造的,不过有些应用程序需要构造自己的IP首部以取代IP置于该首部中的某些字段;
当本选项开启时,我们构造完整的IP首部,不过下列情况例外:
	- IP总是计算并存储P首部校验和;
	- 如果我们将P标识字段置为0,内核将设置该字段;
	- 如果源IP地址是INADDR_ANY,IP将把它设置为外出接口的主IP地址;
	- 如何设置IP选项取决于实现,有些实现取出我们预先使用IP_OPTIONS套接字选项设置的任何IP选项,把它们添加到我们构造的首部中,而其他实现则要求
	我们亲自在首部指定任何期望的IP选项;
	- IP首部中有些字段必须以主机字节序填写,有些字段必须以网络字节序填写,具体取决于实现。这使得利用本套接字选项编排原始分组的代码不像期待的那
	样便于移植;
6.2 IP_OPTIONS
本选项的设置允许我们在IPv4首部中设置IP选项;
6.3 IP_RECVDSTADDR
本套接字选项导致所收到UDP数据报的目的IP地址由recvmsg函数作为辅助数据返回;
6.4 IP_RECVIF套接字选项
本套接字选项导致所收到UDP数据报的接收接口索引由recvmsg函数作为辅助数据返回;
6.5 IP_TOS
本套接字选项允许我们为TCP、UDP或SCTP套接字设置IP首部中的服务类型字段;
若给本选项调用getsockopt,那么用于放入外出IP数据报首部的DSCP和ECN字段中的TOS当前值(默认为0)将返回,我们无法从接收到的IP数据报中取得该值;
6.6 TP_TTL
本选项设置或获取系统用在从某个给定套接字发送的单播分组上的默认TTL值;

7、ICMPv6套接字选项

这个唯一的套接字选项由ICMPv6处理,它的级别(即getsockopt和setsockopt函数的第二个参数)为IPPROTO__ICMPV6;
7.1 ICMP6_FILTER
本选项允许我们获取或设置一个icmp6_filter结构,该结构指出256个可能的ICMPv6消息类型中哪些将经由某个原始套接字传递给所在进程;

8、IPv6套接字选项

8.1 IPV6_CHECKSUM
本选项指定用户数据中校验和所处位置的字节偏移。如果该值为非负,那么内核将:
	- 给所有外出分组计算并存储校验和;
	- 验证外来分组的校验和,丢弃所有校验和无效的分组;本选项影响除ICMPv6原始套接字以外的所有IPv6原始套接字。(内核总是给ICMPv6原始套接字计算并
	存储校验和。)如果指定本选项的值为-1,那么内核不会在相应的原始套接字上计算并存储外出分组的校验和,也不会验证外来分组的校验和。
8.2 IPV6_DONTFRAG
开启本选项将禁止为UDP套接字或原始套接字自动插入分片首部,外出分组中大小超过发送接口MTU的那些分组将被丢弃;
发送分组的系统调用不会为此返回错误,因为已发送出去仍在途中的分组也可能因为超过路径MTU而被丢弃;
应用进程应该开启IPV6_RECVPATHMTS选项以获悉路径MTU的变动。
8.3 IPV6_NEXTHOP套接字选项
本选项将外出数据报的下一跳地址指定为一个套接字地址结构,这是一个特权操作;
8.4 IPV6_PATHMTU套接字选项
本选项不能设置,只能获取,获取本选项时,返回值为由路径MTU发现功能确定的当前MTU;
8.5 IPV6_RECVDSTOPTS套接字选项
开启本选项表明,任何接收到的IPv6目的地选项都将由recvmsg作为辅助数据返回。本选项默认为关闭;
8.6 IPv6_RECVHOPLIM工T套接字选项
开启本选项表明,任何接收到的跳限字段都将由recvmsg作为辅助数据返回。本选项默认为关闭;
对IPv4而言,没有办法可以获取接收到的TTL字段。
8.7 IPv6_RECVHOPOPTS套接字选项
开启本选项表明,任何接收到的IPv6步跳选项都将由recvmsg作为辅助数据返回,本选项默认为关闭;
8.8 IPv6_RECVPATHMTU 套接字选项
开启本选项表明,某条路径的路径MTU在发生变化时将由recvmsg作为辅助数据返回(不伴随任何数据)
8.9 IPV6_RECVPKTINFO套接字选项
开启本选项表明,接收到的IPv6数据报的以下两条信息将由recvmsg作为辅助数据返回:目的IPv6地址和到达接口索引;
8.10 IPV6_RECVRTHDR套接字选项
开启本选项表明,接收到的IPv6路由首部将由recvmsg作为辅助数据返回。本选项默认为关闭;
8.11 IPV6_RECVTCLAss套接字选项
开启本选项表明,接收到的流通类别(包含DSCP和ECN字段〉将由recvmsg作为辅助数据返回。本选项默认为关闭;
8.12 IPv6_UNTCAST_HOPS套接字选项
本IPv6选项类似于IPv4的IP_TTL套接字选项。设置本选项会给在相应套接字上发送的外出数据报指定默认跳限,获取本选项会返回内核用于相应套接字的跳限值。
来自接收到的IPv6数据报中跳限字段的实际值通过使用IPVv6_RECVHOPLIMIT套接字选项取得;
8.13 IPV6_USE_MIN_MTU套接字选项
本选项设置为1表明,路径MTU发现功能不必执行,为避免分片,分组就使用IPv6的最小MTU发送;
本选项设置为0表明,路径MTU发现功能对于所有目的地都得执行;
本选项设置为-1表明(默认),路径MTU发现功能仅对单播目的地执行,对于多播目的地就使用最小MTU;
8.14 IPV6_V6ONLY套接字选项
在一个AF_INET6套接字上开启本选项将限制它只执行IPv6通信。本选项默认为关闭,不过有些系统存在默认开启本选项的手段;
8.15 IPV6_XXX套接字选项
大多数用于修改协议首部的IPv6选项假设:
	- 就UDP套接字而言,信息由recvmsg和sendmsg作为辅助数据在内核和应用进程之间传递;
	- 就TCP套接字而言,同样的信息改用getsockopt和setsockopt获取和设置;
套接字选项和辅助数据的类型一致,并且访问套接字选项的缓冲区所含的信息和辅助数据中存放的信息也一致;

9、TCP套接字选项

9.1 TCP_MAXSEG套接字选项
本选项允许我们获取或设置TCP连接的最大分节大小(MSS);返回值是我们的TCP可以发送给对端的最大数据量,它通常是由对端使用SYN分节通告的MSS,除非我们的TCP选择使用一个比对端通告的MSS小些的值;
如果该值在相应套接字的连接建立之前取得,那么返回值是未从对端收到MSS选项的情况下所用的默认值。还得注意的是,如果用上譬如说时间戳选项的话,那么实际用于连接中的最大分节大小可能小于本套接字选项的返回值,因为时间戳选项在每个分节中要占用12字节的TCP选项容量。
如果TCP支持路径MTU发现功能,那么它将发送的每个分节的最大数据量还可能在连接存活期内改变;若到对端的路径发送变动,该值就会有所调整;
9.2 TCP_NODELAY
本选项将禁止TCP的Nagle算法,默认下启动;
【Nagle算法】:防止一个连接在任何时刻有多个小分组待确认;该算法指出:如果某个给定连接上有待确认数据,那么原本应该作为用户写操作之响应的在该		
	连接上立即发送相应小分组的行为就不会发生,直到现有数据被确认为止。这里“小”分组的定义就是小于MSS的任何分组。TCP总是尽可能地发送最大大小的分组;

在这里插入图片描述

右图中,第一个字符独自作为一个分组发送,然而下两个字符没有立即发送,因为该连接上有一个小分组待确认;
	在时刻600处收到对第一个分组的ACK后(该ACK由第一个字符的回显捎带),这两个字符才被发送;
	在该分组在时刻1200处被确认之前,没有其他小分组被发送;

Nagle与ACK延滞算法联合使用

- 该算法使得TCP在接收到数据后不立即发送ACK,而是等待一小段时间(50~200ms),才发送ACK;TCP期待在这一小段时间内自身有数据发送回对端,被延
滞的ACK就可以由这些数据捎带,从而省掉一个TCP分节;
- 这种情形对于Rlogin和Telnet客户来说通常可行,因为它们的服务器一般都回显客户发送来的每个字符,这样对客户端字符的ACK完全可以在服务器对该字符的回显中捎带返回;
- 而对于服务器不在相反方向产生数据以便携带ACK的客户来说,这些客户可能会明显察觉延迟,由于客户要等到服务器的ACK延滞定时器超时才继续给服务器发送数据;此时需要用到该选项进行禁用;
- 另一类不适合使用Nagle算法和TCP的ACK延滞算法的客户是以若干小片数据向服务器发送单个逻辑请求的客户;
	【使用以下方法修正】:
		- 使用writev而不是两次调用write;
		- 把前4字节的数据和后396字节的数据复制到单个缓冲区中,然后对该缓冲区调用一次write;

10、SCTP套接字选项

10.1 SCTP_ADAPTION_LAYER套接字选项
在关联初始化期间,任何一个端点都可能指定一个适配层指示(32位无符号整数),可由两端的应用进程用来协调任何本地应用适配层;
	- 本选项允许调用者获取或设置将由本端提供给对端的适配层指示;
	- 获取本选项的值时,调用者得到的是本地套接字将提供给所有未来对端的值;
	- 要获取对端的适配层指示,应用进程必须预订适配层事件;
10.2 SCTP_ASSOCINFO
本套接字选项可用于以下三个目的:
	- 获取关于某个现有关联的信息;
	- 改变某个已有关联的参数;
	- 为未来的关联设置默认信息。
	在获取关于某个现有关联的信息时,应该使用sctp_opt_info函数而不是getsockopt函数。作为本选项的输入的是sctp_assocparams结构;
struct sctp_assocparams {
	/*
	存放待访问关联的标识,若调用setsockopt时置0,则sasoc_asocmaxrxt和sasoc_cookie_life字段代表将作为默认信息置在相应套接字上的值;
	若调用getsockopt时提供关联ID,返回就是特定于该关联的信息,否则如果置为0本子u但,返回的就是默认的端点设置信息;
	*/
	sctp_assoc_t sasoc_assoc_id;	
	/*
		存放的是某个关联在已发送数据没有得到确认的情况下尝试重传的最大次数。达到这个次数后SCTP放弃重传,报告用户对端不可用,然后关闭该关联
	*/
	u_int16_t sasoc_asocmaxrxt;
	/*
		存放对端目的地址数。它不能设置,只能获取;
	*/
	u_int16_t sasoc_number_peer_destinations;
	/*
		存放对端的当前接收窗口。该值表示还能发送给对端的数据字节总数。本字段是动态的,本地端点发送数据时其值减小,外地应用进程读取已经收到的数据时其值增大。它不能设置,只能获取;
	*/
	u_int32_t sasoc_peer_rwnd;
	/*
		存放本地SCTP协议栈当前通告对端的接收窗口。本字段也是动态的,并受so_SNDBUF套接字选项影响。它不能设置,只能获取;
	*/
	u_int32_t sasoc_local_rwnd;
	/*
		存放送给对端的状态cookie以毫秒为单位的有效期;防护重放攻击;
	*/
	u_int32_t sasoc_cookie_life;
};
10.3 SCTP_AUTOCLOSE套接字选项
本选项允许我们获取或设置一个SCTP端点的自动关闭时间;
自动关闭时间是一个SCTP关联在空闲时保持打开的秒数,SCTP协议栈把空闲定义为一个关联的两个端点都没有在发送或接收用户数据的状态。默认是禁止的;
自动关闭选项意在用于一到多式SCTP接口;
- 当设置本选项时,传递给它的整数值为某个空闲关联被自动关闭前的持续秒数,值为0表示禁止自动关闭;
- 本选项仅仅影响由相应本地端点将来创建的关联,已有关联保持它们的现行设置不变;
自动关闭功能可由服务器用来强制关闭空闲的关联,服务器无需为此维护额外的状态;
使用本特性的服务器应该仔细估算它的所有关联预期的最长空闲时间;
自动关闭时间设置过短会导致关联的过早关闭;
10.4 SCTP_DEFAULT_SEND_PARAM
SCTP有许多可选的发送参数,通常作为辅助数据传递,或者由sctp_sendmsg函数使用;
希望发送大量消息具有相同发送参数的应用进程可以使用本选项设置默认参数,从而避免使用辅助数据或执行sctp_senamsg调用;
本选项接受sctp_sndrcvinfo结构作为输入;
struct sctp_sndrcvinfo {
	u_int16_t sinfo_stream;		// 指定新的默认流,所有外出消息将被发送到该流中
	// 在设置默认发送参数时被忽略,当使用recvmsg或sctp_recvmsg函数接收消息时,本字段将存放由对端置于SCTPDATA块的流序号字段中的值
	u_int16_t sinfo_ssn;
	// 指定新的默认标志,它们将应用于所有消息发送
	u_int16_t sinfo_flags;
	// sinfo_ppid指定将置于所有外出消息中的SCTP净荷协议标识字段的默认值;
	u_int32_t sinfo_ppid;
	// sinfo_context指定新的默认上下文。本字段是个本地标志,用于检索无法发送到对端的消息;
	u_int32_t sinfo_context;
	// 指定新的默认生命期,它将应用于所有消息发送
	u_int32__t sinfo_tirmetolive;
	// 在设置默认发送参数时被忽略
	u_int32_t sinfo_tsn;
	// 设置默认发送参数时被忽略
	u_int32_t sinfo_cumtsn;
	// 指定请求者希望对其设置默认参数的关联标识
	sctp_assoc_t sinfo_assoc_id;
};

在这里插入图片描述

10.5 SCTP_DISABLE_FRAGMENTS
SCTP通常把太大而不适合置于单个SCTP分组中的用户消息分割成多个DATA块;
开启本选项将在发送端禁止这种行为,SCTP将为此向用户返送EMSGSIZE错误,并且不发送用户消息;SCTP的默认行为与本选项被禁止等效,即SCTP通常会对用户消息执行分片。
10.6 SCTP_EVENTS
本套接字选项允许调用者获取、开启或禁止各种SCTP通知。SCTP通知是由SCTP协议栈发送给应用进程的消息;
使用本选项传递一个sctp_event_subscribe结构即可预定8类事件的通知;

在这里插入图片描述

10.7 SCTP_GET_PEER_ADDR_INFO
用于获取给定对端地址的相关信息,包括拥塞窗口、平滑化后的RTT和MTU;
struct sctp _paddrinfo {
	sctp_assoc_t spinfo_assoc_id;		// 存放关联标识
	struct sockaddr__storage spinfo_address;	// 告知SCTP套接字要获取哪一个对端地址的信息
	int32_t spinfo_state;		// 状态
	u_int32_t spinfo__cwrd;		// 所指定对端地址维护的当前拥塞窗口		
	u_int32_t spinfo_srtt;		// 表示所指定对端地址的平滑化后RTT的当前估计值
	u_int32_t spinfo_rto;		// 用于指定对端地址的当前重传超时值;
	u_int32_t spinfo_mtu;		// 由路径MTU发现功能发发现的通往所指定对端地址的路径MTU的当前值;
};
10.8 SCTP_I_WANT_MAPPED_V4_ADDR
这个标志套接字选项用于为AF_INET6类型的套接字开启或禁止IPv4映射地址,其默认状态为开启;
注意,本选项开启时,所有IPv4地址在送往应用进程之前将被映射成一个IPv6地址;
本选项禁止时,SCTP套接字不会对IPv4地址进行映射,而是作为sockaddr_in结构直接传递;
10.9 SCTP_INITMSG
本套接字选项用于获取或设置某个SCTP套接字在发送INIT消息时所用的默认初始参数
struct sctp_initmsg {
	uint16_t sinit_num_ostreams;		// 表示应用进程要请求的外出SCTP流的数目
	uint16_t sinit_max_instreans;		// 表示应用进程准备运行的外来STCP流的最大数目
	uint16_t sinit_max_attermpts;		// 表示SCTP协议栈应该重传多少次初始INIT消息
	uint16_t sinit_max_init_timeo;		// 表示INIT定时器的最大RTO值;
};
10.10 SCTP_MAXBURST
本套接字选项允许应用进程获取或设置用于分组发送的最大猝发大小;
具体的SCTP实现有两种方法应用这个限制:
	- 把拥塞窗口缩减为当前飞行大小加上最大猝发大小与路径MTU的乘积;
	- 把该值作为--个独立的微观控制量,在任意一个发送机会最多只发送这个数自的分组;
10.11 SCTP_MAXSEG
本套接字选项允许应用进程获取或设置用于SCTP分片的最大片段大小;
当某个SCTP发送端从其应用进程收到一个大于这个大小的消息时,它将把该消息分割成多个块,以便分别传送到对端;
SCTP发送端通常使用的这个大小是通达它的对端的所有路径各目的MTU中的最小值;
设置本选项可以把这个大小降低到所指定的值。注意,SCTP可能以比本选项所请求的值更小的边界分割消息。当通达对端的呆条路伦的MTU变得比本选项所请
	求的值还要小时,这种偏小的分割就会发生。
最大片段大小是一个端点范围的设置,在一到多式接口中,它可能影响不止一个关联;
10.12 SCT_NODELAY
开启本选项将禁止SCTP的Nagle算法。本选项默认关闭;
10.13 SCTP_PEER_ADDR_PARAMS
本套接字选项允许应用进程获取或设置关于某个关联的对端地址的各种参数;
作为本选项的输入的是sctp_paddrparams结构。调用者必须在该结构中填写关联标识和或一个对端地址;
struct sctp paddrparams {
	/*
		存放在其上获取或设置参数信息的关联标识。如果该值为0,那么所访问的是端点默认参数,而不是特定于关联的参数。
	*/
	sctp_assoc_t spp_assoc_id;
	/*
		指定其参数待获取或待设置的对端IP地址。如果spp_assoc_id字段值为0,那么本字段被忽略。
	*/
	struct sockaddr_storage spp_address;
	/*
	表示心搏间隔时间。设置该值为sCTP_NO_HB将禁止心搏,为SCTP_ISSUE_HB将按请求心搏,为其他值则将把心搏间隔重置为以毫秒为单位的新值设置端点		
		默认参数时,不能使用sCTP_ISSUE_HB这个值。
	*/
	u_int32_t spp_hbinterval;
	/*
		表示在声明所指定对端地址为不活跃之前将尝试的重传次数。当主目的地址被声明为不活跃时,另外一个对端地址将被选为主目的地址。
	*/
	u_int16_t spp_pathrnaxrxt;
};
10.14 SCTP_PRIMARY_ADDR
本套接字选项用于获取或设置本地端点所用的主目的地址;主目的地址是本端发送给对端的所有消息的默认目的地址;
作为本选项的输入的是sctp_setprim结构;
struct sctp_setprim {
	// 存放在其上获取或设置当前主目的地址的关联标识
	sctp_assoc_t ssp_assoc_id;
	// 指定主目的地址
	struct sockaddr_storage ssp_addr ;
};
10.15 SCTP_RTOINFO
本套接字选项用于获取或设置各种RTO信息,它们既可以是关于某个给定关联的设置,也可以是用于本地端点的默认设置;
为了便于移植,当获取信息时,调用者应该使用sctp_opt_info函数而不是getsockopt函数;
作为本选项的输入的是sctp_rtoinfo结构;
struct sctp_rtoinfo {
	sctp_assoc_t srto_assoc_id;	// 存放关联标识
	uint32_t srto_initial;		// 存放用于对端地址的初始RTO值
	uint32_t srto_max;		// 存放在更新重传定时器时使用的最大RTO值
	uint32_t srto_min;		// 启动重传定时器时使用的最小RTO值
};
10.16 SCTP_SET_PEER_PRIMARY_ADDR
设置本套接字选项导致发送一个消息:请求对端把所指定的本地地址作为它的主目的地址;
作为本选项的输入的是sctp_setpeerprim结构;
调用者必须在该结构中填写关联标识和一个请求对端标为其主目的地址的本地地址。这个本地地址必须已经绑定在本地端点;
struct sctp_setpeerprim {
	sctp_assoc_t sspp_assoc_id;		// 指定在其上想要设置主目的地址的标识
	struct sockaddr__storage sspp_addr;	// 存放要对端设置为目的地址的本地地址 
};
10.17 SCTP_STATUS
本套接字选项用于获取某个SCTP关联的状态;
为了便于移植,调用者应该使用sctp_opt_info函数而不是getsockopt函数;
作为本选项的输入的是sctp_status结构;
调用者必须在该结构中填写关联标识,关于这个关联的信息将在返回时被填写到该结构的其他字段;
struct sctp_status {
	// 存放关联标识
	sctp_assoc_t sstat_assoc_id;	
	// 关联的总体状态	
	int32_t sstat_state;		
	// 存放本地端点对于对端接收窗口的当前估计	
	u_int32_t sstat_rwnd;			
	// 存放等着对端处理的未确认DATA块数目
	u_int16_t sstat__unackdata;		
	// 存放本地端点暂存并等着应用进程读取的未读DATA块数目
	u_int16_t sstat penddata;		 
	// 存放对端用于向本端发送数据的流的数目
	u_int16_t sstat_instrms;
	// 存放本端可用于向对端发送数据的流的数目
	u_int16_t sstat_outstrms;
	// 存放本地SCTP端点将其用作用户消息分割点的当前值
	u_int32_t sstat_fragmentation_point;
	// 存放当前主目的地址。主目的地址是向对端发送数据时使用的默认目的地址。
	struct sctp paddrinfo sstat primary;
};

在这里插入图片描述

11、fcntl函数

#include <fcntl.h>

int fcntl(int fd, int cmd, ...);
/**
- 非阻塞式I/O,通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型;
- 信号驱动式I/O,通过使用F_SETFL命令设置O_ASYNC文件状态标志,我们可以把一个套接字设置成一旦其状态发生变化,内核就产生一个SIGNO信号;
- F_SETOWN命令允许我们指定用于接收SIGIO和SIGURG信号的套接字属主,其中SIGIO信号是套接字被设置为信号驱动式IO型后产生的,SIGURG信号是在新的		
	带外数据到达套接字时产生的,F_GETOWN命令返回套接字的当前属主;
每种描述符都有一组F_GETFL命令获取或由F_SETFL命令设置的文件标志,影响套接字描述符的两个标志:
	- O_NONBLOCK:非阻塞式I/O;
	- O_ASYNC:信号驱动式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");
//【设置某个文件状态标志的方法】:先取得当前标志,与新标志逻辑或后再设置标志;
11.1 信号SIGIO和SIGURG
该两信号在已使用F_SETOWN命令给相关套接字指派了属主后才会产生;
【F_SETOWN】:参数可为正整数,即接收信号的进程ID;
		也可为负整数,绝对值指出接收信号的进程组ID;
【F_GETOWN】:把套接字属主作为fcntl函数的返回值返回,可为进程ID或进程组ID;
使用socket函数新创建的套接字没有属主,而一个新的套接字是从一个监听套接字创建来的,则套接字属主将由已连接字
从监听套接字继承而来;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值