套接字选项SO_LINGER

来源:互联网 发布:侠客风云传优化差 编辑:程序博客网 时间:2024/05/17 09:25

               在说明套接字选项SO_LINGER之前,我们来先看一个问题。如果发送缓冲区中还有数据没有发送到对方协议栈,此时close发送端的socket会发生什么,下面代码给出答案。

服务端:

#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>#include <signal.h>#include <sys/wait.h>int main(){    char recvbuf[100000]={0};    int sockSrv = socket(AF_INET, SOCK_STREAM, 0);    struct sockaddr_in addrSrv;    addrSrv.sin_family = AF_INET;    addrSrv.sin_addr.s_addr =htonl(INADDR_ANY);    addrSrv.sin_port = htons(8888);    bind(sockSrv, (const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));    listen(sockSrv, 5);    sockaddr_in addrClient;    socklen_t len=sizeof(addrClient);    int sockConn = accept(sockSrv, (struct sockaddr *)&addrClient, &len);    while(1)    {       getchar();       read(sockConn,recvbuf,sizeof(recvbuf)-1);    }    getchar();    close(sockSrv);    return 0;}
客户端:

#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>#include <error.h>int main(){     int ret=0;     int sockClient = socket(AF_INET, SOCK_STREAM, 0);     char readbuf[175000]={0};     struct sockaddr_in addrSrv;     addrSrv.sin_addr.s_addr=inet_addr("127.0.0.1");     addrSrv.sin_family = AF_INET;     addrSrv.sin_port = htons(8888);     ret=connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));     write(sockClient,readbuf,sizeof(readbuf)+1);     getchar();     close(sockClient);     return 0;}

编译并运行,并用tcpdump进行抓包,在客户端多按几次回车键,用netstat命令查看情况。

[mapan@localhost ~]$ netstat -nao|grep 8888 tcp        0      0 0.0.0.0:8888                0.0.0.0:*                   LISTEN      off (0.00/0/0)tcp   161018      0 127.0.0.1:8888              127.0.0.1:41282             ESTABLISHED off (0.00/0/0)tcp        0 124001 127.0.0.1:41282             127.0.0.1:8888              ESTABLISHED probe (0.19/0/1)

其中1611018为服务端接收缓冲区中的数据,124001为发送缓冲区中的数据。此时由于服务端没有调用read函数读取数据,服务端的接收缓冲区已满,所以客户端的发送缓冲区的数据不能由TCP到达服务端的接收缓冲区中。如果这时我们突然断开客户端,客户端调用了close函数,close函数立即返回,发送缓冲区剩余的数据由系统接管将数据发送至对端,但是我们并不知道对方的是否已接收到数据,这时SO_LINGER选项改登场了。


SO_LINGER结构如下:

struct linger {        int l_onoff  //0=off, nonzero=on(开关)        int l_linger //linger time(延迟时间)  } 
1)当l_onoff为0时,l_linger的值被忽略,这也是close的默认操作。

2)当l_onoff为0时,l_linger的值也为0。在这个情况下,close将会延迟l_linger秒,这里是0秒。此时当调用close的时候,TCP连接会立即断开。发送缓冲区里面剩余的数据将被丢弃,并向对方发送一个RST,这是非正常的断开连接。下面看代码,服务端不变,客户端稍微变一下。

客户端:

#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>#include <error.h>int main(){     int ret=0;     int sockClient = socket(AF_INET, SOCK_STREAM, 0);     char readbuf[175000]={0};     struct sockaddr_in addrSrv;     addrSrv.sin_addr.s_addr=inet_addr("127.0.0.1");     addrSrv.sin_family = AF_INET;     addrSrv.sin_port = htons(8888);     struct linger so_linger;     so_linger.l_onoff=1;     so_linger.l_linger=0;     setsockopt(sockClient,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));     ret=connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));     write(sockClient,readbuf,sizeof(readbuf)+1);     getchar();     close(sockClient);          return 0;}

编译并运行,用netstat查看。

[mapan@localhost ~]$ netstat -nao|grep 8888 tcp        0      0 0.0.0.0:8888                0.0.0.0:*                   LISTEN      off (0.00/0/0)tcp   138900      0 127.0.0.1:8888              127.0.0.1:41332             ESTABLISHED off (0.00/0/0)tcp        0  36101 127.0.0.1:41332             127.0.0.1:8888              ESTABLISHED probe (1.23/0/0)
此时在客户端还卡在getchar()出,在客户端按下回车键,查看tcpdump抓包的结果。抓包结构如下:

[root@localhost mapan]# tcpdump -iany port 8888 -nlps0tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes19:06:53.835303 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [S], seq 2551942925, win 65495, options [mss 65495,sackOK,TS val 3407479544 ecr 0,nop,wscale 7], length 019:06:53.835318 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41336: Flags [S.], seq 3358092135, ack 2551942926, win 65483, options [mss 65495,sackOK,TS val 3407479544 ecr 3407479544,nop,wscale 7], length 019:06:53.835333 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [.], ack 1, win 512, options [nop,nop,TS val 3407479544 ecr 3407479544], length 019:06:53.835421 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [.], seq 1:32742, ack 1, win 512, options [nop,nop,TS val 3407479544 ecr 3407479544], length 3274119:06:53.835431 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41336: Flags [.], ack 32742, win 829, options [nop,nop,TS val 3407479544 ecr 3407479544], length 019:06:53.835466 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 32742:65483, ack 1, win 512, options [nop,nop,TS val 3407479544 ecr 3407479544], length 3274119:06:53.835558 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [.], seq 65483:98224, ack 1, win 512, options [nop,nop,TS val 3407479544 ecr 3407479544], length 3274119:06:53.835571 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 98224:130965, ack 1, win 512, options [nop,nop,TS val 3407479544 ecr 3407479544], length 3274119:06:53.874708 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41336: Flags [.], ack 130965, win 62, options [nop,nop,TS val 3407479584 ecr 3407479544], length 019:06:54.079730 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 130965:138901, ack 1, win 512, options [nop,nop,TS val 3407479789 ecr 3407479584], length 793619:06:54.079741 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41336: Flags [.], ack 138901, win 0, options [nop,nop,TS val 3407479789 ecr 3407479789], length 019:06:54.241704 IP 127.0.0.1.41336 > 127.0.0.1.ddi-tcp-1: Flags [R.], seq 138901, ack 1, win 512, options [nop,nop,TS val 3407479951 ecr 3407479789], length 0

客户端发RST了。

3)当l_onoff为非零,l_linger也为非零。此时调用close函数时,内核将会延迟l_linger秒直到所有数据都发送对端且收到应答消息或者l_linger秒被消耗完。如果l_linger被消耗完,发送缓冲区中还有数据,则剩余的数据被丢弃,并向对端发送RST。如果套接字处于非阻塞状态,发送缓冲区的数据立即丢弃,close返回,所以要判断一下close函数的返回值。还是服务端代码不变,客户端代码稍作修改,客户端代码如下:

#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>#include <error.h>int main(){     int ret=0;     int sockClient = socket(AF_INET, SOCK_STREAM, 0);     char readbuf[175000]={0};     struct sockaddr_in addrSrv;     addrSrv.sin_addr.s_addr=inet_addr("127.0.0.1");     addrSrv.sin_family = AF_INET;     addrSrv.sin_port = htons(8888);     struct linger so_linger;     so_linger.l_onoff=1;     so_linger.l_linger=60;     setsockopt(sockClient,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));     ret=connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));     write(sockClient,readbuf,sizeof(readbuf)+1);     getchar();     close(sockClient);     return 0;}

编译并运行,先运行服务端后运行客户端。和上面一样,此时客户端发送缓冲区内有残余数据。这时在客户端按下回车键会发现进程并没有结束,而是休眠了。然后在服务端连续按回车键,随着客户端进程就结束了,标明数据已全部发送到服务端且受到答复。我们看下tcpdump抓包结果:

[root@localhost mapan]# tcpdump -iany port 8888 -nlps0tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes19:27:14.937303 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [S], seq 1492693418, win 65495, options [mss 65495,sackOK,TS val 3408700646 ecr 0,nop,wscale 7], length 019:27:14.937319 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [S.], seq 1875150207, ack 1492693419, win 65483, options [mss 65495,sackOK,TS val 3408700646 ecr 3408700646,nop,wscale 7], length 019:27:14.937334 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], ack 1, win 512, options [nop,nop,TS val 3408700646 ecr 3408700646], length 019:27:14.937425 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], seq 1:32742, ack 1, win 512, options [nop,nop,TS val 3408700646 ecr 3408700646], length 3274119:27:14.937434 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 32742, win 829, options [nop,nop,TS val 3408700646 ecr 3408700646], length 019:27:14.937474 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 32742:65483, ack 1, win 512, options [nop,nop,TS val 3408700646 ecr 3408700646], length 3274119:27:14.937573 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], seq 65483:98224, ack 1, win 512, options [nop,nop,TS val 3408700646 ecr 3408700646], length 3274119:27:14.937586 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 98224:130965, ack 1, win 512, options [nop,nop,TS val 3408700646 ecr 3408700646], length 3274119:27:14.976713 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 130965, win 62, options [nop,nop,TS val 3408700686 ecr 3408700646], length 019:27:15.181743 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 130965:138901, ack 1, win 512, options [nop,nop,TS val 3408700891 ecr 3408700686], length 793619:27:15.181754 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 138901, win 0, options [nop,nop,TS val 3408700891 ecr 3408700891], length 019:27:15.386744 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], ack 1, win 512, options [nop,nop,TS val 3408701096 ecr 3408700891], length 019:27:15.386754 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 138901, win 0, options [nop,nop,TS val 3408701096 ecr 3408700891], length 019:27:15.796774 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], ack 1, win 512, options [nop,nop,TS val 3408701506 ecr 3408701096], length 019:27:16.616810 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [.], ack 1, win 512, options [nop,nop,TS val 3408702326 ecr 3408701096], length 019:27:16.616820 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 138901, win 0, options [nop,nop,TS val 3408702326 ecr 3408700891], length 019:27:16.807990 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 138901, win 780, options [nop,nop,TS val 3408702517 ecr 3408700891], length 019:27:16.808017 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [P.], seq 138901:163706, ack 1, win 512, options [nop,nop,TS val 3408702517 ecr 3408702517], length 2480519:27:16.808021 IP 127.0.0.1.41348 > 127.0.0.1.ddi-tcp-1: Flags [FP.], seq 163706:175002, ack 1, win 512, options [nop,nop,TS val 3408702517 ecr 3408702517], length 1129619:27:16.808033 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 163706, win 631, options [nop,nop,TS val 3408702517 ecr 3408702517], length 019:27:16.847713 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.41348: Flags [.], ack 175003, win 562, options [nop,nop,TS val 3408702557 ecr 3408702517], length 0
最后4行是客户端发送数据到服务端,服务端给出ACK回应。


通过上面的学习,我们知道SO_LINGER能保证数据能发送到对端。