7.1 获取和设置选项
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); // 成功0 ,错误-1
其中optval
是0或是非0值表示是否开启,其他结构提表示设置值
7.2 level:SOL_SOCKET
7.2.1 SO_BROADCAST
optval:int类型
是否允许广播
不能再基于连接的传输协议和点对点链路上进行广播
应用程序在发送广播数据包之前必须设置本套接字选项,能够有效的防止一个进程在其应用程序本没有设计成可广播的时候,发送广播数据报.
如果没有设置SO_BROADCAST
选项,返回EACCES
错误
7.2.2 SO_DONTROUTE
int
套接字外出分组逃过底层协议的正常路由选路.
通常是在一个子网直连网络上.
如果不是在直连网络上,而又设置了该选项,返回ENETUNREACH
错误
这个选项和send
,`sendto
,sendmsg
使用了MSG_DONTROUTE
选项是相同的结果.
7.2.3 SO_ERROR
int
当套接字出现错误的时候,使用该选项,获取错误值,内核so_error变量
当套接字出现错误的时候,则该套接字为待处理错误:
-
多路复用下,该套接字可读可写
-
信号驱动下,SIGIO信号
当read套接字的时如果没有读到数据,同时so_error非0,则read返回so_error的值(而非0),同时内核重置so_error
write时,如果so_error非0,则返回so_error,同时内核重置so_error
so_error的值为标准的error中的一个.
7.2.4 SO_KEEPALIVE
int
保活选项.
Tcp定时给对端发送保活探测分节:
-
对端回复ACK,表示连接正常
-
对端RST回复,表示对端已经崩溃重启
so_error为
ECONNRESET
套接字本身被关闭 -
没有相应,超时后放弃,
so_error为
ETIMEOUT
如果收到了ICMP主机不可达的报文,表示对端主机没有崩溃只是不可达,此时so_error为
EHOSTUNREACH
这种情况是可能为对端主机崩溃,或是网络故障.
如果想要更改保活探测时间,不能从这两个函数中修改,应该修改系统的参数,此时对所有套接字起效(修改时间时).
7.2.5 SO_LINGER
struct linger
{
int l_onoff; // 0 关闭,非0,开启
int l_linger; //单位秒
};
表示,在close
套接字的时候,在发送缓冲区的数据,如果和处理
-
00
表示关闭,此时执行默认操作,close立即返回发送缓冲区的数据被发送出去,发送完毕后发送FIN分节关闭套接字(套接字引用计数为0时,也就是只有这个描述符关联了这个套接字)
第一个为0时,忽略第二个值
-
10
开启了选项,同时不等待数据发送完毕
close直接返回,发送RST分节,套接字状态直接到CLOSED,不进入TIME_WAIT阶段
-
1n
开启选项,同时设置等待事件
close阻塞直到发送完毕正常返回,如果指定时间内没有发送完,那么close返回
EWOULDBLOCK
错误.数据被丢弃
7.2.5 SO_RCVBUF SO_SNDBUF
int
每个套接字都有发送和接受缓冲区.
TCP的SO_RCVBUF是窗口通告大小,udp的则是接收到的数据不能放到缓冲区就丢弃
TCP连接在三次握手时交换窗口大小,因此需要在connect和listen之前设置该选项
TCP套接字缓冲区至少为MSS的四倍,而且为双数倍,原因在于,tcp双工,在一个管道中发送的包数量应该等于相反方向管道中ACK的数量,那么发送缓冲区的MSS就是双数倍大小,存在2n个待确认的包.
7.2.6 SO_RCVLOWAT SO_SNDLOWAT
IO复用用的何时需要唤醒一个套接字可读和可写
SO_RCVLOWAT一般都为1
SO_SNDLOWAT:TCP为2048.udp因为发送不需要保存副本,因此只要只要发送缓冲区大小大于套接字低水位标记,udp总是可写
7.2.7 SO_REUSEADDR
int
linux中,只要一个端口正在使用,也就是说端口上有链接,那么就不能在该端口上启动另一个连接
S_REUSEADDR:
-
允许一个监听服务器绑定在一个已经存在连接的本地端口上(此时该链接不是监听连接,而一个服务连接)
主要在于让bind成功
-
同一端口启动同一服务器的多个实例,只要ip不同即可
-
允许单进程绑定同一端口到多个套接字上,只要本地ip地址不同即可
-
协议允许的话,相同的ip和端口可已绑定在同一套接字上
这里的套接字都是只外出,监听的套接字,也就是说,用了同一台电脑的同一协议端口,(可能)同一ip地址
需要在bind之前设置
对于bind到同一端口的不同ip的进程,数据包如何分配的问题,是更加通用的优先获得.最后再分给最通用的
7.2.8 SO_REUSEPORT
int
-
允许重复ip和端口绑定
但是必须这些套接字都开启了该选项
-
如果是一个多播地址,那么SO_REUSEADDR == SO_REUSEPORT
7.2.9 SO_TYPE
int
返回套接字类型
7.3 level:IPPEOTO_IP
ipv4套接字选项
7.3.1 IP_HDRINCL
int
应用于原始套接字.是否需要我们手动构造ip头部,开启表示我们手动构造.
开启是内核为我们构造的是:
-
ip首部校验和
-
ip标识字段为0,内核为构造
-
ip源地址为INADDR_ANY,内核填充
-
设置的ip选项,内核填充
诸暨序和网络序手动确定
7.3.2 IP_OPTIONS
int
是否允许设置ip选项
7.3.3 IP_TTL
int
设置TTL
7.4 level:IPPROTO_TCP
7.4.1 TCP_MAXSEG
int
设置TCP连接最大分节大小MSS.
该选项必须在connect之前,因为MSS是在SYN中告诉对端
7.4.2 TCP_NODELAY
int
是否禁用nagle算法.
在接收到ACK之前,那么任何小于MSS的报文将被组合为一个MSS,在收到ACK后发送,因此是一种等停式的传输.
nagle与延时ACK构成短暂的死锁.
SO_REUSEADDR和SO_REUSEPORT区别
如果不用SO_REUSEADDR
的话,如果我们将socketA
绑定到0.0.0.0:21
,那么任何将本机其他socket绑定到端口21
的举动(如绑定到192.168.1.1:21
)都会导致EADDRINUSE
错误。因为0.0.0.0
是一个通配符IP地址,意味着任意一个IP地址,所以任何其他本机上的IP地址都被系统认为已被占用。如果设置了SO_REUSEADDR
选项,因为0.0.0.0:21
和192.168.1.1:21
并不是完全相同的地址端口对(其中一个是通配符IP地址,另一个是一个本机的具体IP地址),所以这样的绑定是可以成功的。需要注意的是,无论socketA
和socketB
初始化的顺序如何,只要后一个设置了SO_REUSEADDR
,绑定都会成功;而只要没有设置SO_REUSEADDR
,绑定都不会成功。
SO_REUSEPORT
基本上来说,SO_REUSEPORT
允许我们将任意数目的socket绑定到完全相同的源地址端口对上,只要所有之前绑定的socket都设置了SO_REUSEPORT
选项。如果第一个绑定在该地址端口对上的socket没有设置SO_REUSEPORT
,无论之后的socket是否设置SO_REUSEPORT
,其都无法绑定在与这个地址端口完全相同的地址上。除非第一个绑定在这个地址端口对上的socket释放了这个绑定关系。与SO_REUSEADDR
不同的是 ,处理SO_REUSEPORT
的代码不仅会检查当前尝试绑定的socket的SO_REUSEPORT
,而且也会检查之前已绑定了当前尝试绑定的地址端口对的socket的SO_REUSEPORT
选项。如果当前一个socket没有设置SO_REUSEPORT
已经处于TIME_WAIT
阶段,而这个设置了SO_REUSEPORT
选项的新socket尝试绑定到当前地址,这个绑定操作也会失败。