socket选项函数功能:用于读取和设置socket文件描述符属性的方法*restrictoption_len);intsetsockopt(intsockfd,intlevel,intoption_name,constvoid*option_value,socklen_toption_len);socket选项表如下:getsockopt和setsockopt成功时返回0,失败时返回-1,并设置errno。对于服务端,有些socket选项只能在调用listen系统调用之前为监听socket设置。这是因为connectionsocket只能通过accept调用返回,accept从listen监听队列中接受的连接至少已经完成了TCP三次握手的前两步(因为listen监听队列中的连接至少进入了SYN_RCVD状态),这意味着服务器已经在接收到的连接上发送了一个TCP同步段。然而,一些套接字选项应该在TCP同步段中设置,例如TCP最大段选项。对于这种情况,Linux给开发者提供的解决方案是:为监听套接字设置这些套接字选项,那么accept返回的连接套接字会自动继承这些选项。这些选项包括: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的三次握手就已经完成了。SO_REUSEADDR选项前面讨论了TCP连接的TIME_WAIT状态,提到服务器程序可以通过设置socket选项SO_REUSEADDR强制使用处于TIME_WAIT状态的连接占用的socket地址。#include#include#include#include#include#include#include#include#includeintmain(intargc,char*argv[]){if(argc<=2){printf("usage:%sip_addressport_number\n",基名(argv[0]));返回1;}constchar*ip=argv[1];intport=atoi(argv[2]);intsock=socket(PF_INET,SOCK_STREAM,0);断言(袜子>=0);int重用=1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));结构sockaddr_in地址;bzero(&address,sizeof(地址));地址.sin_family=AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);地址.sin_port=htons(端口);intret=bind(sock,(structsockaddr*)&address,sizeof(address));断言(ret!=-1);ret=listen(袜子,5);断言(ret!=-1);结构sockaddr_in客户端;socklen_tclient_addrlength=sizeof(client);intconnfd=accept(sock,(structsockaddr*)&client,&client_addrlength);if(connfd<0){printf("errnois:%d\n",errno);}else{charremote[INET_ADDRSTRLEN];printf("connectedwithip:%sandport:%d\n",inet_ntop(AF_INET,&client.sin_addr,remote,INET_ADDRSTRLEN),ntohs(client.sin_port));关闭(connfd);}关闭(袜子);return0;}设置setsocketopt后,即使sock处于TIME_WAIT状态,绑定的socket地址也可以立即被重用。另外,我们还可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收关闭的socket,让TCP连接根本不进入TIME_WAIT状态,让应用程序立即重用本地套接字地址。SO_RCVBUF和SO_SNDBUF选项SO_RCVBUF和SO_SNDBUF选项分别表示TCP接收缓冲区和发送缓冲区的大小。但是,当我们使用setsockopt来设置TCP接收缓冲区和发送缓冲区的大小时,系统会把它的值加倍,并且不能小于它的最小值。TCP接收缓冲区的最小大小为256字节,发送缓冲区的最小大小为2048字节(但是,不同的系统可能有不同的默认最小值)。另外,我们可以直接修改内核参数/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制TCP接收缓冲区和发送缓冲区的大小没有最小限制。修改TCP发送绑定冲区的客户端程序:#include#include#include#include#include#include#include#defineBUFFER_SIZE512intmain(intargc,char*argv[]){if(argc<=3){printf("usage:%sip_addressport_numbersend_bufer_size\n",基名(argv[0]));返回1;}constchar*ip=argv[1];intport=atoi(argv[2]);结构sockaddr_in服务器地址;bzero(&server_address,sizeof(server_address));server_address.sin_family=AF_INET;inet_pton(AF_INET,ip,&server_address.sin_addr);server_address.sin_port=htons(端口);intsock=socket(PF_INET,SOCK_STREAM,0);断言(袜子>=0);intsendbuf=atoi(argv[3]);intlen=sizeof(sendbuf);setsockopt(sock,SOL_SOCKET,SO_SNDBUF,&sendbuf,sizeof(sendbuf));getsockopt(袜子,SOL_SOCKET,SO_SNDBUF,&sendbuf,(socklen_t*)&len);printf("设置后的tcp发送缓冲区大小为%d\n",sendbuf);if(connect(sock,(structsockaddr*)&server_address,sizeof(server_address))!=-1){charbuffer[BUFFER_SIZE];内存集(缓冲区,'a',BUFFER_SIZE);发送(袜子,缓冲区,BUFFER_SIZE,0);}关闭(袜子);return0;}修改TCP连接冲区的服务程序:#include#include#include#include#include#include#include#include#include#defineBUFFER_SIZE1024intmain(intargc,char*argv[]){如果(argc<=3){printf("usage:%sip_addressport_numberreceive_buffer_size\n",basename(argv[0]));返回1;}constchar*ip=argv[1];intport=atoi(argv[2]);结构sockaddr_in地址;bzero(&地址,sizeof(地址));地址.sin_family=AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);地址.sin_port=htons(端口);intsock=socket(PF_INET,SOCK_STREAM,0);断言(袜子>=0);intrecvbuf=atoi(argv[3]);intlen=sizeof(recvbuf);setsockopt(sock,SOL_SOCKET,SO_RCVBUF,&recvbuf,sizeof(recvbuf));getsockopt(sock,SOL_SOCKET,SO_RCVBUF,&recvbuf,(socklen_t*)&len);printf("设置后接收缓冲区大小为%d\n",recvbuf);intret=bind(sock,(structsockaddr*)&address,sizeof(address));断言(ret!=-1);ret=listen(袜子,5);断言(ret!=-1);结构sockaddr_in客户端;socklen_tclient_addrlength=sizeof(client);intconnfd=accept(sock,(structsockaddr*)&client,&client_addrlength);if(connfd<0){printf("errnois:%d\n",errno);}else{字符缓冲区[BUFFER_SIZE];memset(缓冲区,'\0',BUFFER_SIZE);while(recv(connfd,buffer,BUFFER_SIZE-1,0)>0){}close(connfd);}关闭(袜子);return0;}运行结果:root@iZbp1anc6yju2dks3nw5j0Z:~/test/socket#./client127.0.0.1123452000设置后的tcp发送缓冲区大小为4608root@iZbp1anc6yju2dks3nw5j0Z:~/test/socket#./server127.030.receivebuffersizeaftersetttingis2304上面解释过:当我们使用setsockopt来设置TCP接收缓冲区和发送缓冲区的大小时,系统会将其值加倍,并且不能小于其最小值SO_RCVLOWAT和SO_SNDLOWAToptionsLow缓冲区和发送缓冲区的水印。它们通常由I/O多路复用系统调用,以确定套接字是否可读或可写。当TCP接收缓冲区中的可读数据总量大于其低水位线时,I/O多路复用系统调用将通知应用程序可以从相应的套接字读取数据;当TCP发送缓冲区中的空闲空间(可以写入数据的空间)大于其低水位线时,I/O多路复用系统调用将通知应用程序可以将数据写入相应的套接字。默认情况下,TCP接收缓冲区的低水位标记和TCP发送缓冲区的低水位标记均为1字节。SO_LINGER选项SO_LINGER选项用于控制关闭TCP连接时关闭系统调用的行为。默认情况下,当我们使用close系统调用关闭一个socket时,close会立即返回,TCP模块负责将socket对应的TCP发送缓冲区中剩余的数据发送给对方。在设置SO_LINGER选项的值时,我们需要传递一个linger类型的结构给setsockopt(getsockopt)系统调用,定义如下:#includestructlinger{intl_onoff;//启用(非0)或关闭(0)这个选项intl_linger;//停留时间};根据linger结构中两个成员变量的不同取值,close系统调用可能会产生以下三种行为之一:l_onoff等于0。此时SO_LINGER选项没有作用,close使用关闭套接字的默认行为。l_onoff不为0,l_linger等于0。此时close系统调用立即返回,TCP模块会丢弃已关闭的socket对应的TCP发送缓冲区中的剩余数据,同时发送一个reset段给对方。因此,这种情况为服务器异常终止连接提供了一种手段。l_onoff不为0,l_linger大于0。此时close的行为取决于两个条件:(1)已关闭的socket对应的TCP发送缓冲区中是否有残留数据;(2)socket是阻塞还是非阻塞。对于blockedsockets,close会等待一段时间l_linger,直到TCP模块发送完所有剩余数据并得到对方的确认。如果在此期间TCP模块没有发送剩余数据并收到对方的确认,close系统调用将返回-1并将errno设置为EWOULDBLOCK。如果套接字是非阻塞的,close将立即返回。这个时候我们需要根据它的返回值和errno来判断剩余的数据是否发送完毕。