《TCP/IP详解 卷2》 笔记: shutdown和close系统调用

版权声明:本文为博主原创文章,转载请注明原文出处。 https://blog.csdn.net/woay2008/article/details/79584971

shutdown系统调用关闭socket的读通道、写通道或读写通道(由how参数决定,how参数是FREAD和FWRITE的组合)。对于读通道,shutdown丢弃所有进程还没有读走的数据以及调用shutdown之后到达的数据。对于写通道,shutdown使协议作相应的处理。对于TCP,所有剩余的数据将被发送,发送完成后发送FIN。这就是TCP的半关闭特点。shutdown系统调用的代码如下:

int
shutdown(p, uap, retval)
	struct proc *p;
	register struct shutdown_args /* {
		syscallarg(int) s;
		syscallarg(int) how;
	} */ *uap;
	register_t *retval;
{
	struct file *fp;
	int error;

	if (error = getsock(p->p_fd, SCARG(uap, s), &fp)) /*从文件描述符获取file结构*/
		return (error);
	return (soshutdown((struct socket *)fp->f_data, SCARG(uap, how))); /*调用soshutdown函数*/
}

soshutdown函数的代码如下:

int
soshutdown(so, how)
	register struct socket *so;
	register int how;
{
	register struct protosw *pr = so->so_proto;

	how++;
	if (how & FREAD) /*关闭读通道*/
		sorflush(so);
	if (how & FWRITE) /*关闭写通道*/
		return ((*pr->pr_usrreq)(so, PRU_SHUTDOWN, /*以PRU_SHUTDOWN命令调用tcp_usrreq函数*/
		    (struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0));
	return (0);
}
关闭读通道的sorflush函数的代码如下:
void
sorflush(so)
	register struct socket *so;
{
	register struct sockbuf *sb = &so->so_rcv;
	register struct protosw *pr = so->so_proto;
	register int s;
	struct sockbuf asb;

	sb->sb_flags |= SB_NOINTR;
	(void) sblock(sb, M_WAITOK); /*给接收缓冲区加锁*/
	s = splimp();
	socantrcvmore(so); /*给socket置上SS_CANTRCVMORE位,并唤醒等待在接收缓冲区的进程*/
	sbunlock(sb);
	asb = *sb;
	bzero((caddr_t)sb, sizeof (*sb)); /*清空socket的表示缓冲区的sockbuf结构*/
	splx(s);
	sbrelease(&asb); /*丢弃接收缓冲区中的所有数据,同时释放mbuf*/
}
tcp_usrreq函数对PRU_SHUTDOWN命令的处理如下:
	/*
	 * Mark the connection as being incapable of further output.
	 */
	case PRU_SHUTDOWN:
		socantsendmore(so); /*给socket置上SS_CANTSENDMORE位,并唤醒等待在发送缓冲区的进程*/
		tp = tcp_usrclosed(tp);
		if (tp)
			error = tcp_output(tp);
		break;

tcp_usrclosed函数使TCP连接的状态转移到下一状态。如果需要发送FIN、ACK或ACK报文段,tcp_output函数会根据状态发送相应的报文段。tcp_usrclosed函数的代码如下:

/*
 * User issued close, and wish to trail through shutdown states:
 * if never received SYN, just forget it.  If got a SYN from peer,
 * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN.
 * If already got a FIN from peer, then almost done; go to LAST_ACK
 * state.  In all other cases, have already sent FIN to peer (e.g.
 * after PRU_SHUTDOWN), and just have to play tedious game waiting
 * for peer to send FIN or not respond to keep-alives, etc.
 * We can let the user exit from the close as soon as the FIN is acked.
 */
struct tcpcb *
tcp_usrclosed(tp)
	register struct tcpcb *tp;
{

	switch (tp->t_state) {

	case TCPS_CLOSED:
	case TCPS_LISTEN:
	case TCPS_SYN_SENT:
		tp->t_state = TCPS_CLOSED;
		tp = tcp_close(tp); /*这几个状态直接将状态置为CLOSED,然后调用tcp_close函数释放相关资源*/
		break;
        /*以下几个状态是TCP断开连接的正常状态*/
	case TCPS_SYN_RECEIVED:
	case TCPS_ESTABLISHED:
		tp->t_state = TCPS_FIN_WAIT_1;
		break;

	case TCPS_CLOSE_WAIT:
		tp->t_state = TCPS_LAST_ACK;
		break;
	}
	if (tp && tp->t_state >= TCPS_FIN_WAIT_2) /*FIN_WAIT_2和TIME_WAIT状态,连接已经算断开了*/
		soisdisconnected(tp->t_inpcb->inp_socket); /*socket置上SS_CANTRCVMORE和SS_CANTSENDMORE标志,唤醒相关进程*/
	return (tp);
}
tcp_close函数,释放协议相关的资源占用的内存。我删去了一个路由特性相关的代码,简化后的代码如下:
/*
 * Close a TCP control block:
 *	discard all space held by the tcp
 *	discard internet protocol block
 *	wake up any sleepers
 */
struct tcpcb *
tcp_close(tp)
	register struct tcpcb *tp;
{
	register struct tcpiphdr *t;
	struct inpcb *inp = tp->t_inpcb;
	struct socket *so = inp->inp_socket;
	register struct mbuf *m;

	/* free the reassembly queue, if any */
	t = tp->seg_next;
	while (t != (struct tcpiphdr *)tp) { /*释放重组队列中所有乱序报文段占用的内存*/
		t = (struct tcpiphdr *)t->ti_next;
		m = REASS_MBUF((struct tcpiphdr *)t->ti_prev);
		remque(t->ti_prev);
		m_freem(m);
	}
	if (tp->t_template)
		(void) m_free(dtom(tp->t_template)); /*释放TCP首部和IP首部模板*/
	free(tp, M_PCB); /*释放tcpcb结构*/
	inp->inp_ppcb = 0;
	soisdisconnected(so); /*socket置上SS_CANTRCVMORE和SS_CANTSENDMORE标志,唤醒相关进程*/
	/* clobber input pcb cache if we're closing the cached connection */
	if (inp == tcp_last_inpcb)
		tcp_last_inpcb = &tcb;
	in_pcbdetach(inp); /*释放socket和inpcb等结构*/
	tcpstat.tcps_closed++;
	return ((struct tcpcb *)0);
}

in_pcbdetach函数的代码如下:

int
in_pcbdetach(inp)
	struct inpcb *inp;
{
	struct socket *so = inp->inp_socket;

	so->so_pcb = 0;
	sofree(so); /*sofree的主要工作是释放socket的发送和接收缓冲区的数据,然后释放socket结构。如果socket与描述符关联,sofree直接返回!*/
	if (inp->inp_options)
		(void)m_free(inp->inp_options); /*释放IP选项*/
	if (inp->inp_route.ro_rt)
		rtfree(inp->inp_route.ro_rt); /*释放路由*/
	ip_freemoptions(inp->inp_moptions); /*释放多播路由选项*/
	remque(inp); /*将此inpcb结构从全局链表中移除*/
	FREE(inp, M_PCB); /*释放inpcb结构*/
}
对于socket类型的描述符,close系统调用会调用soclose函数,它的代码如下:
/*
 * Close a socket on last file table reference removal.
 * Initiate disconnect if connected.
 * Free socket when disconnect complete.
 */
int
soclose(so)
	register struct socket *so;
{
	int s = splnet();		/* conservative */
	int error = 0;

	if (so->so_options & SO_ACCEPTCONN) { /*关闭的是监听socket*/
		while (so->so_q0) /*终止队列中每个TCP连接*/
			(void) soabort(so->so_q0);
		while (so->so_q) /*终止队列中每个TCP连接*/
			(void) soabort(so->so_q);
	}
	if (so->so_pcb == 0)
		goto discard;
	if (so->so_state & SS_ISCONNECTED) { /*socket已建立连接*/
		if ((so->so_state & SS_ISDISCONNECTING) == 0) { /*未开始断开连接的工作*/
			error = sodisconnect(so); /*开始断开连接*/
			if (error)
				goto drop;
		}
		if (so->so_options & SO_LINGER) { /*socket设置了SO_LINGER选项*/
			if ((so->so_state & SS_ISDISCONNECTING) &&
			    (so->so_state & SS_NBIO))
				goto drop;
			while (so->so_state & SS_ISCONNECTED) /*close前,等待选项设置的时间*/
				if (error = tsleep((caddr_t)&so->so_timeo,
				    PSOCK | PCATCH, netcls, so->so_linger * hz))
					break;
		}
	}
drop:
	if (so->so_pcb) { /*如果socket的inpcb结构还存在*/
		int error2 =
		    (*so->so_proto->pr_usrreq)(so, PRU_DETACH, /*以PRU_DETACH命令调用tcp_usrreq函数*/
			(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
		if (error == 0)
			error = error2;
	}
discard:
	if (so->so_state & SS_NOFDREF)
		panic("soclose: NOFDREF");
	so->so_state |= SS_NOFDREF; /*socket不与任何描述符关联*/
	sofree(so); /*释放socket的发送和接收缓冲区的数据,然后释放socket结构*/
	splx(s);
	return (error);
}

soabort函数最终会调用tcp_drop函数来(异常)终止一个连接,tcp_drop函数的代码如下:

/*
 * Drop a TCP connection, reporting
 * the specified error.  If connection is synchronized,
 * then send a RST to peer.
 */
struct tcpcb *
tcp_drop(tp, errno)
	register struct tcpcb *tp;
	int errno;
{
	struct socket *so = tp->t_inpcb->inp_socket;

	if (TCPS_HAVERCVDSYN(tp->t_state)) { /*如果TCP的已接收过SYN报文段,说明已进行了连接同步*/
		tp->t_state = TCPS_CLOSED; /*状态置为CLOSED*/
		(void) tcp_output(tp); /*发送RST报文段*/
		tcpstat.tcps_drops++;
	} else
		tcpstat.tcps_conndrops++;
	if (errno == ETIMEDOUT && tp->t_softerror)
		errno = tp->t_softerror;
	so->so_error = errno; /*记录传入的错误码*/
	return (tcp_close(tp)); /*调用tcp_close函数释放相关的资源*/
}
sodisconnect函数的代码如下:
int
sodisconnect(so)
	register struct socket *so;
{
	int s = splnet();
	int error;

	if ((so->so_state & SS_ISCONNECTED) == 0) { /*连接未建立?*/
		error = ENOTCONN;
		goto bad;
	}
	if (so->so_state & SS_ISDISCONNECTING) { /*连接正在断开?*/
		error = EALREADY;
		goto bad;
	}
	error = (*so->so_proto->pr_usrreq)(so, PRU_DISCONNECT, /*以PRU_DISCONNECT命令调用tcp_usrreq函数*/
	    (struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
bad:
	splx(s);
	return (error);
}

tcp_usrreq函数对PRU_DISCONNECT命令调用tcp_disconnect函数处理,它的代码如下:

/*
 * Initiate (or continue) disconnect.
 * If embryonic state, just send reset (once).
 * If in ``let data drain'' option and linger null, just drop.
 * Otherwise (hard), mark socket disconnecting and drop
 * current input data; switch states based on user close, and
 * send segment to peer (with FIN).
 */
struct tcpcb *
tcp_disconnect(tp)
	register struct tcpcb *tp;
{
	struct socket *so = tp->t_inpcb->inp_socket;

	if (tp->t_state < TCPS_ESTABLISHED) /*连接尚未建立,直接调用tcp_close释放相关资源*/
		tp = tcp_close(tp);
	else if ((so->so_options & SO_LINGER) && so->so_linger == 0) /*设置了SO_LINGER选项,并且so_linger值为0*/
		tp = tcp_drop(tp, 0); /*调用tcp_drop函数终止连接。连接不经过TIME_WAIT状态,直接跳到CLOSED状态。*/
	else { /*正常的TCP连接断开*/
		soisdisconnecting(so); /*socket置上SS_ISDISCONNECTING|SS_CANTRCVMORE|SS_CANTSENDMORE位,唤醒相关进程*/
		sbflush(&so->so_rcv); /*丢弃所有滞留在接收缓存中的数据,发送缓存中的数据仍保留,tcp_output函数将试图发送剩余的数据*/
		tp = tcp_usrclosed(tp); /*更新连接状态*/
		if (tp)
			(void) tcp_output(tp); /*根据状态发送FIN、ACK或ACK报文段*/
	}
	return (tp);
}

tcp_usrreq函数对PRU_DETACH命令的处理代码片段如下:

	/*
	 * PRU_DETACH detaches the TCP protocol from the socket.
	 * If the protocol state is non-embryonic, then can't
	 * do this directly: have to initiate a PRU_DISCONNECT,
	 * which may finish later; embryonic TCB's can just
	 * be discarded here.
	 */
	case PRU_DETACH: /*在CLOSED、LISTEN、SYN_SENT和SYN_RECEIVED状态直接调用tcp_close函数释放资源*/
		if (tp->t_state > TCPS_LISTEN)
			tp = tcp_disconnect(tp); /*其余状态调用tcp_disconnect函数执行正常连接断开*/
		else
			tp = tcp_close(tp);
		break;

猜你喜欢

转载自blog.csdn.net/woay2008/article/details/79584971