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
.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 <stdio.h> /* */
#include <stdlib.h> /* exit() */
#include <string.h> /* memset(), memcpy() */
#include <sys/utsname.h> /* uname() */
#include <sys/types.h>
#include <sys/socket.h> /* socket(), bind(),
listen(), accept() */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h> /* 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 <port>\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选项会不会传递给子脚本?