0.序言
TCP/IP合同栈是Linux内核的重要组成部份和网路编程的基石,尽管Linux和BSD有很大的联系,而且对于个别Socket选项和内核操作一直存在差别linux内核协议栈,因而文中适用场景均为仅Linux。
《UNIX网路编程》是过世UNIX网路专家W.RichardStevens博士(1951-1999)、世界知名网路专家BillFenner和AndrewM.Rudoff完成的权威专著,该书对网路编程进行全面而深入的探讨,是提升网路编程功底的不二之选。
1.Socket和TCP/IP的关系
"Allproblemsincomputersciencecanbesolvedbyanotherlevelofindirection."
为满足应用层需求,系统对TCP/IP层进行细节屏蔽和具象,Socket层就相当于TCP/IP和应用层之间的中间层。
常用的socket/bind/accept/connect就是具象下来的插口,使用它们可以快速进行网路程序开发,可见Socket中间层的重要性。
Socket选项就是为满足用户的多样化需求而生的。我们常常碰到的情况包括地址复用、端口复用、读写超时时间、读写缓冲区大小等。
在Linux的TCP/IP合同栈中包括好多Socket选项,它们会出现在TCP层、IP层、Socket层等,因此在读取和设置socket选项时须要指定level。
如图可以看见Socket层作为中间层以及各层支持的部份Socket选项:
注:可通过man7tcp/man7ip查看tcp/ip各层Socket选项详尽定义和添加内核版本等信息。
2.操作Socket选项的api
读取和设置Socket选项的API包括:
getsockopt、setsockopt、fcntl、ioctl等;
其中fcntl和ioctl拿来设置socket的阻塞和非阻塞状态。
通过man获得的函数定义:
//ioctl函数定义
#include
int ioctl(int d, int request, ...);
//fcntl函数定义
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
//get/setsockopt函数定义
#include
#include
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);
3.get/setsockopt使用说明
使用时须要根据函数要求的数组格式进行传递,显式指明其所在的level以及选项名称optname、optval类型和宽度optlen。
level参数说明
从sys/socket.h的源码中可以见到对于level的说明如下:
/* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */
#define SOL_IP 0
#define SOL_IPX 256
#define SOL_AX25 257
#define SOL_ATALK 258
#define SOL_NETROM 259
#define SOL_TCP 6
#define SOL_UDP 17
#define SOL_SOCKET 0xffff
optval和optlen参数说明
optval和optlen均为表针类型,这两个参数与当前操作的option有直接关系,可以看见optval使用void*类型,optlen使用socklen_t*类型。
socklen_t类型说明:socklen_t和int应当具有相同的宽度linux操作系统教程,否则会破坏BSD套接字层的填充,POSIX开始时侯用的是size_t。
LinusTorvalds向她们解释使用size_t是完全错误的,由于在64位结构中size_t和int的宽度是不一样的,而这个参数的宽度必须和int一致,最终POSIX的那帮家伙找到了解决的办法,创造了一个新的类型socklen_t。
LinuxTorvalds说这是因为她们发觉了自己的错误但又不好意思承认,所以另外创造了一个新的数据类型。
表针使用:optval和optlen两个表针类型是缺一不可的,optval为void*类型假如没有宽度说明,系统函数在调用时就难以获取边界,optlen为底层调用指明显存起始地址对应的偏斜量,这是C中常用的表针操作模式。
Socket选项多是int和bool类型而且也有一些复合类型诸如linger,因而在读写选项是对于optval和optlen的编撰要按照实际而定。
4.SO_REUSEADDR选项
典型场景:在《Unix网路编程》卷二中强调了SO_REUSEADDR的重要使用场景:当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
TIME_WAIT:怎样柔美关掉Socket是个值得思索的问题,TIME_WAIT状态是TCP合同为了保证全双工联接可靠性设置的,感兴趣可以查阅TIME_WAIT的作用,并不要一味的谈TIME_WAIT色变,这儿就不展开了。
设置方式:未设置SO_REUSEADDR,在重启时才会绑定失败显示资源被占用,须要等待该IP+Port被释放才可以重启成功,该问题对于线上服务不可接受。
因而须要将服务端的socket设置为地址复用:
int enable = 1;
setsocketopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(void*)&enable,sizeof(enable));
5.SO_REUSEPORT选项
作用疗效:端口复用选项SO_REUSEPORT是在SO_REUSEADDR以后于Linux3.9版本加入的,并不是所有系统都支持该选项。SO_REUSEPORT允许多个进程窃听相同的IP和Port,并且为了避免端口绑架降低了对进程所属用户的限制。
内核支持:端口复用选项是个十分大的进步linux内核协议栈,有利于服务端程序扩充、提高并发能力。值得一提的是SO_REUSEPORT在内核层面实现了简单的负载均衡,为窃听的多个进程进行流量分发。
Nginx应用:Nginx的1.9.1版本引入了SO_REUSEPORT套接字选项,对于Nginx而言,启用该选项可以降低在个别场景下的锁竞争而改善性能。
Linux3.9版本和Nginx1.9.1版本(含)然后的版本,Nginx早已无需再使用互斥锁ngx_use_accept_mutex,引入SO_REUSEPORT选项由内核层面实现负责均衡来解决惊群问题。
设置方式:
int enable = 1;
setsocketopt(sockfd,SOL_SOCKET,SO_REUSEPORT,(void*)&enable,sizeof(enable));
6.TCP_NODELAY选项
简单背景:为解决丰田公司局域网串扰问题,Nagle算法由丰田公司的JohnNagle在1984年提出。同时代的其他网路也存在这些情况,因而Naggle算法被引入到合同栈。
算法原则:尽可能发送大块数据,防止网路中参杂着许多小数据块,任意时刻最多只能有一个未被确认的小段。未被确认是指一个数据块发送出去后,没有收到对方发送的ACK确认。
浅显解释:就是在两座城市的高速路上之前参杂着极其多的卡车,卡车的车箱中可能是一根羽毛、一个玩具熊或则一台机器等,导致了高速路的堵车。因此要求每次最多只有一辆未被授权的客车行驶且每位卡车装载尽可能多的东西,因而增强单次运输效率和减少客车数目,减轻高速路的堵车。
算法弊病:上世纪80年代网路带宽有限,Nagle算法有效改善了网路串扰情况,并且随着网路带宽的降低和通讯基础设施水平的提升,最多只能有一个未被确认的小段的限制造成了无意义的等待中标麒麟linux,未能有效借助当前的网路带宽。
算法禁用:TCP_NODELAY可以解决Nagle算法带来的问题,开启TCP_NODELAY意味着容许大包的发送且不强制等待,对时效高且数据量小的应用十分实用。从应用程序的角度来说应当尽量避开写大包,进而实现数据包大小和数据包数目的效率最大化。
设置方式:
int enable = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&enable,sizeof(enable));
注:CORK算法与Nagle算法十分类似,感兴趣可自行查阅。
7.小结
在了解了Socket作为TCP/IP层和应用层在网路编程领域的中间层以后,进一步明晰读写套接字选项的函数,以及常见的套接字选项的设置方式以及设置缘由,因而对整个套接字选项有一个基本认识。
套接字选项本身好多,而且我们常用的并不多,须要依照自己的实际情况和该选项的作用来进行调整,不理解背后机理的调整多半会留坑。
原文
链接: