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

linux下SO_LINGER使用,SO_LINGER选项的作用和意义

万俟小林
2023-12-01

一、选项在内核中的使用

搜索一下内核中对于SO_LINGER的使用,主要集中在socket的关闭、两个必不可少的set/get sockopt函数中,所以真正使用这个选项的地方并不多,所以分析起来可能并不复杂,也没什么影响,但是正如之前所说的,问题的严重性和重要性往往不是问题本身决定的,而是它可能引起的后果决定的,所以还是简单总结一下这个选项的意义。

两个读取和设置该选项的内容就直接跳过了,现在直接看一下这个参数真正起作用的位置。

二、socket文件的关闭

sock_close--->>>sock_release

if (sock->ops) {

struct module *owner = sock->ops->owner;

sock->ops->release(sock);

sock->ops = NULL;

module_put(owner);

}

对于TCP连接来说,这个注册的位置在static int __init inet_init(void)函数中初始化

for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)

inet_register_protosw(q);

注册的TCP结构为

{

.type =       SOCK_STREAM,

.protocol =   IPPROTO_TCP,

.prot =       &tcp_prot,

.ops =        &inet_stream_ops,

.capability = -1,

.no_check =   0,

.flags =      INET_PROTOSW_PERMANENT |

INET_PROTOSW_ICSK,

},

注册之后TCP socket创建的时候通过inet_create中找到对应的注册协议,然后初始化sock的操作

sock->ops = answer->ops;

所以这里的sock的ops就被初始化为下面的结构

const struct proto_ops inet_stream_ops = {

.family           = PF_INET,

.owner           = THIS_MODULE,

.release       = inet_release,

.bind           = inet_bind,

.connect       = inet_stream_connect,

.socketpair       = sock_no_socketpair,

.accept           = inet_accept,

.getname       = inet_getname,

.poll           = tcp_poll,

.ioctl           = inet_ioctl,

.listen           = inet_listen,

.shutdown       = inet_shutdown,

.setsockopt       = sock_common_setsockopt,

.getsockopt       = sock_common_getsockopt,

.sendmsg       = inet_sendmsg,

.recvmsg       = sock_common_recvmsg,

.mmap           = sock_no_mmap,

.sendpage       = tcp_sendpage,

#ifdef CONFIG_COMPAT

.compat_setsockopt = compat_sock_common_setsockopt,

.compat_getsockopt = compat_sock_common_getsockopt,

#endif

}

接下来的事情就水到渠成了。

三、socket关闭时SO_LINGER的作用

1、网络层关闭

inet_release中

if (sk) {

long timeout;

/* Applications forget to leave groups before exiting */

ip_mc_drop_socket(sk);

/* If linger is set, we don't return until the close

* is complete.  Otherwise we return immediately. The

* actually closing is done the same either way.

*

* If the close is due to the process exiting, we never

* linger..

*/

timeout = 0;

if (sock_flag(sk, SOCK_LINGER) &&

!(current->flags & PF_EXITING)) 此时LINGER第一次出场,此时如果设置了SOCK_LINGER选项,此时的超时时间为linger中设置的超时时间,如果timeout为零表示无限等待。

timeout = sk->sk_lingertime;

sock->sk = NULL;

sk->sk_prot->close(sk, timeout);

}

2、tcp层关闭

tcp_close中对于SO_LINGER的处理

if (data_was_unread) {

/* Unread data was tossed, zap the connection. */

NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);

tcp_set_state(sk, TCP_CLOSE);

tcp_send_active_reset(sk, GFP_KERNEL);

} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {如果设置了SO_LINGER选项,并且LINGER时间为0,则直接丢掉缓冲区中未发送数据,否则在在下面的if分支中判断进入FIN_WAIT1状态,并且发送tcp_fin包,开始断开连接,在tcp_disconnect中通过tcp_need_reset判断,会向对方发送RESET命令,导致对方不优雅的结束。

/* Check zero linger _after_ checking for unread data. */

sk->sk_prot->disconnect(sk, 0);

NET_INC_STATS_USER(LINUX_MIB_TCPABORTONDATA);

} else if (tcp_close_state(sk)) {

/* We FIN if the application ate all the data before

* zapping the connection.

*/

/* RED-PEN. Formally speaking, we have broken TCP state

* machine. State transitions:

*

* TCP_ESTABLISHED -> TCP_FIN_WAIT1

* TCP_SYN_RECV    -> TCP_FIN_WAIT1 (forget it, it's impossible)

* TCP_CLOSE_WAIT -> TCP_LAST_ACK

*

* are legal only when FIN has been sent (i.e. in window),

* rather than queued out of window. Purists blame.

*

* F.e. "RFC state" is ESTABLISHED,

* if Linux state is FIN-WAIT-1, but FIN is still not sent.

*

* The visible declinations are that sometimes

* we enter time-wait state, when it is not required really

* (harmless), do not send active resets, when they are

* required by specs (TCP_ESTABLISHED, TCP_CLOSE_WAIT, when

* they look as CLOSING or LAST_ACK for Linux)

* Probably, I missed some more holelets.

*                         --ANK

*/

tcp_send_fin(sk);

}

3、FIN_WAIT1何时结束

由于发送方发送了FIN包,所以当FIN包被确认之后就可以认为FIN1时间结束了。如果对方一直不发送FIN的回应包,那么此时只有等待正常的TCP写操作超时来关闭套接口。

如果设置了LINGER1表示,在inet_release中,这个lingertime将会传递给tcp_close函数,从而在lingertime之后超时,超时之后套接口被关闭,这也就是lingertime非零时的意义。

四、如果listen套接口设置了该选项,accept的套接口是否会继承该选项

tcp_check_req--->>tcp_v4_syn_recv_sock--->>>tcp_create_openreq_child--->>>inet_csk_clone--->>sk_clone

大家看看在这个过程中有没有修改SO_LINGER选项所在的sk_flags字段和超时时间所在的sk_lingertime,答案是否定的,也就是说这些字段都是直接clone了父套接口,也就是listen的标志状态。

下面是验证代码,测试代码由下面程序简单修改之后得到例子代码原版

修改之后代码

/*

* Listing 1:

* Simple "Hello, World!" server

* Ivan Griffin (ivan.griffin@ul.ie)

*/

#include    /* */

#include   /* exit() */

#include   /* memset(), memcpy() */

#include    /* uname() */

#include

#include    /* socket(), bind(),

listen(), accept() */

#include

#include

#include

#include   /* fork(), write(), close() */

/*

* prototypes

*/

int _GetHostName(char *buffer, int length);

/*

* constants

*/

const char MESSAGE[] = "Hello, World!\n";

const int BACK_LOG = 5;

int main(int argc, char *argv[])

{

int serverSocket = 0,

on = 0,

port = 0,

status = 0,

childPid = 0;

struct hostent *hostPtr = NULL;

char hostname[80] = "";

struct sockaddr_in serverName = { 0 };

if (2 != argc)

{

fprintf(stderr, "Usage: %s \n",

argv[0]);

exit(1);

}

port = atoi(argv[1]);

serverSocket = socket(PF_INET, SOCK_STREAM,

IPPROTO_TCP);

if (-1 == serverSocket)

{

perror("socket()");

exit(1);

}

/*

* turn off bind address checking, and allow

* port numbers to be reused - otherwise

* the TIME_WAIT phenomenon will prevent

* binding to these address.port combinations

* for (2 * MSL) seconds.

*/

on = 1;

/*

status = setsockopt(serverSocket, SOL_SOCKET,

SO_REUSEADDR,

(const char *) &on, sizeof(on));

if (-1 == status)

{

perror("setsockopt(...,SO_REUSEADDR,...)");

}

*/

/*

* when connection is closed, there is a need

* to linger to ensure all data is

* transmitted, so turn this on also

*/

{

struct linger linger = { 0 };

linger.l_onoff = 1;

linger.l_linger = 0;

status = setsockopt(serverSocket,

SOL_SOCKET, SO_LINGER,

(const char *) &linger,

sizeof(linger));

if (-1 == status)

{

perror("setsockopt(...,SO_LINGER,...)");

}

}

/*

* find out who I am

*/

status = _GetHostName(hostname,

sizeof(hostname));

if (-1 == status)

{

perror("_GetHostName()");

exit(1);

}

hostPtr = gethostbyname(hostname);

if (NULL == hostPtr)

{

perror("gethostbyname()");

exit(1);

}

(void) memset(&serverName, 0,

sizeof(serverName));

(void) memcpy(&serverName.sin_addr,

hostPtr->h_addr,

hostPtr->h_length);

/*

* to allow server be contactable on any of

* its IP addresses, uncomment the following

* line of code:

* serverName.sin_addr.s_addr=htonl(INADDR_ANY);

*/

serverName.sin_family = AF_INET;

/* network-order */

serverName.sin_port = htons(port);

status = bind(serverSocket,

(struct sockaddr *) &serverName,

sizeof(serverName));

if (-1 == status)

{

perror("bind()");

exit(1);

}

status = listen(serverSocket, BACK_LOG);

if (-1 == status)

{

perror("listen()");

exit(1);

}

for (;;)

{

struct sockaddr_in clientName = { 0 };

int slaveSocket, clientLength =

sizeof(clientName);

(void) memset(&clientName, 0,

sizeof(clientName));

slaveSocket = accept(serverSocket,

(struct sockaddr *) &clientName,

&clientLength);

if (-1 == slaveSocket)

{

perror("accept()");

exit(1);

}

childPid = fork();

switch (childPid)

{

case -1: /* ERROR */

perror("fork()");

exit(1);

case 0: /* child process */

close(serverSocket);

if (-1 == getpeername(slaveSocket,

(struct sockaddr *) &clientName,

&clientLength))

{

perror("getpeername()");

}

else

{

printf("Connection request from %s\n",

inet_ntoa(clientName.sin_addr));

}

/*

* Server application specific code

* goes here, e.g. perform some

* action, respond to client etc.

*/

write(slaveSocket, MESSAGE,

strlen(MESSAGE));

struct linger linger = { 0 };

socklen_t socklen;

status = getsockopt(serverSocket,

SOL_SOCKET, SO_LINGER,

&linger,

(socklen_t*)&socklen);

printf("status %d linger.",status, linger.l_onoff ,linger.l_linger );

close(slaveSocket);

exit(0);

default: /* parent process */

close(slaveSocket);

}

}

return 0;

}

/*

* Local replacement of gethostname() to aid

* portability */

int _GetHostName(char *buffer, int length)

{

struct utsname sysname = { 0 };

int status = 0;

status = uname(&sysname);

if (-1 != status)

{

strncpy(buffer, sysname.nodename, length);

}

return (status);

}

2、测试例子

编译服务器代码

[root@Harry socklinger]# gcc socklinger.c

[root@Harry socklinger]# ./a.out 2222

通过Telnet连接该端口

[root@Harry ~]# telnet 127.0.0.1 2222

Trying 127.0.0.1...

Connected to 127.0.0.1.

Escape character is '^]'.

Hello, World!

Connection closed by foreign host.

[root@Harry ~]#

服务器端输出代码

Connection request from 127.0.0.1

status 0 linger.onoff 1 linger.linger 0

3、其它相关问题

make中命令行设置的-i选项会不会传递给子make,bash的 -e选项会不会传递给子脚本?

标签:选项,status,linger,LINGER,sock,TCP,sk,SO,inet

来源: https://www.cnblogs.com/tsecer/p/10487522.html

 类似资料: