linux下SO_LINGER使⽤,SO_LINGER选项的作⽤和意义⼀、选项在内核中的使⽤
搜索⼀下内核中对于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
pat_setsockopt = compat_sock_common_setsockopt, pat_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 (iffin@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);
}fprintf作用
port = atoi(argv[1]);
serverSocket = socket(PF_INET, SOCK_STREAM,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论