当前位置: 首页 > 工具软件 > Linger > 使用案例 >

套接字选项SO_LINGER

池永长
2023-12-01

               在说明套接字选项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 -nlps0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
19: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 0
19: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 0
19: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 0
19: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 32741
19: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 0
19: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 32741
19: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 32741
19: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 32741
19: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 0
19: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 7936
19: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 0
19: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 -nlps0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
19: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 0
19: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 0
19: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 0
19: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 32741
19: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 0
19: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 32741
19: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 32741
19: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 32741
19: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 0
19: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 7936
19: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 0
19: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 0
19: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 0
19: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 0
19: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 0
19: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 0
19: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 0
19: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 24805
19: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 11296
19: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 0
19: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能保证数据能发送到对端。

 

 

 

 

 

 

 

 


 

 

 

 


 

 

 

 

 

 类似资料: