Linux网络编程基础--socket常用选项

《Linux高性能服务器编程》阅读笔记:

  Linux系统中,有控制文件描述符属性的通用Posix系统调用fcntl(),还有两个专门用来读取和设置socket文件描述符属性的方法:

#include <sys/types.h>
#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);

  (1) sockfd参数指定被操作的目标socket
  (2) level参数该描述符的使用的协议,如IPPROTO_IP(IPv4)、IPPROTO_IPV6(IPv6)、IPPROTO_TCP(TCP选项)
  (3) option_name参数指定要设置的属性的名字,如用于修改描述符的接收缓冲区大小的SO_RCVBUF
  (4) optval参数和optlen分别指定该属性要使用的值和值的属性,如修改缓冲区大小时optval为缓冲区的大小

  具体如下表格:

leveloption_nameoptval说明
SOL_SOCKET (通用socket协议,与具体协议无关) SO_DEBUGint打开调试信息
SO_REUSEADDRint重用本地地址(TIME_WAIT状态的地址)
SO_TYPEint获取socket类型
SO_ERRORint获取并清除socket错误状态
SO_DONTROUTEint不查看路由表,直接将数据发送给本地局域网的主机。含义和send()的flags参数设置为MSG_DONTROUTE标志类似
SO_RCVBUFintTCP接收缓冲区的大小
SO_SNDBUFintTCP发送缓冲区的大小
SO_KEEPALIVEint发送周期性特定报文以维持连接
SO_OOBINLINEint将接收到的带外数据(如果有的话)存留在普通数据的输入队列中,此时程序员不能使用带MSG_OOB标志的读操作来读取带外数据(而应该向读取普通数据那样读取带外数据)
SO_LINGERlinger(结构体)关闭连接时,若发送缓冲区还有数据则延迟关闭
SO_RCVLOWATintTCP接收缓冲区低水位标记
SO_SNDLOWATintTCP发送缓冲区低水位标记
SO_RCVTIMEOtimeval(结构体)接收数据超时时间
SO_SNDTIMEOtimeval(结构体)发送数据超时时间
IPPROTO_IP (IPv4协议) IP_TOSint服务类型
IP_TTLint生存时间
IPPROTO_IPV6 (IPv6选项) IPV6_NEXTHOPFsockaddr_in6下一跳的IP地址
IPV6_RECVPKTINFOint接收分组信息
IPV6_DONTFRAGint禁止分片
IPV6_RECVTCLASSint接收通信类型
IPPROTO_TCP (TCP选项) TCP_MAXSEGintTCP最大报文段大小
TCP_NODELAYint禁止Nagle算法

  函数执行成功返回0,否则返回-1并设置errno。

  下面是几个重要的socket选项:
  (1) SO_REUSEADDR
  TIME_WAIT状态出现的时机如下图所示:

这里写图片描述

  在TCP协议–TCP连接的状态转移中说道,当客户端主动发出FIN报文且得到服务端的确认、服务端的FIN报文后,在之后很长的时间内就一直处于TIME_WAIT状态,这时间为2MSL(2倍报文的最大生存时间)。若是服务端主动发起FIN报文,也会出现TIME_WAIT状态。处于TIME_WAIT状态的socket地址是不可被使用的,当socket被设置为SO_REUSEADDR选项后,可以强制使用尚处于TIME_TIME的socket地址,代码片段为:

int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);

int ret = 1;
setsocketopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof(ret));

struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &addr.sin_addr);
addr.sin_port = htons(port);
bind(sockfd, (struct sockaddr* )&addr, sizoef(addr));

  setsocketopt()设置套接字为SO_REUSEADDR后,即使sockfd处于TIME_WAIT状态,与之绑定的sock地址也能被立即重用。此外,内核参数

/proc/sys/net/ipv4/tcp_tw_recycle

  也可以打开快速回收被关闭的sockfd(默认为0表示关闭),从而使得TCP连接根本不会进入TIME_WAIT状态。

  (2) SO_RCVBUF和SO_SNDBUF
  SO_RCVBUF和SO_SNDBUF分别用来设置或者获取TCP接收缓冲区和发送缓冲区的大小。注意,虽然程序员能够使用setsockopt()来设置缓冲区的大小,但是系统还是会将其值加倍且不得小于某个值。TCP的接收缓冲区的最小值为2240字节,发送缓冲区的最小值为4480字节(不同的系统可能不同),其目的是确保一个TCP连接拥有足够的空闲缓冲区来处理拥塞。另外直接修改内核参数

/proc/sys/net/ipv4/tcp_rmem
/proc/sys/net/ipv4/tcp_wmem

  来强制接收/发送缓冲区大小没有限制。修改TCP接收发送缓冲区的代码片段为:

int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
ERRP(socket_fd <= 0, return -1, "socket");

int rcv_buf_size = 50;
int len = sizeof(rcv_buf_size);     
setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, len);
getsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, (socklen_t* )&len);
printf("the tcp recv buffer size after setting is %d\n", rcv_buf_size);

//...
int ret = bind(socket_fd, (struct sockaddr* )&address, sizeof(address));

ret = listen(socket_fd, 5);

int connfd = accept(socket_fd, (struct sockaddr* )&client, &client_addrlen);
//...

  这里将TCP接收缓冲区设置为50字节,由于它小于系统允许的TCP接收缓冲区的最小字节数(2240),所以系统会忽略此设置操作。缓冲区的大小会直接关系到数据在传输过程中接收通告的大小。

  (3) SO_RCVLOWAT和SO_SNDLOWAT
  SO_RCVLOWAT和SO_SNDLOWAT分别表示TCP接收缓冲区和发送缓冲区的低水位标记。当TCP接收缓冲区中可读数据的总大小大于其低水位标记时,应用程序可以从该socket上读取数据;当TCP发送缓冲区的空闲空间大于其低水位标记是,应用程序可以往对应的socket写入数据。应用程序一般是通过I/O复用技术来判断socket是否可读可写的:

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

  readfds和writefds装载着待被判断可读和可写的文件描述符,通过判断函数返回值即可知道是否可读可写了,而select()的判断依据正是基于上述的水位标记。默认情况下,TCP接收/发送缓冲区的低水位标记均为1字节。

  (4) SO_LINGER
  默认情况下close()系统调用关闭一个socket连接时,close()立即返回,内核的TCP模块负责将该socket对应的TCP发送缓冲区中残留的数据发送个对端。然而,SO_LINGER选项可用于控制close()系统调用在关闭TCP连接时的行为。SO_LINGER选项的选项值是一个linger结构体,其定义为:

struct linger
{
    int l_onoff;                /* 开启(非0)/关闭(0)该选项  */
    int l_linger;               /* 滞留时间 */
};

  (1) 当l_onoff为0时,SO_LINGER选项不起作用,close()还是以默认行为关闭socket
  (2) 当l_onoff不为0,l_linger为0时,close()立即返回,TCP模块将丢弃被关闭的socket的发送缓冲区残留的数据,且返回对端一个复位(RST)报文段。这种情况给服务端提供了异常终止一个连接的方法。
  (3) 当l_onoff不为0,l_linger大于0,且该发送缓冲区还存有待发送的数据:
  a. 若该socket是阻塞的,close()将等待一段长为l_linger的时间,直到TCP模块发送完所有残留数据并得到对端确认,如果该时间段内TCP模块没有发送完成并得到对端确认,close()返回-1并设置errn为EWOULDBLOCK。
  b. 若该socket为非阻塞的,close()立即返回,此时根据其返回值和errno判断残留数据是否发送完毕。

  另外要注意:
  对服务端而言,监听的socket和用来和客户端通信的socket并不是同一个,且socket选项一般程序员都是设置在后者,然而后者是accept()系统调用从监听队列中直接提取出来的socket,也就是说该socket已经完成了TCP建立连接的3次握手(至少也是完成前两个步骤,即socket连接进入SYN_RCVD状态),完成握手后的socket,某些属性选项将是无法设置,如TCP最大报文段选项是要在同步报文段中发送的,也就是要设置同步报文段发送前的socket。Linux提供的解决办法是: 对监听socket设置这些选项,那么accept()返回的连接用于和客户端通信的socket将自动继承这些选项。这些选项包括: SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG、TCP_NODELAY。
  对客户端来说,这些socket则是应该在connect()函数之前设置,因为connect()成功返回后,TCP三次握手已经完成。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值